@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
|
@@ -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
|
+
}
|