@panaversity/ksor 0.0.0 → 0.0.2

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/NOTICE +8 -0
  3. package/README.md +29 -1307
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.mjs +384 -0
  6. package/dist/index.d.mts +34 -0
  7. package/dist/index.mjs +37 -0
  8. package/docs/index.md +53 -0
  9. package/package.json +52 -23
  10. package/templates/LICENSE +23 -0
  11. package/templates/scaffold/.agents/skills/add-sources/SKILL.md +43 -0
  12. package/templates/scaffold/.agents/skills/format-checker/SKILL.md +39 -0
  13. package/templates/scaffold/.agents/skills/format-checker/check.mjs +782 -0
  14. package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +46 -0
  15. package/templates/scaffold/.claude/skills/add-sources/SKILL.md +43 -0
  16. package/templates/scaffold/.claude/skills/format-checker/SKILL.md +39 -0
  17. package/templates/scaffold/.claude/skills/format-checker/check.mjs +782 -0
  18. package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +46 -0
  19. package/templates/scaffold/.gemini/settings.json +5 -0
  20. package/templates/scaffold/.gitattributes +5 -0
  21. package/templates/scaffold/.github/workflows/validate.yml +23 -0
  22. package/templates/scaffold/AGENTS.md +104 -0
  23. package/templates/scaffold/CLAUDE.md +1 -0
  24. package/templates/scaffold/README.md +63 -0
  25. package/templates/scaffold/gitignore +13 -0
  26. package/templates/scaffold/instance.md +26 -0
  27. package/templates/scaffold/knowledge/example.md +23 -0
  28. package/templates/scaffold/package.json +15 -0
  29. package/templates/scaffold/pnpm-lock.yaml +4041 -0
  30. package/templates/scaffold/pnpm-workspace.yaml +19 -0
  31. package/templates/scaffold/system/site/app/(home)/layout.tsx +6 -0
  32. package/templates/scaffold/system/site/app/(home)/page.tsx +83 -0
  33. package/templates/scaffold/system/site/app/api/search/route.ts +11 -0
  34. package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +53 -0
  35. package/templates/scaffold/system/site/app/docs/layout.tsx +24 -0
  36. package/templates/scaffold/system/site/app/global.css +26 -0
  37. package/templates/scaffold/system/site/app/icon.png +0 -0
  38. package/templates/scaffold/system/site/app/layout.tsx +41 -0
  39. package/templates/scaffold/system/site/app/llms-full.txt/route.ts +10 -0
  40. package/templates/scaffold/system/site/app/llms.txt/route.ts +15 -0
  41. package/templates/scaffold/system/site/components/built-with.tsx +18 -0
  42. package/templates/scaffold/system/site/components/mdx.tsx +15 -0
  43. package/templates/scaffold/system/site/lib/layout.shared.tsx +17 -0
  44. package/templates/scaffold/system/site/lib/shared.ts +51 -0
  45. package/templates/scaffold/system/site/lib/source.ts +119 -0
  46. package/templates/scaffold/system/site/next-env.d.ts +6 -0
  47. package/templates/scaffold/system/site/next.config.mjs +32 -0
  48. package/templates/scaffold/system/site/package.json +29 -0
  49. package/templates/scaffold/system/site/postcss.config.mjs +7 -0
  50. package/templates/scaffold/system/site/source.config.ts +35 -0
  51. package/templates/scaffold/system/site/tsconfig.json +35 -0
  52. package/bin/ksor.js +0 -22
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env node
2
+ import { exitCodes, resolveCommand, verbs } from "./index.mjs";
3
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { spawnSync } from "node:child_process";
6
+ import path from "node:path";
7
+ //#region src/init/errors.ts
8
+ /** The `code` of a Node system error, or null for anything that carries none. */
9
+ function errnoCode(value) {
10
+ if (typeof value !== "object" || value === null) return null;
11
+ const { code } = value;
12
+ return typeof code === "string" ? code : null;
13
+ }
14
+ /**
15
+ * Failures the environment caused and the environment must fix — exit 3. Any
16
+ * other throw is a ksor bug and keeps its stack rather than being dressed up
17
+ * as the operator's fault.
18
+ */
19
+ const ENVIRONMENT_CODES = /* @__PURE__ */ new Set([
20
+ "EACCES",
21
+ "EAGAIN",
22
+ "EBUSY",
23
+ "EDQUOT",
24
+ "EEXIST",
25
+ "EIO",
26
+ "EISDIR",
27
+ "ELOOP",
28
+ "EMFILE",
29
+ "ENAMETOOLONG",
30
+ "ENFILE",
31
+ "ENOENT",
32
+ "ENOSPC",
33
+ "ENOTDIR",
34
+ "ENOTEMPTY",
35
+ "EPERM",
36
+ "EROFS",
37
+ "EXDEV"
38
+ ]);
39
+ function isEnvironmentError(value) {
40
+ const code = errnoCode(value);
41
+ return code !== null && ENVIRONMENT_CODES.has(code);
42
+ }
43
+ //#endregion
44
+ //#region src/init/materialize.ts
45
+ const EMITTED_NAMES = /* @__PURE__ */ new Map([["gitignore", ".gitignore"]]);
46
+ const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
47
+ ".md",
48
+ ".json",
49
+ ".yaml",
50
+ ".yml",
51
+ ".ts",
52
+ ".tsx",
53
+ ".mjs",
54
+ ".js",
55
+ ".css",
56
+ ".txt"
57
+ ]);
58
+ function isTextFile(file) {
59
+ const base = path.basename(file);
60
+ if (base === "gitignore" || base === ".gitattributes") return true;
61
+ return TEXT_EXTENSIONS.has(path.extname(file));
62
+ }
63
+ /**
64
+ * Copy the template tree into targetDir, applying the two stamps to text
65
+ * files. Byte-determinism holds because templates are shipped bytes and the
66
+ * stamps are pure string substitution.
67
+ *
68
+ * Every path it brings into existence is appended to `created`, parents before
69
+ * children, so a caller that cannot rename-over (the `init .` form) can undo
70
+ * a half-written tree in reverse order.
71
+ */
72
+ function materialize(templateDir, targetDir, stamps, created = []) {
73
+ for (const entry of readdirSync(templateDir, { withFileTypes: true })) {
74
+ const from = path.join(templateDir, entry.name);
75
+ const to = path.join(targetDir, EMITTED_NAMES.get(entry.name) ?? entry.name);
76
+ if (entry.isDirectory()) {
77
+ if (!existsSync(to)) {
78
+ mkdirSync(to, { recursive: true });
79
+ created.push(to);
80
+ }
81
+ materialize(from, to, stamps, created);
82
+ } else if (isTextFile(from)) {
83
+ const text = readFileSync(from, "utf8").replaceAll("KSOR-STAMP-NAME", stamps.name).replaceAll("KSOR-STAMP-VERSION", stamps.version);
84
+ created.push(to);
85
+ writeFileSync(to, text);
86
+ } else {
87
+ created.push(to);
88
+ copyFileSync(from, to);
89
+ }
90
+ }
91
+ return created;
92
+ }
93
+ //#endregion
94
+ //#region src/init/name.ts
95
+ /** Project-name grammar (spec: `^[a-z0-9][a-z0-9-]{0,62}$`). */
96
+ const NAME_GRAMMAR = /^[a-z0-9][a-z0-9-]{0,62}$/;
97
+ /**
98
+ * Windows refuses these as directory names at the filesystem layer, whatever
99
+ * the grammar says — and the spec runs its acceptance on windows-latest, so a
100
+ * name accepted here must be a directory everywhere.
101
+ */
102
+ const WINDOWS_RESERVED = /* @__PURE__ */ new Set([
103
+ "con",
104
+ "prn",
105
+ "aux",
106
+ "nul",
107
+ ...Array.from({ length: 9 }, (_, index) => `com${index + 1}`),
108
+ ...Array.from({ length: 9 }, (_, index) => `lpt${index + 1}`)
109
+ ]);
110
+ function nameProblem(name) {
111
+ if (!NAME_GRAMMAR.test(name)) return "grammar";
112
+ if (WINDOWS_RESERVED.has(name)) return "windows-reserved";
113
+ return null;
114
+ }
115
+ function isValidName(name) {
116
+ return nameProblem(name) === null;
117
+ }
118
+ /** A best-effort valid slug from an invalid name, for the remedy line. */
119
+ function suggestName(input) {
120
+ const slug = input.toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/-+/g, "-").replace(/^-+/, "").replace(/-+$/, "").slice(0, 63);
121
+ const usable = WINDOWS_RESERVED.has(slug) ? `${slug}-sor` : slug;
122
+ return isValidName(usable) ? usable : null;
123
+ }
124
+ //#endregion
125
+ //#region src/init/walk.ts
126
+ /** Nearest ancestor (inclusive) containing instance.md, or null. */
127
+ function findAncestorProject(startDir) {
128
+ let dir = path.resolve(startDir);
129
+ for (;;) {
130
+ if (existsSync(path.join(dir, "instance.md"))) return dir;
131
+ const parent = path.dirname(dir);
132
+ if (parent === dir) return null;
133
+ dir = parent;
134
+ }
135
+ }
136
+ /**
137
+ * Nearest ancestor (exclusive of startDir) that is a pnpm workspace root
138
+ * whose globs could swallow a nested project. Presence of the file is enough
139
+ * to warn — glob analysis would promise precision the warning doesn't need.
140
+ */
141
+ function findAncestorWorkspace(startDir) {
142
+ let dir = path.dirname(path.resolve(startDir));
143
+ for (;;) {
144
+ const manifest = path.join(dir, "pnpm-workspace.yaml");
145
+ if (existsSync(manifest)) return dir;
146
+ const pkg = path.join(dir, "package.json");
147
+ if (existsSync(pkg)) try {
148
+ if (JSON.parse(readFileSync(pkg, "utf8")).workspaces !== void 0) return dir;
149
+ } catch {}
150
+ const parent = path.dirname(dir);
151
+ if (parent === dir) return null;
152
+ dir = parent;
153
+ }
154
+ }
155
+ //#endregion
156
+ //#region src/init/index.ts
157
+ const STAGE_PREFIX = ".ksor-init-";
158
+ const GRAMMAR = "^[a-z0-9][a-z0-9-]{0,62}$";
159
+ function fail(io, slug, lines, code) {
160
+ io.err(`error: ${slug}\n${lines.join("\n")}\n`);
161
+ return code;
162
+ }
163
+ function refuse(io, slug, lines) {
164
+ return fail(io, slug, lines, exitCodes.refused);
165
+ }
166
+ function refuseExists(io, word) {
167
+ return refuse(io, "exists", [`${word}/ already exists here.`, `pick another name, or remove ${word}/ first if it is disposable.`]);
168
+ }
169
+ function usage$1(io) {
170
+ io.out(`ksor init <name> create a new Knowledge System of Record in ./<name>
171
+ ksor init . scaffold into the current directory (must be empty)
172
+
173
+ Nothing was scaffolded: bare \`ksor init\` never writes — an unattended
174
+ agent must not scaffold into an unknown directory by accident.
175
+ The name must match ${GRAMMAR} (e.g. accounting-sor).\n`);
176
+ return 0;
177
+ }
178
+ /**
179
+ * Spec: stale stage dirs are reported, never deleted — they may hold work.
180
+ *
181
+ * Reported only once this run has written its own tree: a note ahead of a
182
+ * refusal would take the first stderr line, which belongs to the slug (found
183
+ * live: 25 concurrent init pairs, where the loser's refusal was pushed to line
184
+ * two by a note about the winner's live stage — 2026-08-18).
185
+ */
186
+ function noteStaleStages(dir, io) {
187
+ for (const entry of readdirSync(dir).sort()) if (entry.startsWith(STAGE_PREFIX)) io.err(`note: found ${entry} — left by an interrupted init; inspect and remove it\n`);
188
+ }
189
+ /** Undo a half-written tree, children before parents. */
190
+ function rollback(created) {
191
+ for (const target of [...created].reverse()) try {
192
+ rmSync(target, {
193
+ recursive: true,
194
+ force: true
195
+ });
196
+ } catch {}
197
+ }
198
+ /** found live: git may be absent on minimal CI images — warn, never fail. */
199
+ function gitInit(dir, io) {
200
+ if (spawnSync("git", ["rev-parse", "--git-dir"], {
201
+ cwd: dir,
202
+ stdio: "ignore"
203
+ }).status === 0) return;
204
+ const result = spawnSync("git", ["init", "--quiet"], {
205
+ cwd: dir,
206
+ stdio: [
207
+ "ignore",
208
+ "ignore",
209
+ "pipe"
210
+ ],
211
+ encoding: "utf8"
212
+ });
213
+ if (result.error !== void 0) {
214
+ io.err(errnoCode(result.error) === "ENOENT" ? "note: git was not found — initialize the repository yourself when convenient\n" : `note: git init failed: ${result.error.message}\n`);
215
+ return;
216
+ }
217
+ if (result.status !== 0) {
218
+ const detail = (result.stderr ?? "").trim().split("\n")[0] || `git exited ${result.status}`;
219
+ io.err(`note: git init failed: ${detail}\n`);
220
+ }
221
+ }
222
+ function handoff(io, name, targetWasDot) {
223
+ const enter = targetWasDot ? "" : ` cd ${name}\n`;
224
+ io.out(`${name} is ready — your knowledge, your repo, yours outright.\n
225
+ Next (or just tell your coding agent to take it from here):
226
+ ` + enter + " pnpm install\n pnpm dev # the site, live at http://localhost:3000\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");
227
+ }
228
+ function init(args, cwd, io, env) {
229
+ const { version, templatesDir } = env;
230
+ 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);
231
+ const word = args[0] ?? null;
232
+ if (word === null) return usage$1(io);
233
+ if (args.length > 1) {
234
+ const joined = suggestName(args.join("-"));
235
+ return refuse(io, "bad-name", [`a project name is one word — ${args.length} were given: ${args.join(" ")}`, joined !== null ? `try: ksor init ${joined}` : `pick a short name matching ${GRAMMAR}: ksor init <name>.`]);
236
+ }
237
+ const isDot = word === ".";
238
+ const targetDir = isDot ? path.resolve(cwd) : path.resolve(cwd, word);
239
+ const name = isDot ? path.basename(targetDir) : word;
240
+ const problem = nameProblem(name);
241
+ if (problem !== null) {
242
+ const reason = problem === "windows-reserved" ? "Windows reserves it as a device name, so no directory can carry it there" : `it must match ${GRAMMAR} — lowercase letters, digits, hyphens`;
243
+ const suggestion = suggestName(name);
244
+ return refuse(io, "bad-name", isDot ? [`\`ksor init .\` takes the project name from this directory, and "${name}" cannot be one: ${reason}.`, suggestion !== null ? `run \`ksor init ${suggestion}\` from the parent directory, or rename this directory first.` : "rename this directory to lowercase letters, digits and hyphens, then re-run."] : [`"${name}" is not a usable project name: ${reason}.`, suggestion !== null ? `try: ksor init ${suggestion}` : "pick a short lowercase name."]);
245
+ }
246
+ const ancestorProject = findAncestorProject(isDot ? path.dirname(targetDir) : cwd);
247
+ if (ancestorProject !== null) return refuse(io, "nested", [`an existing ksor project owns this path: ${ancestorProject}`, "a corpus lives inside exactly one project — create the new one outside it."]);
248
+ if (isDot) {
249
+ const contents = existsSync(targetDir) ? readdirSync(targetDir).filter((e) => e !== ".git").sort() : [];
250
+ if (contents.length > 0) {
251
+ const listed = contents.slice(0, 5).join(", ") + (contents.length > 5 ? ", …" : "");
252
+ return refuse(io, "blocked", [`the current directory is not empty (${contents.length} entr${contents.length === 1 ? "y" : "ies"}: ${listed}).`, "run `ksor init .` in an empty directory, or `ksor init <name>` to create one."]);
253
+ }
254
+ } else if (existsSync(targetDir)) return refuseExists(io, word);
255
+ const ancestorWorkspace = findAncestorWorkspace(targetDir);
256
+ if (isDot) {
257
+ const created = [];
258
+ try {
259
+ materialize(templatesDir, targetDir, {
260
+ name,
261
+ version
262
+ }, created);
263
+ } catch (error) {
264
+ rollback(created);
265
+ throw error;
266
+ }
267
+ } else {
268
+ const stage = mkdtempSync(path.join(path.dirname(targetDir), STAGE_PREFIX));
269
+ try {
270
+ materialize(templatesDir, stage, {
271
+ name,
272
+ version
273
+ });
274
+ } catch (error) {
275
+ rmSync(stage, {
276
+ recursive: true,
277
+ force: true
278
+ });
279
+ throw error;
280
+ }
281
+ try {
282
+ renameSync(stage, targetDir);
283
+ } catch (error) {
284
+ rmSync(stage, {
285
+ recursive: true,
286
+ force: true
287
+ });
288
+ const code = errnoCode(error);
289
+ if (code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM") return refuseExists(io, word);
290
+ throw error;
291
+ }
292
+ }
293
+ try {
294
+ if (ancestorWorkspace !== null) io.err(`warning: parent pnpm workspace at ${ancestorWorkspace} — its globs may enroll this\nproject's packages into the parent install. Exclude it there if builds misbehave.
295
+ `);
296
+ if (!isDot) {
297
+ chmodSync(targetDir, statSync(path.join(targetDir, "knowledge")).mode & 511);
298
+ noteStaleStages(path.dirname(targetDir), io);
299
+ }
300
+ gitInit(targetDir, io);
301
+ } catch (error) {
302
+ if (!isEnvironmentError(error)) throw error;
303
+ const detail = error instanceof Error ? error.message : String(error);
304
+ io.err(`note: the project was created, but a follow-up step failed: ${detail}\n`);
305
+ }
306
+ handoff(io, name, isDot);
307
+ return 0;
308
+ }
309
+ function runInit(args, cwd, io, env) {
310
+ try {
311
+ return init(args, cwd, io, env);
312
+ } catch (error) {
313
+ if (!isEnvironmentError(error)) throw error;
314
+ return fail(io, "environment", [`the filesystem refused: ${error instanceof Error ? error.message : String(error)}`, "fix the environment and re-run — nothing was kept."], exitCodes.environment);
315
+ }
316
+ }
317
+ //#endregion
318
+ //#region src/init/platform.ts
319
+ /** The scaffold's toolchain requires it (decision 5, and `engines` here). */
320
+ const MINIMUM_NODE_MAJOR = 24;
321
+ /**
322
+ * The remedy for a runtime ksor cannot run on, or null when it can. Pure so
323
+ * the refusal is testable without installing a second Node.
324
+ */
325
+ function unsupportedPlatform(nodeVersion) {
326
+ const version = nodeVersion.replace(/^v/, "");
327
+ const major = Number.parseInt(version, 10);
328
+ if (Number.isNaN(major) || major >= MINIMUM_NODE_MAJOR) return null;
329
+ return `ksor requires Node >= ${MINIMUM_NODE_MAJOR} — you are on v${version}; install a current Node and re-run.`;
330
+ }
331
+ //#endregion
332
+ //#region src/cli.ts
333
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
334
+ const notice = `
335
+ Knowledge System of Record: one governed source of markdown, published as a
336
+ site people read and an MCP surface AI agents query — with citations, and an
337
+ honest refusal when the corpus does not cover the question.
338
+
339
+ Follow along: ${pkg.homepage}\n`;
340
+ const usage = `ksor ${pkg.version} — Knowledge System of Record\n
341
+ Usage: ksor <verb>
342
+
343
+ Verbs (init is implemented; the rest exit 2 until they ship):\n init create a new KSoR project (implemented)
344
+ dev run the human surface locally, watching
345
+ build validate and build both surfaces
346
+ serve expose the MCP agent surface
347
+
348
+ Exit codes: 1 refused · 2 designed but not implemented · 3 environment
349
+ Docs: node_modules/${pkg.name}/docs · ${pkg.homepage}\n`;
350
+ function main(args) {
351
+ if (args.includes("--help") || args.includes("-h")) {
352
+ process.stdout.write(usage);
353
+ return 0;
354
+ }
355
+ if (args.includes("--version")) {
356
+ process.stdout.write(`${pkg.version}\n`);
357
+ return 0;
358
+ }
359
+ const { word, verb } = resolveCommand(args);
360
+ if (verb === "init") {
361
+ const remedy = unsupportedPlatform(process.versions.node);
362
+ if (remedy !== null) {
363
+ process.stderr.write(`error: unsupported-platform\n${remedy}\n`);
364
+ return exitCodes.environment;
365
+ }
366
+ return runInit(args.slice(args.indexOf("init") + 1), process.cwd(), {
367
+ out: (text) => process.stdout.write(text),
368
+ err: (text) => process.stderr.write(text)
369
+ }, {
370
+ version: pkg.version,
371
+ templatesDir: fileURLToPath(new URL("../templates/scaffold", import.meta.url))
372
+ });
373
+ }
374
+ if (word !== null && verb === null) {
375
+ process.stderr.write(`error: unknown-verb\n"${word}" is not a ksor verb. The vocabulary is: ${verbs.join(", ")}.\n`);
376
+ return exitCodes.refused;
377
+ }
378
+ const heading = verb === null ? `ksor ${pkg.version} — the name is reserved; this is not a release.` : `ksor ${verb}: designed but not implemented in ${pkg.version}.`;
379
+ process.stdout.write(`${heading}\n${notice}`);
380
+ return exitCodes.notImplemented;
381
+ }
382
+ process.exitCode = main(process.argv.slice(2));
383
+ //#endregion
384
+ export {};
@@ -0,0 +1,34 @@
1
+ //#region src/index.d.ts
2
+ /**
3
+ * Public entry point of @panaversity/ksor.
4
+ *
5
+ * Nothing here is a released capability: 0.x builds expose only the CLI
6
+ * contract, so that scripts and agents driving `ksor` can rely on stable,
7
+ * documented exit semantics before the verbs are implemented.
8
+ */
9
+ /**
10
+ * Exit-code contract for the `ksor` CLI. 2 must never be read as a crash: it
11
+ * is the honest "this verb is designed but not implemented in this build".
12
+ */
13
+ declare const exitCodes: {
14
+ /** The command was refused: bad input or a guarded operation. */
15
+ readonly refused: 1;
16
+ /** The verb exists in the design but is not implemented in this build. */
17
+ readonly notImplemented: 2;
18
+ /** The environment cannot run ksor (missing runtime requirement). */
19
+ readonly environment: 3;
20
+ };
21
+ type ExitCode = (typeof exitCodes)[keyof typeof exitCodes];
22
+ /** The CLI vocabulary. Deliberately small; see the README. */
23
+ declare const verbs: readonly ["init", "dev", "build", "serve"];
24
+ type Verb = (typeof verbs)[number];
25
+ interface ResolvedCommand {
26
+ /** The first non-flag token, or null when only flags (or nothing) appear. */
27
+ readonly word: string | null;
28
+ /** `word` when it is in the vocabulary, otherwise null. */
29
+ readonly verb: Verb | null;
30
+ }
31
+ /** Resolve the command word from CLI arguments (flags are skipped). */
32
+ declare function resolveCommand(argv: readonly string[]): ResolvedCommand;
33
+ //#endregion
34
+ export { ExitCode, ResolvedCommand, Verb, exitCodes, resolveCommand, verbs };
package/dist/index.mjs ADDED
@@ -0,0 +1,37 @@
1
+ //#region src/index.ts
2
+ /**
3
+ * Public entry point of @panaversity/ksor.
4
+ *
5
+ * Nothing here is a released capability: 0.x builds expose only the CLI
6
+ * contract, so that scripts and agents driving `ksor` can rely on stable,
7
+ * documented exit semantics before the verbs are implemented.
8
+ */
9
+ /**
10
+ * Exit-code contract for the `ksor` CLI. 2 must never be read as a crash: it
11
+ * is the honest "this verb is designed but not implemented in this build".
12
+ */
13
+ const exitCodes = {
14
+ /** The command was refused: bad input or a guarded operation. */
15
+ refused: 1,
16
+ /** The verb exists in the design but is not implemented in this build. */
17
+ notImplemented: 2,
18
+ /** The environment cannot run ksor (missing runtime requirement). */
19
+ environment: 3
20
+ };
21
+ /** The CLI vocabulary. Deliberately small; see the README. */
22
+ const verbs = [
23
+ "init",
24
+ "dev",
25
+ "build",
26
+ "serve"
27
+ ];
28
+ /** Resolve the command word from CLI arguments (flags are skipped). */
29
+ function resolveCommand(argv) {
30
+ const word = argv.find((arg) => !arg.startsWith("-")) ?? null;
31
+ return {
32
+ word,
33
+ verb: word !== null && verbs.includes(word) ? word : null
34
+ };
35
+ }
36
+ //#endregion
37
+ export { exitCodes, resolveCommand, verbs };
package/docs/index.md ADDED
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: ksor documentation
3
+ status: draft
4
+ ---
5
+
6
+ # ksor documentation
7
+
8
+ These docs ship inside the npm package (`node_modules/@panaversity/ksor/docs/`)
9
+ so that coding agents read documentation matching the **installed** version
10
+ instead of their training memory. The corpus grows with each implemented verb.
11
+
12
+ ## What exists in this build
13
+
14
+ - **`ksor init <name>` works.** One command emits a complete governed
15
+ project: the record (`knowledge/`, CommonMark only), a working
16
+ documentation site (`system/site/`, Next.js + Fumadocs — static export,
17
+ hot reload, static search, `llms.txt`), the agent kit (`AGENTS.md`,
18
+ a `CLAUDE.md` pointer, skills under `.agents/skills/` with byte-identical
19
+ `.claude/skills/` copies), adopter CI, and a dependency-free format
20
+ checker (`pnpm check`). `ksor init .` scaffolds into an empty directory
21
+ whose name passes the project-name grammar. Everything emitted belongs to
22
+ the adopter (templates are MIT-0).
23
+ - Inside a scaffolded project, `pnpm install && pnpm dev` serves the record
24
+ at `http://localhost:3000`; `pnpm build` writes a fully static export to
25
+ `system/site/out/`. `KSOR_BASE_PATH=/repo pnpm build` targets sub-path
26
+ hosting.
27
+ - The remaining verbs (`dev`, `build`, `serve`) are designed, not
28
+ implemented: each prints an honest notice and exits `2`.
29
+ - Exit codes are a contract: `1` refused (first stderr line is a stable
30
+ slug such as `error: bad-name`, followed by a remedy), `2` designed but
31
+ not implemented, `3` the environment cannot run ksor
32
+ (`error: unsupported-platform`, `error: broken-install`,
33
+ `error: environment`).
34
+ - The package root exports the CLI contract: `exitCodes`, `verbs`, and
35
+ `resolveCommand`.
36
+
37
+ ## For the agent operating a scaffolded project
38
+
39
+ Read the scaffold's own `AGENTS.md` first — it is the working contract.
40
+ Knowledge lives in `knowledge/` and never inside the site; frontmatter uses
41
+ a closed key set (`title` + `status` required); `pnpm check` explains any
42
+ violation and how to fix it. Sidebar order is the governed `order:`
43
+ frontmatter key — never `meta.json` or `sidebar_position`. The site shell
44
+ at `system/site/` is replaceable behind a four-clause surface contract; a
45
+ Docusaurus conformance shell lives in the ksor repository under
46
+ `workbench/shells/docusaurus/` with its swap recipe.
47
+
48
+ ## Where truth lives
49
+
50
+ - [`docs/status.md`](https://github.com/panaversity/ksor/blob/main/docs/status.md)
51
+ in the repository is authoritative for implemented functionality.
52
+ - The repository [`README`](https://github.com/panaversity/ksor#readme) is the
53
+ concept document, not a capability claim.
package/package.json CHANGED
@@ -1,39 +1,68 @@
1
1
  {
2
2
  "name": "@panaversity/ksor",
3
- "version": "0.0.0",
3
+ "version": "0.0.2",
4
4
  "description": "Knowledge System of Record — the authoritative, governed source of knowledge that humans and AI agents operate from. Name reserved; implementation in progress.",
5
- "license": "Apache-2.0",
6
- "author": "Panaversity",
7
- "homepage": "https://github.com/panaversity/ksor#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/panaversity/ksor.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/panaversity/ksor/issues"
14
- },
15
5
  "keywords": [
6
+ "abstention",
7
+ "citations",
8
+ "documentation",
9
+ "docusaurus",
10
+ "governance",
16
11
  "knowledge-system-of-record",
17
- "system-of-record",
18
12
  "mcp",
19
13
  "model-context-protocol",
20
- "governance",
21
14
  "provenance",
22
- "citations",
23
- "abstention",
24
- "documentation",
25
- "docusaurus"
15
+ "system-of-record"
26
16
  ],
17
+ "homepage": "https://github.com/panaversity/ksor#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/panaversity/ksor/issues"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "author": "Panaversity",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/panaversity/ksor.git",
26
+ "directory": "packages/ksor"
27
+ },
27
28
  "bin": {
28
- "ksor": "bin/ksor.js"
29
+ "ksor": "./dist/cli.mjs"
29
30
  },
30
31
  "files": [
31
- "bin"
32
+ "dist",
33
+ "docs",
34
+ "templates",
35
+ "CHANGELOG.md",
36
+ "NOTICE"
32
37
  ],
33
- "engines": {
34
- "node": ">=20"
38
+ "type": "module",
39
+ "sideEffects": false,
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.mts",
43
+ "import": "./dist/index.mjs",
44
+ "default": "./dist/index.mjs"
45
+ },
46
+ "./package.json": "./package.json"
35
47
  },
36
48
  "publishConfig": {
37
- "access": "public"
49
+ "access": "public",
50
+ "provenance": true
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^24.13.3",
54
+ "playwright": "^1.58.0",
55
+ "publint": "0.3.23",
56
+ "tsdown": "0.22.14",
57
+ "typescript": "7.0.2",
58
+ "vitest": "^4.1.10"
59
+ },
60
+ "engines": {
61
+ "node": ">=24"
62
+ },
63
+ "scripts": {
64
+ "build": "tsdown",
65
+ "typecheck": "tsc -p tsconfig.json",
66
+ "publint": "publint"
38
67
  }
39
- }
68
+ }
@@ -0,0 +1,23 @@
1
+ MIT No Attribution (MIT-0)
2
+
3
+ Copyright 2026 Panaversity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ SOFTWARE.
19
+
20
+ (This licence covers the scaffold templates in this directory — the files
21
+ `ksor init` copies into an adopter's repository. Everything init emits is the
22
+ adopter's, with no attribution or licence-propagation obligations. See
23
+ AGENTS.md decision 10.)
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: add-sources
3
+ description: Turn source material — documents, pages, pasted text, notes — into governed knowledge in knowledge/. Use when the owner shares material to add, says "add this to the knowledge base", or asks how to get existing content in. Not for editing the site.
4
+ metadata:
5
+ version: "1.1.0"
6
+ ---
7
+
8
+ # Add sources
9
+
10
+ Converting material into the record is the everyday work of this project.
11
+ The rules that make it _governed_ rather than merely stored:
12
+
13
+ ## Placement and shape
14
+
15
+ - One document per topic, under `knowledge/`, path = identity: lowercase,
16
+ hyphens, a folder per natural grouping. Plain CommonMark `.md` — if the
17
+ source is rich (tables, images), tables become markdown tables and images
18
+ land _beside the document_ with relative links.
19
+ - A folder's front page is `<folder>/index.md`; reading order is the
20
+ `order:` frontmatter key (ordered documents first, ascending; the rest
21
+ follow alphabetically) — never `meta.json` or `sidebar_position`.
22
+ - Frontmatter: `title` and `status: draft` always; add `owner` (who stands
23
+ behind this content) and `provenance` (a list naming the actual sources —
24
+ file names, systems, people, dates) whenever the owner can tell you.
25
+ Precision matters: "Finance policy manual §4.2, 2025 edition" governs;
26
+ "internal docs" does not.
27
+
28
+ ## Fidelity rules
29
+
30
+ - **Copy load-bearing values exactly** — numbers, thresholds, dates, names.
31
+ Never round, never paraphrase a figure.
32
+ - **Two disagreeing sources stay two statements**, each with its provenance
33
+ — never smooth a conflict into one invented truth; flag it to the owner.
34
+ - **Do not fill gaps from general knowledge.** If the source doesn't cover
35
+ something, the record doesn't either — that boundary is the product.
36
+ - A document replacing an older one: mark the old one `status: superseded`
37
+ with `superseded_by:` pointing at the new — never delete it.
38
+
39
+ ## Finish every batch
40
+
41
+ Run `pnpm check` and fix what it reports (its errors explain themselves),
42
+ then show the owner the rendered result (`pnpm dev`) — the site is the
43
+ review surface: you write, they check.