@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.
Files changed (108) hide show
  1. package/CHANGELOG.md +965 -0
  2. package/README.md +29 -9
  3. package/dist/checker/check-main.mjs +14049 -0
  4. package/dist/cli.mjs +11528 -5213
  5. package/dist/gateway-api-CF4ED9_g-BQusM_dK.mjs +10895 -0
  6. package/dist/gateway.d.mts +52 -13
  7. package/dist/gateway.mjs +2 -2
  8. package/dist/index.d.mts +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/{src-pl4aOpVs.mjs → src-dqpI-p1a.mjs} +1 -0
  11. package/docs/authorization.md +8 -6
  12. package/docs/deploying.md +36 -25
  13. package/docs/index.md +26 -13
  14. package/docs/ingesting.md +70 -22
  15. package/docs/tool-surface.md +69 -16
  16. package/package.json +4 -3
  17. package/schema/migrations/2.4-2.5__okf-profile.sql +114 -0
  18. package/schema/schema.sql +77 -14
  19. package/templates/scaffold/.agents/skills/add-sources/SKILL.md +63 -18
  20. package/templates/scaffold/.agents/skills/format-checker/SKILL.md +42 -33
  21. package/templates/scaffold/.agents/skills/format-checker/check.mjs +13827 -1314
  22. package/templates/scaffold/.agents/skills/intake-interview/SKILL.md +65 -27
  23. package/templates/scaffold/.agents/skills/make-slides/SKILL.md +7 -5
  24. package/templates/scaffold/.agents/skills/make-summary/SKILL.md +13 -6
  25. package/templates/scaffold/.claude/skills/add-sources/SKILL.md +63 -18
  26. package/templates/scaffold/.claude/skills/format-checker/SKILL.md +42 -33
  27. package/templates/scaffold/.claude/skills/format-checker/check.mjs +13827 -1314
  28. package/templates/scaffold/.claude/skills/intake-interview/SKILL.md +65 -27
  29. package/templates/scaffold/.claude/skills/make-slides/SKILL.md +7 -5
  30. package/templates/scaffold/.claude/skills/make-summary/SKILL.md +13 -6
  31. package/templates/scaffold/.github/workflows/validate.yml +9 -1
  32. package/templates/scaffold/.ksor/governance.yaml +17 -0
  33. package/templates/scaffold/AGENTS.md +293 -119
  34. package/templates/scaffold/Dockerfile +5 -1
  35. package/templates/scaffold/README.md +160 -42
  36. package/templates/scaffold/env.example +37 -6
  37. package/templates/scaffold/gitignore +14 -6
  38. package/templates/scaffold/instance.md +21 -17
  39. package/templates/scaffold/knowledge/governance-ladder.md +11 -2
  40. package/templates/scaffold/knowledge/index.md +9 -0
  41. package/templates/scaffold/knowledge/surfaces/for-agents.md +13 -6
  42. package/templates/scaffold/knowledge/surfaces/for-people.md +7 -6
  43. package/templates/scaffold/knowledge/surfaces/index.md +4 -20
  44. package/templates/scaffold/knowledge/surfaces/overview.md +25 -0
  45. package/templates/scaffold/knowledge/what-is-a-ksor.md +6 -5
  46. package/templates/scaffold/knowledge/what-is-a-ksor.summary.md +4 -0
  47. package/templates/scaffold/package.json +3 -4
  48. package/templates/scaffold/pnpm-lock.yaml +3 -0
  49. package/templates/scaffold/system/gateways/content.ts +13 -0
  50. package/templates/scaffold/system/site/app/(home)/page.tsx +2 -2
  51. package/templates/scaffold/system/site/app/.well-known/mcp/server.json/route.ts +10 -0
  52. package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +134 -91
  53. package/templates/scaffold/system/site/app/global.css +198 -6
  54. package/templates/scaffold/system/site/app/layout.tsx +8 -3
  55. package/templates/scaffold/system/site/app/llms-full.txt/route.ts +13 -7
  56. package/templates/scaffold/system/site/app/llms.txt/route.ts +12 -7
  57. package/templates/scaffold/system/site/app/md/[[...slug]]/route.ts +26 -25
  58. package/templates/scaffold/system/site/components/code-block.tsx +87 -0
  59. package/templates/scaffold/system/site/components/embed.tsx +276 -0
  60. package/templates/scaffold/system/site/components/footer-mark.tsx +3 -2
  61. package/templates/scaffold/system/site/components/governance.tsx +219 -87
  62. package/templates/scaffold/system/site/components/mdx.tsx +10 -2
  63. package/templates/scaffold/system/site/components/record-index.tsx +5 -5
  64. package/templates/scaffold/system/site/components/record-stack.tsx +10 -9
  65. package/templates/scaffold/system/site/components/record-views.tsx +6 -17
  66. package/templates/scaffold/system/site/components/sidebar-status.tsx +19 -18
  67. package/templates/scaffold/system/site/lib/alert-rule.ts +214 -0
  68. package/templates/scaffold/system/site/lib/attachment-rule.ts +6 -1
  69. package/templates/scaffold/system/site/lib/attachments.ts +0 -28
  70. package/templates/scaffold/system/site/lib/audience-rule.ts +15 -21
  71. package/templates/scaffold/system/site/lib/audience.ts +42 -146
  72. package/templates/scaffold/system/site/lib/embed-rule.ts +255 -0
  73. package/templates/scaffold/system/site/lib/governance.ts +339 -225
  74. package/templates/scaffold/system/site/lib/index-routes.ts +125 -0
  75. package/templates/scaffold/system/site/lib/lifecycle-rule.ts +52 -0
  76. package/templates/scaffold/system/site/lib/lock.ts +282 -0
  77. package/templates/scaffold/system/site/lib/order-rule.ts +37 -0
  78. package/templates/scaffold/system/site/lib/record-href.ts +68 -0
  79. package/templates/scaffold/system/site/lib/record-link.tsx +26 -0
  80. package/templates/scaffold/system/site/lib/rules-version.ts +11 -0
  81. package/templates/scaffold/system/site/lib/shared.ts +67 -104
  82. package/templates/scaffold/system/site/lib/sim-rule.ts +49 -0
  83. package/templates/scaffold/system/site/lib/source.ts +256 -186
  84. package/templates/scaffold/system/site/lib/stage-knowledge.ts +638 -467
  85. package/templates/scaffold/system/site/lib/stage-manifest.ts +128 -0
  86. package/templates/scaffold/system/site/package.json +1 -0
  87. package/templates/scaffold/system/site/record/actor.ts +23 -0
  88. package/templates/scaffold/system/site/record/check.ts +571 -0
  89. package/templates/scaffold/system/site/record/citations.ts +312 -0
  90. package/templates/scaffold/system/site/record/frontmatter.ts +134 -0
  91. package/templates/scaffold/system/site/record/git-ledger.ts +171 -0
  92. package/templates/scaffold/system/site/record/hygiene.ts +320 -0
  93. package/templates/scaffold/system/site/record/index-file.ts +150 -0
  94. package/templates/scaffold/system/site/record/index.ts +103 -0
  95. package/templates/scaffold/system/site/record/instance.ts +257 -0
  96. package/templates/scaffold/system/site/record/instant.ts +43 -0
  97. package/templates/scaffold/system/site/record/ledger.ts +694 -0
  98. package/templates/scaffold/system/site/record/load.ts +129 -0
  99. package/templates/scaffold/system/site/record/lock.ts +306 -0
  100. package/templates/scaffold/system/site/record/near-miss.ts +37 -0
  101. package/templates/scaffold/system/site/record/policy.ts +414 -0
  102. package/templates/scaffold/system/site/record/profile.ts +535 -0
  103. package/templates/scaffold/system/site/record/refusal.ts +106 -0
  104. package/templates/scaffold/system/site/record/yaml-file.ts +103 -0
  105. package/templates/scaffold/system/site/source.config.ts +109 -23
  106. package/dist/gateway-api-CmIthmJS-IUA9qS-T.mjs +0 -3225
  107. package/templates/scaffold/system/site/lib/denial-rule.ts +0 -220
  108. package/templates/scaffold/system/site/lib/page-order.ts +0 -93
@@ -0,0 +1,255 @@
1
+ /**
2
+ * An interactive page the document points at, as a CLICK-TO-LOAD frame.
3
+ *
4
+ * A record often wants to show something running — a simulation, a dashboard,
5
+ * a player — at one exact point in the prose. A deck is one per document and
6
+ * lives in an attachment (`<doc>.slides.yaml`); this is the other shape: many
7
+ * per document, each where the sentence before it puts it.
8
+ *
9
+ * The form is an ordinary CommonMark link whose TITLE is the word `embed`:
10
+ *
11
+ * [Play run-until-done](https://example.org/sims/goal-loop "embed")
12
+ *
13
+ * Chosen the way the alert syntax was chosen — for what it does everywhere the
14
+ * record is read that is NOT this site. GitHub renders a link with a tooltip.
15
+ * `/md/` and `llms-full.txt` carry the author's link. A plain editor shows a
16
+ * link. No grammar is added to `knowledge/`, so critical rule 2 holds.
17
+ *
18
+ * The TITLE, rather than a lone link on its own line, because the opt-in has
19
+ * to be something an author WROTE. A rule that reframes any link standing
20
+ * alone would silently pull a third party's page into the record the first
21
+ * time someone put a citation on its own line.
22
+ *
23
+ * REHYPE, not remark, for the reason `alert-rule.ts` records: this record's
24
+ * markdown is serialized from the mdast, so rewriting there would publish this
25
+ * site's React component to the agent surface in place of the author's link.
26
+ *
27
+ * A LEAF: no imports, so the rule can be tested on its own.
28
+ */
29
+
30
+ /** The link title that opts a link in. Anything else is an ordinary link. */
31
+ export const EMBED_TITLE = "embed";
32
+
33
+ /** What the panel names when the page is the record's own. */
34
+ export const SELF_HOST = "this record";
35
+
36
+ export interface EmbedMatch {
37
+ readonly url: string;
38
+ readonly host: string;
39
+ }
40
+
41
+ /**
42
+ * The host a frame would reach, or null when the value is not a url.
43
+ *
44
+ * Surfaced rather than hidden: the placeholder names it, so a reader who
45
+ * clicks has already been told whose page is about to load. That naming is
46
+ * half of why there is no host allowlist here — the other half is that an
47
+ * allowlist written into a scaffold would be one adopter's hosts shipped to
48
+ * every other adopter.
49
+ */
50
+ export function hostOf(value: string): string | null {
51
+ try {
52
+ return new URL(value).hostname;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * The frame this link asks for, or null when it is an ordinary link.
60
+ *
61
+ * https only. A browser blocks an http frame inside a secure page as mixed
62
+ * content, so an http embed publishes a panel that silently never loads —
63
+ * worse than refusing it, because nothing goes red.
64
+ */
65
+ /**
66
+ * The suffix that marks an asset as a page to be SERVED rather than bundled.
67
+ *
68
+ * A sim is an asset of its document, exactly like the figures beside it: many
69
+ * per document, named freely, staged only when a published document links to
70
+ * it. It is deliberately NOT an attachment — an attachment is named after its
71
+ * parent (`<doc>.quiz.yaml`), and seven sims cannot all be `index.sim.html`.
72
+ *
73
+ * CANONICAL in `packages/content/src/lib/sim-rule.ts`, because the record's
74
+ * checker decides which files are admitted at all and this file only decides
75
+ * which links become a frame — one rule, two readers. The definition is
76
+ * repeated here rather than imported to keep this file a LEAF (above), which
77
+ * is not a preference: a relative import here needs `./sim-rule.js` under the
78
+ * package's node16 typecheck and `./sim-rule` under the site's bundler, and
79
+ * no one spelling satisfies both. So the two copies are PINNED to each other
80
+ * in `embed-rule.test.ts` instead — decision 18's shape, on a smaller rule.
81
+ */
82
+ export const SIM_SUFFIX = ".sim.html";
83
+
84
+ /**
85
+ * Where a sim is served, derived from where it sits in the record.
86
+ *
87
+ * The record path is the identity (product principle 3), so two documents
88
+ * may each own a `goal-loop.sim.html` without colliding.
89
+ */
90
+ export function publicSimPath(recordRelative: string): string {
91
+ return recordRelative.slice(0, -SIM_SUFFIX.length).replaceAll("\\", "/") + ".html";
92
+ }
93
+
94
+ export function matchEmbed(url: string, title: string | undefined): EmbedMatch | null {
95
+ if (title !== EMBED_TITLE) return null;
96
+
97
+ // A sim carried IN the record. Same origin, so no third party learns
98
+ // anything, it works offline, and no host can refuse to be framed — which
99
+ // is not hypothetical: every sim this was first built for answers
100
+ // `x-frame-options: SAMEORIGIN`, so a cross-origin frame to them can never
101
+ // render (measured 2026-08-24, all seven).
102
+ if (url.endsWith(SIM_SUFFIX) && !url.includes(":")) {
103
+ return { url, host: SELF_HOST };
104
+ }
105
+
106
+ let parsed: URL;
107
+ try {
108
+ parsed = new URL(url);
109
+ } catch {
110
+ return null;
111
+ }
112
+ if (parsed.protocol !== "https:") return null;
113
+
114
+ return { url, host: parsed.hostname };
115
+ }
116
+
117
+ export interface EmbedCase {
118
+ readonly url: string;
119
+ /** `undefined` is the ordinary link — no title at all. */
120
+ readonly title: string | undefined;
121
+ readonly embeds: boolean;
122
+ }
123
+
124
+ /** The rule, as a table — what embeds, and what deliberately does not. */
125
+ export const EMBED_CASES: readonly EmbedCase[] = [
126
+ { url: "https://example.org/sims/goal-loop", title: EMBED_TITLE, embeds: true },
127
+ // Carried in the record, beside its document.
128
+ { url: "goal-loop.sim.html", title: EMBED_TITLE, embeds: true },
129
+ { url: "sims/goal-loop.sim.html", title: EMBED_TITLE, embeds: true },
130
+ // Still opt-in: an ordinary link to a sim is an ordinary link.
131
+ { url: "goal-loop.sim.html", title: undefined, embeds: false },
132
+ { url: "https://example.org/sims/goal-loop?v=3", title: EMBED_TITLE, embeds: true },
133
+ // No title is the ordinary case, and by far the commonest link in a record.
134
+ { url: "https://example.org/sims/goal-loop", title: undefined, embeds: false },
135
+ // A title an author wrote for a reader is a tooltip, not an instruction.
136
+ { url: "https://example.org/sims/goal-loop", title: "The run-until-done sim", embeds: false },
137
+ // Near misses on the marker, which must stay links.
138
+ { url: "https://example.org/sim", title: "Embed", embeds: false },
139
+ { url: "https://example.org/sim", title: "embedded", embeds: false },
140
+ { url: "https://example.org/sim", title: " embed", embeds: false },
141
+ // No height token: the frame measures the page it holds, so a record never
142
+ // carries a number that a different measure would make wrong.
143
+ { url: "goal-loop.sim.html", title: "embed 660", embeds: false },
144
+ // http would be blocked as mixed content, so it stays a link that works.
145
+ { url: "http://example.org/sim", title: EMBED_TITLE, embeds: false },
146
+ // Neither of these is a url a frame could reach.
147
+ { url: "/sims/goal-loop", title: EMBED_TITLE, embeds: false },
148
+ { url: "mailto:records@example.org", title: EMBED_TITLE, embeds: false },
149
+ ];
150
+
151
+ interface EmbedNode {
152
+ type?: string;
153
+ tagName?: string;
154
+ name?: string;
155
+ value?: string;
156
+ properties?: { href?: unknown; title?: unknown };
157
+ attributes?: readonly { type: string; name: string; value: string }[];
158
+ children?: EmbedNode[];
159
+ }
160
+
161
+ /** The link's own text, which becomes the frame's title and the link out. */
162
+ function labelOf(node: EmbedNode): string {
163
+ return (node.children ?? [])
164
+ .map((child) => (child.type === "text" ? (child.value ?? "") : labelOf(child)))
165
+ .join("")
166
+ .trim();
167
+ }
168
+
169
+ /** The record-relative directory a staged document sits in, or "". */
170
+ /**
171
+ * BOTH roots, because there are two. A record that declares `audiences:` or
172
+ * carries a takedown is read from `.staged-knowledge/`; every other record is
173
+ * read from `knowledge/` directly, which is the level-0 fast path and the
174
+ * common case. Keying on the staged one alone dropped the directory from
175
+ * every url on exactly the records most people have (found live 2026-08-24:
176
+ * `/sims/goal-loop.html` for a sim that lives in `loop-engineering/`).
177
+ */
178
+ const RECORD_ROOTS = [".staged-knowledge/", "knowledge/"] as const;
179
+
180
+ export function recordDirOf(filePath: string | undefined): string {
181
+ if (!filePath) return "";
182
+ const normalized = filePath.replaceAll("\\", "/");
183
+ for (const marker of RECORD_ROOTS) {
184
+ const at = normalized.lastIndexOf(marker);
185
+ if (at === -1) continue;
186
+ const rel = normalized.slice(at + marker.length);
187
+ const cut = rel.lastIndexOf("/");
188
+ return cut === -1 ? "" : rel.slice(0, cut);
189
+ }
190
+ return "";
191
+ }
192
+
193
+ /** The served url for a sim linked from a document in `dir`. */
194
+ export function servedSimUrl(dir: string, target: string): string {
195
+ const joined = dir === "" ? target : `${dir}/${target}`;
196
+ return "/sims/" + publicSimPath(joined);
197
+ }
198
+
199
+ function embedFor(node: EmbedNode, dir: string): EmbedNode | null {
200
+ if (node.type !== "element" || node.tagName !== "p") return null;
201
+
202
+ // ALONE in its paragraph. hast keeps the source's whitespace between inline
203
+ // children, so a link that shares a sentence still has text nodes beside it
204
+ // — and reframing that would swallow the sentence around the link.
205
+ const meaningful = (node.children ?? []).filter(
206
+ (child) => child.type !== "text" || (child.value ?? "").trim() !== "",
207
+ );
208
+ const [link] = meaningful;
209
+ if (meaningful.length !== 1 || !link || link.type !== "element" || link.tagName !== "a") {
210
+ return null;
211
+ }
212
+
213
+ const { href, title } = link.properties ?? {};
214
+ if (typeof href !== "string") return null;
215
+ const match = matchEmbed(href, typeof title === "string" ? title : undefined);
216
+ if (match === null) return null;
217
+
218
+ // A sim is written as a link to the file BESIDE the document, exactly like
219
+ // a figure. The url it is served at is derived here rather than authored,
220
+ // so the record never contains a path into the site's own build output.
221
+ const url = href.endsWith(SIM_SUFFIX) ? servedSimUrl(dir, href) : match.url;
222
+
223
+ return {
224
+ type: "mdxJsxFlowElement",
225
+ name: "Embed",
226
+ attributes: [
227
+ { type: "mdxJsxAttribute", name: "url", value: url },
228
+ { type: "mdxJsxAttribute", name: "host", value: match.host },
229
+ // Carried in the record, or someone else's page. The two say very
230
+ // different things to a reader, so the panel is told which.
231
+ { type: "mdxJsxAttribute", name: "owned", value: String(match.host === SELF_HOST) },
232
+ { type: "mdxJsxAttribute", name: "label", value: labelOf(link) },
233
+ ],
234
+ children: [],
235
+ };
236
+ }
237
+
238
+ function convertEmbeds(node: EmbedNode, dir: string): void {
239
+ const children = node.children;
240
+ if (!children) return;
241
+
242
+ for (let i = 0; i < children.length; i++) {
243
+ const child = children[i];
244
+ if (!child) continue;
245
+ convertEmbeds(child, dir);
246
+ const embed = embedFor(child, dir);
247
+ if (embed) children[i] = embed;
248
+ }
249
+ }
250
+
251
+ export function rehypeEmbeds(): (tree: EmbedNode, file?: { path?: string }) => void {
252
+ return (tree: EmbedNode, file?: { path?: string }): void => {
253
+ convertEmbeds(tree, recordDirOf(file?.path));
254
+ };
255
+ }