@panaversity/ksor 0.0.39 → 0.0.41
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 +965 -0
- package/README.md +29 -9
- package/dist/checker/check-main.mjs +14049 -0
- package/dist/cli.mjs +11528 -5213
- package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
- package/dist/gateway.d.mts +52 -13
- package/dist/gateway.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
- package/docs/authorization.md +8 -6
- package/docs/deploying.md +36 -25
- package/docs/index.md +26 -13
- package/docs/ingesting.md +70 -22
- package/docs/tool-surface.md +69 -16
- package/package.json +4 -3
- package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
- package/schema/schema.sql +77 -14
- package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
- package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
- package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
- package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
- package/templates/scaffold/.github/workflows/validate.yml +9 -1
- package/templates/scaffold/.ksor/governance.yaml +17 -0
- package/templates/scaffold/AGENTS.md +293 -119
- package/templates/scaffold/Dockerfile +5 -1
- package/templates/scaffold/README.md +160 -42
- package/templates/scaffold/env.example +37 -6
- package/templates/scaffold/gitignore +14 -6
- package/templates/scaffold/instance.md +21 -17
- package/templates/scaffold/knowledge/governance-ladder.md +11 -2
- package/templates/scaffold/knowledge/index.md +9 -0
- package/templates/scaffold/knowledge/surfaces/for-agents.md +13 -6
- package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
- package/templates/scaffold/knowledge/surfaces/index.md +4 -20
- package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
- package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
- package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
- package/templates/scaffold/package.json +3 -4
- package/templates/scaffold/pnpm-lock.yaml +3 -0
- package/templates/scaffold/system/gateways/content.ts +13 -0
- package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
- package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +134 -91
- package/templates/scaffold/system/site/app/global.css +198 -6
- package/templates/scaffold/system/site/app/layout.tsx +8 -3
- package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
- package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
- package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
- package/templates/scaffold/system/site/components/code-block.tsx +87 -0
- package/templates/scaffold/system/site/components/embed.tsx +276 -0
- package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
- package/templates/scaffold/system/site/components/governance.tsx +219 -87
- package/templates/scaffold/system/site/components/mdx.tsx +10 -2
- package/templates/scaffold/system/site/components/record-index.tsx +5 -5
- package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
- package/templates/scaffold/system/site/components/record-views.tsx +6 -17
- package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
- package/templates/scaffold/system/site/lib/alert-rule.ts +214 -0
- package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
- package/templates/scaffold/system/site/lib/attachments.ts +0 -28
- package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
- package/templates/scaffold/system/site/lib/audience.ts +42 -146
- package/templates/scaffold/system/site/lib/embed-rule.ts +255 -0
- package/templates/scaffold/system/site/lib/governance.ts +339 -225
- package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
- package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
- package/templates/scaffold/system/site/lib/lock.ts +282 -0
- package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
- package/templates/scaffold/system/site/lib/record-href.ts +68 -0
- package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
- package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
- package/templates/scaffold/system/site/lib/shared.ts +67 -104
- package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
- package/templates/scaffold/system/site/lib/source.ts +256 -186
- package/templates/scaffold/system/site/lib/stage-knowledge.ts +638 -467
- package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
- package/templates/scaffold/system/site/package.json +1 -0
- package/templates/scaffold/system/site/record/actor.ts +23 -0
- package/templates/scaffold/system/site/record/check.ts +571 -0
- package/templates/scaffold/system/site/record/citations.ts +312 -0
- package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
- package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
- package/templates/scaffold/system/site/record/hygiene.ts +320 -0
- package/templates/scaffold/system/site/record/index-file.ts +150 -0
- package/templates/scaffold/system/site/record/index.ts +103 -0
- package/templates/scaffold/system/site/record/instance.ts +257 -0
- package/templates/scaffold/system/site/record/instant.ts +43 -0
- package/templates/scaffold/system/site/record/ledger.ts +694 -0
- package/templates/scaffold/system/site/record/load.ts +129 -0
- package/templates/scaffold/system/site/record/lock.ts +306 -0
- package/templates/scaffold/system/site/record/near-miss.ts +37 -0
- package/templates/scaffold/system/site/record/policy.ts +414 -0
- package/templates/scaffold/system/site/record/profile.ts +535 -0
- package/templates/scaffold/system/site/record/refusal.ts +106 -0
- package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
- package/templates/scaffold/system/site/source.config.ts +109 -23
- package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
- package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
- package/templates/scaffold/system/site/lib/page-order.ts +0 -93
|
@@ -2,12 +2,13 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { governanceVisible } from "./governance";
|
|
5
|
+
import { splitFrontmatter } from "../record/frontmatter";
|
|
5
6
|
|
|
6
|
-
// The record's one identity source: instance.md
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// The record's one identity source: instance.md — the same file every other
|
|
8
|
+
// surface reads, so renaming the instance renames every surface at the next
|
|
9
|
+
// build, and no shell carries a baked-in copy (found live 2026-08-18: a
|
|
10
|
+
// stamped constant survived a restore-from-templates as the literal
|
|
11
|
+
// placeholder name, with every gate green).
|
|
11
12
|
function findInstance(start: string): string {
|
|
12
13
|
let dir = start;
|
|
13
14
|
for (let i = 0; i < 5; i += 1) {
|
|
@@ -22,24 +23,42 @@ function findInstance(start: string): string {
|
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/** The project root: the directory holding instance.md. */
|
|
27
|
+
export const projectRoot: string = path.dirname(findInstance(process.cwd()));
|
|
28
|
+
|
|
25
29
|
/**
|
|
26
|
-
* instance.md's frontmatter
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* instance.md's frontmatter, read with the record's own parser (decision 26:
|
|
31
|
+
* one YAML reader, never a line scanner — the scanner this replaced could not
|
|
32
|
+
* read a nested block and failed silently on it). Only the frontmatter: the
|
|
33
|
+
* body is the MCP server's instructions in full (record spec §3), and prose
|
|
34
|
+
* that looks like a key must never become configuration (review finding,
|
|
35
|
+
* 2026-08-18).
|
|
30
36
|
*/
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
function readInstance(): Readonly<Record<string, unknown>> {
|
|
38
|
+
const split = splitFrontmatter(
|
|
39
|
+
readFileSync(path.join(projectRoot, "instance.md"), "utf8"),
|
|
40
|
+
"instance.md",
|
|
41
|
+
);
|
|
42
|
+
if (!split.ok) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`${split.refusal.slug}: instance.md — ${split.refusal.why}\n fix: ${split.refusal.fix}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return split.frontmatter ?? {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const instanceData: Readonly<Record<string, unknown>> = readInstance();
|
|
51
|
+
|
|
52
|
+
function scalar(key: string): string | null {
|
|
53
|
+
const value = instanceData[key];
|
|
54
|
+
if (typeof value !== "string") return null;
|
|
55
|
+
const trimmed = value.trim();
|
|
56
|
+
return trimmed === "" ? null : trimmed;
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
function readInstanceName(): string {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const name = unquoted?.[2] ?? raw;
|
|
42
|
-
if (name === "") {
|
|
60
|
+
const name = scalar("name");
|
|
61
|
+
if (name === null) {
|
|
43
62
|
throw new Error("instance.md carries no name: — it is the project's identity; run pnpm check.");
|
|
44
63
|
}
|
|
45
64
|
return name;
|
|
@@ -48,89 +67,33 @@ function readInstanceName(): string {
|
|
|
48
67
|
export const appName: string = readInstanceName();
|
|
49
68
|
|
|
50
69
|
/**
|
|
51
|
-
* The record's DISPLAY TITLE: instance.md's
|
|
52
|
-
*
|
|
53
|
-
* the human name every page leads
|
|
54
|
-
*
|
|
70
|
+
* The record's DISPLAY TITLE: instance.md's `title:` (record spec §3, format
|
|
71
|
+
* 2 — what used to be the body's H1). The slug in `name:` is the machine
|
|
72
|
+
* identity (llms.txt, citations); the title is the human name every page leads
|
|
73
|
+
* with. Falls back to the name so a page always has one.
|
|
55
74
|
*/
|
|
56
|
-
|
|
57
|
-
const text = readFileSync(findInstance(process.cwd()), "utf8");
|
|
58
|
-
const body = text.replace(/^\uFEFF?---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, "");
|
|
59
|
-
return /^#[ \t]+(.+)$/m.exec(body)?.[1]?.trim() ?? appName;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const appTitle: string = readInstanceTitle();
|
|
75
|
+
export const appTitle: string = scalar("title") ?? appName;
|
|
63
76
|
|
|
64
77
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
export const showGovernance: boolean = governanceVisible(instanceFrontmatter());
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The record's own statement of what it is authoritative for: the first
|
|
73
|
-
* paragraph of instance.md's body, below the display title.
|
|
74
|
-
*
|
|
75
|
-
* The home page used to carry a line of ksor's marketing copy instead — the
|
|
76
|
-
* framework's voice above somebody else's knowledge, which the project's own
|
|
77
|
-
* critical rule 1 forbids. This is the record speaking for itself, and it is
|
|
78
|
-
* the same prose `ksor serve` hands the MCP server as its instructions, so the
|
|
79
|
-
* two surfaces open with one sentence.
|
|
78
|
+
* The record's own one-sentence statement of what it is authoritative for:
|
|
79
|
+
* instance.md's `description:`. It seeds the home page, `llms.txt` and
|
|
80
|
+
* `server.json`, and it is the record speaking for itself — the framework's
|
|
81
|
+
* marketing copy used to sit where it goes (research/site-design.md F7).
|
|
80
82
|
*
|
|
81
|
-
* Null when the
|
|
82
|
-
* than inventing a purpose.
|
|
83
|
+
* Null when the owner has not written one; the page then renders nothing
|
|
84
|
+
* rather than inventing a purpose.
|
|
83
85
|
*/
|
|
84
|
-
|
|
85
|
-
const text = readFileSync(findInstance(process.cwd()), "utf8");
|
|
86
|
-
const body = text.replace(/^?---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, "");
|
|
87
|
-
const afterTitle = body.replace(/^#[ \t]+.*$/m, "");
|
|
88
|
-
const paragraph = afterTitle
|
|
89
|
-
.split(/\n\s*\n/)
|
|
90
|
-
.map((block) => block.trim())
|
|
91
|
-
.find((block) => block !== "");
|
|
92
|
-
if (paragraph === undefined) return null;
|
|
93
|
-
// One paragraph, as one line: the body wraps at 80 columns for the file's
|
|
94
|
-
// sake, and those newlines are not sentence breaks.
|
|
95
|
-
return paragraph.replaceAll(/\s*\n\s*/g, " ").trim();
|
|
96
|
-
}
|
|
86
|
+
export const appDescription: string | null = scalar("description");
|
|
97
87
|
|
|
98
|
-
|
|
88
|
+
/** The description, under the name the home page's cover takes it by. */
|
|
89
|
+
export const appPurpose: string | null = appDescription;
|
|
99
90
|
|
|
100
91
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* It comes from the record's own prose (instance.md's first real paragraph,
|
|
105
|
-
* which the intake interview writes) because the alternative is what shipped
|
|
106
|
-
* before: one hard-coded sentence, byte-identical in every ksor record ever
|
|
107
|
-
* scaffolded, telling a discovering agent nothing that distinguishes this record
|
|
108
|
-
* from any other. "Discoverability determines whether agents find you at all" is
|
|
109
|
-
* a product principle, and a description that cannot discriminate is not
|
|
110
|
-
* discoverability (found live 2026-08-21).
|
|
111
|
-
*
|
|
112
|
-
* An UNDESCRIBED record says so rather than borrowing a confident sentence it
|
|
113
|
-
* has not earned — the same answer the MCP door already gives an agent that
|
|
114
|
-
* connects, so the two surfaces do not disagree about whether this record knows
|
|
115
|
-
* what it is. The marker is the template's own unfilled placeholder, matched on
|
|
116
|
-
* the WHOLE body: a scaffold's first paragraphs are authoring guidance, and
|
|
117
|
-
* reading one of those as the record's scope is worse than admitting there is
|
|
118
|
-
* none.
|
|
92
|
+
* Whether the pages show the governance each document declares
|
|
93
|
+
* (`site.governance` in instance.md, default on). Read once at build/server
|
|
94
|
+
* start, like the identity above — restart `pnpm dev` after changing it.
|
|
119
95
|
*/
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
function readInstanceScope(): string | null {
|
|
123
|
-
const text = readFileSync(findInstance(process.cwd()), "utf8");
|
|
124
|
-
const body = text.replace(/^\uFEFF?---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, "");
|
|
125
|
-
if (body.includes(TEMPLATE_MARKER)) return null;
|
|
126
|
-
const afterHeading = body.replace(/^[\s\S]*?^#[ \t]+.+$/m, "");
|
|
127
|
-
for (const para of afterHeading.split(/\n[ \t]*\n/)) {
|
|
128
|
-
const one = para.trim().replace(/\s+/g, " ");
|
|
129
|
-
if (one === "" || one.startsWith("#") || one.startsWith("-") || one.startsWith(">")) continue;
|
|
130
|
-
return sentence(one);
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
96
|
+
export const showGovernance: boolean = governanceVisible(instanceData);
|
|
134
97
|
|
|
135
98
|
/**
|
|
136
99
|
* The MCP registry schema caps `ServerDetail.description` at **100 characters**
|
|
@@ -146,9 +109,10 @@ function readInstanceScope(): string | null {
|
|
|
146
109
|
*/
|
|
147
110
|
const DESCRIPTION_MAX = 100;
|
|
148
111
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
112
|
+
/** One paragraph as one line: a wrapped description is not several sentences. */
|
|
113
|
+
function sentence(text: string): string {
|
|
114
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
115
|
+
return /^(.+?[.!?])(\s|$)/.exec(one)?.[1] ?? one;
|
|
152
116
|
}
|
|
153
117
|
|
|
154
118
|
/** Fit `text` inside `max`, breaking on a word rather than mid-word. */
|
|
@@ -156,11 +120,11 @@ function fit(text: string, max: number): string {
|
|
|
156
120
|
if (text.length <= max) return text;
|
|
157
121
|
const cut = text.slice(0, max - 1);
|
|
158
122
|
const at = cut.lastIndexOf(" ");
|
|
159
|
-
return `${(at > max / 2 ? cut.slice(0, at) : cut).replace(/[,;:.\s]+$/, "")}
|
|
123
|
+
return `${(at > max / 2 ? cut.slice(0, at) : cut).replace(/[,;:.\s]+$/, "")}…`;
|
|
160
124
|
}
|
|
161
125
|
|
|
162
|
-
/** null until the owner has written one — never a guess. */
|
|
163
|
-
export const appScope: string | null =
|
|
126
|
+
/** The first sentence of the description, or null until the owner has written one — never a guess. */
|
|
127
|
+
export const appScope: string | null = appDescription === null ? null : sentence(appDescription);
|
|
164
128
|
|
|
165
129
|
/**
|
|
166
130
|
* The one-line description every discovery surface publishes. Built here so the
|
|
@@ -181,9 +145,7 @@ export function recordDescription(): string {
|
|
|
181
145
|
* agent would try it and conclude the record is down rather than unpublished.
|
|
182
146
|
*/
|
|
183
147
|
export function mcpEndpoint(): string | null {
|
|
184
|
-
|
|
185
|
-
const value = declared.trim().replace(/^["']|["']$/g, "");
|
|
186
|
-
return value === "" ? null : value;
|
|
148
|
+
return scalar("mcp_url");
|
|
187
149
|
}
|
|
188
150
|
|
|
189
151
|
/**
|
|
@@ -217,7 +179,8 @@ export function mcpNamespace(): string {
|
|
|
217
179
|
* instance.md and falls back to a first-release default.
|
|
218
180
|
*/
|
|
219
181
|
export function recordVersion(): string {
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
182
|
+
const value = instanceData["version"];
|
|
183
|
+
const text =
|
|
184
|
+
typeof value === "number" ? String(value) : typeof value === "string" ? value.trim() : "";
|
|
185
|
+
return /^\d+\.\d+\.\d+/.test(text) ? text : "0.1.0";
|
|
223
186
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What makes a file in the record a SIM — an interactive page the record
|
|
3
|
+
* CARRIES, served rather than bundled.
|
|
4
|
+
*
|
|
5
|
+
* A sim is an ASSET of its document, exactly like the figures beside it: many
|
|
6
|
+
* per document, named freely, linked from the prose the way a figure is, and
|
|
7
|
+
* staged only when a published document references it. It is deliberately NOT
|
|
8
|
+
* a study attachment (decision 24) — an attachment is named after its parent
|
|
9
|
+
* (`<doc>.quiz.yaml`), and seven sims cannot all be `index.sim.html`. So this
|
|
10
|
+
* rule is the SUFFIX and nothing else: there is no parent name to derive, and
|
|
11
|
+
* governance is inherited by POSITION — through the concept whose link is the
|
|
12
|
+
* only way a sim ever reaches the stage.
|
|
13
|
+
*
|
|
14
|
+
* The suffix is what separates a page the record MEANS to serve from an
|
|
15
|
+
* `.html` that fell into `knowledge/`. Both are bytes with the same extension,
|
|
16
|
+
* so without a marker the record would either serve every stray export or
|
|
17
|
+
* serve none of the sims — which is why the checker admits this shape by name
|
|
18
|
+
* and refuses the rest.
|
|
19
|
+
*
|
|
20
|
+
* A LEAF: no imports, so the record's checker and the site's embed rule can
|
|
21
|
+
* each take it without taking anything else with them. Duplicated by
|
|
22
|
+
* construction — the site cannot import the kernel (decision 18) — and the
|
|
23
|
+
* copy is asserted rather than trusted.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** The suffix that marks an asset as a page to be SERVED rather than bundled. */
|
|
27
|
+
export const SIM_SUFFIX = ".sim.html";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* True when this file name is a sim.
|
|
31
|
+
*
|
|
32
|
+
* Matched on the whole base name: a file called exactly `.sim.html` is a
|
|
33
|
+
* dotfile with no stem, so it names no page and is not one — the same
|
|
34
|
+
* boundary `attachmentKindOf` draws, and it keeps a stray dotfile falling
|
|
35
|
+
* through to the honest "unexpected file type" refusal.
|
|
36
|
+
*/
|
|
37
|
+
export function isSim(baseName: string): boolean {
|
|
38
|
+
return baseName.length > SIM_SUFFIX.length && baseName.endsWith(SIM_SUFFIX);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Where a sim is served, derived from where it sits in the record.
|
|
43
|
+
*
|
|
44
|
+
* The record path is the identity (product principle 3), so two documents may
|
|
45
|
+
* each own a `goal-loop.sim.html` without colliding.
|
|
46
|
+
*/
|
|
47
|
+
export function publicSimPath(recordRelative: string): string {
|
|
48
|
+
return recordRelative.slice(0, -SIM_SUFFIX.length).replaceAll("\\", "/") + ".html";
|
|
49
|
+
}
|