@panaversity/ksor 0.0.30 → 0.0.31
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 +67 -0
- package/README.md +24 -0
- package/dist/cli.mjs +7 -3
- package/dist/{gateway-api-CbFkHZiU-HvlJRjRB.mjs → gateway-api-6nC9x54K-BWFTI_6U.mjs} +1 -1
- package/dist/gateway.mjs +1 -1
- package/package.json +1 -1
- package/templates/scaffold/.agents/skills/format-checker/check.mjs +9 -1
- package/templates/scaffold/.agents/skills/make-slides/SKILL.md +160 -0
- package/templates/scaffold/.claude/skills/format-checker/check.mjs +9 -1
- package/templates/scaffold/.claude/skills/make-slides/SKILL.md +160 -0
- package/templates/scaffold/AGENTS.md +45 -5
- package/templates/scaffold/README.md +24 -1
- package/templates/scaffold/knowledge/what-is-a-ksor.slides.yaml +65 -0
- package/templates/scaffold/system/site/app/docs/[[...slug]]/page.tsx +9 -1
- package/templates/scaffold/system/site/app/global.css +45 -0
- package/templates/scaffold/system/site/components/deck-viewer.tsx +195 -0
- package/templates/scaffold/system/site/components/slides.tsx +128 -0
- package/templates/scaffold/system/site/lib/attachment-rule.ts +7 -0
- package/templates/scaffold/system/site/lib/attachments.ts +49 -2
- package/templates/scaffold/system/site/lib/slides-embed.ts +93 -0
- package/templates/scaffold/system/site/lib/slides.ts +123 -0
- package/templates/scaffold/system/site/source.config.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# @panaversity/ksor
|
|
2
2
|
|
|
3
|
+
## 0.0.31
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c69232d: Adversarial coverage for the MCP door (issue #33), first slice: the governance
|
|
8
|
+
leak sweep and cross-replica snapshot behaviour.
|
|
9
|
+
|
|
10
|
+
**A withdrawn document must not appear in any field of any reachable response.**
|
|
11
|
+
The existing takedown test proves each serving arm behaves at the arms someone
|
|
12
|
+
thought of. This one plants an unguessable marker inside the withdrawn document
|
|
13
|
+
— in its body _and its title_ — and asserts the marker appears nowhere in the
|
|
14
|
+
serialized result, across eighteen request shapes: search by body, by marker, by
|
|
15
|
+
title words, at several limits, keyword search, `topOneScore`, read by
|
|
16
|
+
stable_id / slug / qualified path, and outline at every anchor and page. A leak
|
|
17
|
+
into a field the test has never heard of still fails it.
|
|
18
|
+
|
|
19
|
+
It carries a **positive control**, because every other assertion is a
|
|
20
|
+
not-contains and a probe that could never see the marker would pass them all
|
|
21
|
+
while proving nothing: each shape runs before the takedown and the ones that
|
|
22
|
+
testify are required to have found it first.
|
|
23
|
+
|
|
24
|
+
It also covers the subtlest case, which carries no content at all — `topOneScore`
|
|
25
|
+
feeds the abstention gate, so a withdrawn document scoring there would let a
|
|
26
|
+
record claim coverage on the strength of text it refuses to show.
|
|
27
|
+
|
|
28
|
+
**Cross-replica snapshot tokens**, listed in #33 as "documented, untested" and
|
|
29
|
+
since found on a real deployment: two processes with no `KSOR_SNAPSHOT_KEYS`
|
|
30
|
+
produce tokens neither can verify from the other, and the verdict is `invalid`
|
|
31
|
+
rather than `unknown_key` — the key _id_ matches and only the secret differs,
|
|
32
|
+
which is why the failure is invisible until you read it. Also pins rotation
|
|
33
|
+
(outstanding tokens survive while the old key is listed, and die when it is
|
|
34
|
+
dropped) and cross-deployment refusal.
|
|
35
|
+
|
|
36
|
+
- d96b139: Presentations, as governed attachments of a document.
|
|
37
|
+
|
|
38
|
+
A document in `knowledge/` may now carry `<doc>.slides.yaml`. It renders at the
|
|
39
|
+
top of that document's page — before the prose, because a deck is the shape of
|
|
40
|
+
the thing and gives the detail somewhere to land.
|
|
41
|
+
|
|
42
|
+
**Ask your coding agent and it writes the deck.** `make slides for
|
|
43
|
+
knowledge/expenses/approvals.md` runs the new `make-slides` skill, which reads
|
|
44
|
+
the document whole, writes the slides, checks every claim and every number back
|
|
45
|
+
against it, and tells you what it left out because the document did not support
|
|
46
|
+
it — which is usually how you find out a document has a gap. No browser, no
|
|
47
|
+
third-party tool, no step where a person takes over.
|
|
48
|
+
|
|
49
|
+
**The record owns the deck by default.** `deck:` carries the slides themselves
|
|
50
|
+
and the site renders them, which is the only mode where a presentation is
|
|
51
|
+
governed: reviewed in the same pull request as its document, versioned with it,
|
|
52
|
+
withdrawn when it is withdrawn, and incapable of rotting into a dead link. Every
|
|
53
|
+
slide ships in the server-rendered HTML, so a reader without JavaScript, a
|
|
54
|
+
crawler and an agent parsing the page all get the whole deck. Presenter notes
|
|
55
|
+
render outside the slide, so they are not projected in fullscreen.
|
|
56
|
+
|
|
57
|
+
**A deck you keep elsewhere** can be embedded instead — `slides.url:`, with the
|
|
58
|
+
embed url derived for Google Slides, Canva and SlideShare. Its frame is
|
|
59
|
+
click-to-load: nothing is requested from the host until a reader asks for it, so
|
|
60
|
+
a page still makes zero external requests and a reader who only wanted the
|
|
61
|
+
policy never announces that to a slide host. Declaring both modes is refused
|
|
62
|
+
(`ksor-slides-two-sources`) — two presentations with nothing to say which one
|
|
63
|
+
governs is the disagreement a system of record exists to settle. `http` urls are
|
|
64
|
+
refused too, since a browser blocks a mixed-content frame silently.
|
|
65
|
+
|
|
66
|
+
Like every attachment, a deck has no URL, no sidebar row, no `llms.txt` line and
|
|
67
|
+
no id an agent can cite, and it takes its `visibility:` and any takedown from its
|
|
68
|
+
parent.
|
|
69
|
+
|
|
3
70
|
## 0.0.30
|
|
4
71
|
|
|
5
72
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -32,6 +32,30 @@ scaffold is deterministic and offline, and every refusal explains itself.
|
|
|
32
32
|
> and the released version number are authoritative for the exact released
|
|
33
33
|
> functionality.
|
|
34
34
|
|
|
35
|
+
### Study attachments
|
|
36
|
+
|
|
37
|
+
A document may carry companions named after it, and the site renders each on
|
|
38
|
+
that document's page and nowhere else:
|
|
39
|
+
|
|
40
|
+
| File | What it is |
|
|
41
|
+
| ----------------------- | ------------------------------------------------------ |
|
|
42
|
+
| `<doc>.summary.md` | a précis, shown as a second tab beside the document |
|
|
43
|
+
| `<doc>.slides.yaml` | a presentation, at the top of the page |
|
|
44
|
+
| `<doc>.flashcards.yaml` | a recall deck, at the end |
|
|
45
|
+
| `<doc>.quiz.yaml` | a multiple-choice check, at the end |
|
|
46
|
+
|
|
47
|
+
Ask your coding agent — `make slides for knowledge/expenses/approvals.md` —
|
|
48
|
+
and the `make-slides` skill writes the deck from the document, checks every
|
|
49
|
+
claim and number back against it, and reports what it left out because the
|
|
50
|
+
document did not support it.
|
|
51
|
+
|
|
52
|
+
An attachment is **part of its document**: no URL, no sidebar row, no
|
|
53
|
+
`llms.txt` line, and no id an agent can cite. It takes its `visibility:` and
|
|
54
|
+
any takedown from its parent, so restricting the document restricts them all.
|
|
55
|
+
A quiz whose answers are guessable is refused by the build, and because ingest
|
|
56
|
+
creates no node for an attachment, a quiz's answer key can never reach the
|
|
57
|
+
agent surface at all.
|
|
58
|
+
|
|
35
59
|
Full concept, design goals, and project status:
|
|
36
60
|
**<https://github.com/panaversity/ksor>**
|
|
37
61
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as resolveCommand, r as verbs, t as exitCodes } from "./src-pl4aOpVs.mjs";
|
|
3
|
-
import { A as runProbe$1, C as keyRingFromEnv, D as prewarmPool, E as pooledEndpointFor, F as withPgRetry$1, I as withProbeDeadline$1, L as z$1, M as storedTextSearchConfig, N as tlsPosture, O as readHandler, P as visibleTiers$1, S as embedQueryVlit, T as parseInstanceText$1, _ as buildShippedProvider$1, a as MAX_OUTLINE_LIMIT, b as contentPool$1, c as MissingProviderKeyError$1, d as READ_OUTPUT, f as SEARCH_OUTPUT, g as assertSchemaCompatible, h as assertGovernanceServable$1, i as GovernanceGateError$1, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, m as TextSearchConfigMismatch, n as EmbeddingSpaceMismatch$1, p as SchemaVersionError, r as FLOOR, s as McpServer$1, t as ContentStoreError$1, u as READ_ONLY, v as checkEmbeddingSpace$1, w as outlineHandler, x as contentPoolMin, y as composeInstructions } from "./gateway-api-
|
|
3
|
+
import { A as runProbe$1, C as keyRingFromEnv, D as prewarmPool, E as pooledEndpointFor, F as withPgRetry$1, I as withProbeDeadline$1, L as z$1, M as storedTextSearchConfig, N as tlsPosture, O as readHandler, P as visibleTiers$1, S as embedQueryVlit, T as parseInstanceText$1, _ as buildShippedProvider$1, a as MAX_OUTLINE_LIMIT, b as contentPool$1, c as MissingProviderKeyError$1, d as READ_OUTPUT, f as SEARCH_OUTPUT, g as assertSchemaCompatible, h as assertGovernanceServable$1, i as GovernanceGateError$1, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, m as TextSearchConfigMismatch, n as EmbeddingSpaceMismatch$1, p as SchemaVersionError, r as FLOOR, s as McpServer$1, t as ContentStoreError$1, u as READ_ONLY, v as checkEmbeddingSpace$1, w as outlineHandler, x as contentPoolMin, y as composeInstructions } from "./gateway-api-6nC9x54K-BWFTI_6U.mjs";
|
|
4
4
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { InMemoryTransport, LATEST_PROTOCOL_VERSION, createMcpHandler } from "@modelcontextprotocol/server";
|
|
@@ -16,7 +16,7 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
16
16
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
18
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
19
|
-
//#region ../content-gateway/dist/main-
|
|
19
|
+
//#region ../content-gateway/dist/main-DdIuyneg.mjs
|
|
20
20
|
/**
|
|
21
21
|
* The default registration — and the ORIGINAL of the file `ksor init` emits.
|
|
22
22
|
*
|
|
@@ -1583,7 +1583,7 @@ async function withPgRetry(op, options = {}) {
|
|
|
1583
1583
|
throw lastError;
|
|
1584
1584
|
}
|
|
1585
1585
|
//#endregion
|
|
1586
|
-
//#region ../content/dist/commands-
|
|
1586
|
+
//#region ../content/dist/commands-mZxv9bLG.mjs
|
|
1587
1587
|
/**
|
|
1588
1588
|
* EVAL-LOCKED constants, quarried verbatim from the oracle
|
|
1589
1589
|
* (sor-agentfactory @ b554f91, config.py) — changing any of these is a
|
|
@@ -4212,6 +4212,10 @@ const ATTACHMENT_SUFFIXES = [
|
|
|
4212
4212
|
{
|
|
4213
4213
|
suffix: ".quiz.yaml",
|
|
4214
4214
|
kind: "quiz"
|
|
4215
|
+
},
|
|
4216
|
+
{
|
|
4217
|
+
suffix: ".slides.yaml",
|
|
4218
|
+
kind: "slides"
|
|
4215
4219
|
}
|
|
4216
4220
|
];
|
|
4217
4221
|
/**
|
|
@@ -5,7 +5,7 @@ import { z, z as z$1 } from "zod";
|
|
|
5
5
|
import pg from "pg";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
|
-
//#region ../content-gateway/dist/gateway-api-
|
|
8
|
+
//#region ../content-gateway/dist/gateway-api-6nC9x54K.mjs
|
|
9
9
|
/**
|
|
10
10
|
* The record's system prompt: instance.md's body, preserved beneath a framework
|
|
11
11
|
* floor. Lifted out of server.ts so the public gateway surface can re-export
|
package/dist/gateway.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { L as z$1, O as readHandler, a as MAX_OUTLINE_LIMIT, d as READ_OUTPUT, f as SEARCH_OUTPUT, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, o as MAX_SEARCH_K, r as FLOOR, s as McpServer$1, u as READ_ONLY, w as outlineHandler, y as composeInstructions } from "./gateway-api-
|
|
1
|
+
import { L as z$1, O as readHandler, a as MAX_OUTLINE_LIMIT, d as READ_OUTPUT, f as SEARCH_OUTPUT, j as searchHandler, k as recordIsUndescribed, l as OUTLINE_OUTPUT, o as MAX_SEARCH_K, r as FLOOR, s as McpServer$1, u as READ_ONLY, w as outlineHandler, y as composeInstructions } from "./gateway-api-6nC9x54K-BWFTI_6U.mjs";
|
|
2
2
|
export { FLOOR, MAX_OUTLINE_LIMIT, MAX_SEARCH_K, McpServer$1 as McpServer, OUTLINE_OUTPUT, READ_ONLY, READ_OUTPUT, SEARCH_OUTPUT, composeInstructions, outlineHandler, readHandler, recordIsUndescribed, searchHandler, z$1 as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panaversity/ksor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"description": "Knowledge System of Record — compile governed markdown into a static site for people and an MCP server for AI agents, with citations and measured abstention.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abstention",
|
|
@@ -41,7 +41,13 @@ const ASSET_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web
|
|
|
41
41
|
// rules below. This mirrors packages/content/src/lib/attachment-rule.ts, which
|
|
42
42
|
// this dependency-free file cannot import; ATTACHMENT_CASES there is the table
|
|
43
43
|
// both are held to.
|
|
44
|
-
const ATTACHMENT_SUFFIXES = [
|
|
44
|
+
const ATTACHMENT_SUFFIXES = [
|
|
45
|
+
".summary.md",
|
|
46
|
+
".summary.mdx",
|
|
47
|
+
".flashcards.yaml",
|
|
48
|
+
".quiz.yaml",
|
|
49
|
+
".slides.yaml",
|
|
50
|
+
];
|
|
45
51
|
// One character off a real attachment, refused BY NAME: `.yml` reaches the
|
|
46
52
|
// site bundler's `Unknown file type` throw, which names the path and nothing
|
|
47
53
|
// about the rule.
|
|
@@ -51,6 +57,8 @@ const ATTACHMENT_NEAR_MISSES = [
|
|
|
51
57
|
[".summary.markdown", ".summary.md"],
|
|
52
58
|
[".quiz.yml", ".quiz.yaml"],
|
|
53
59
|
[".quiz.json", ".quiz.yaml"],
|
|
60
|
+
[".slides.yml", ".slides.yaml"],
|
|
61
|
+
[".slides.json", ".slides.yaml"],
|
|
54
62
|
];
|
|
55
63
|
|
|
56
64
|
/** The attachment suffix this name carries, or null. A dotfile has no stem. */
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-slides
|
|
3
|
+
description: Generate a presentation from one document and attach it, so it renders on that document's page. Use when the owner says "make slides for X", "turn this into a deck", "I need to present this", asks for a teaching aid or a slideshow, or when onboarding needs a session rather than a page.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "2.0.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Generating a presentation for a document
|
|
9
|
+
|
|
10
|
+
You write the slides. Not an outline for somebody else to build — the actual
|
|
11
|
+
deck, into `<doc>.slides.yaml`, which the site renders on that document's page.
|
|
12
|
+
No browser, no third-party tool, no step where a human takes over.
|
|
13
|
+
|
|
14
|
+
Run it end to end: read the document, write the deck, check every line back
|
|
15
|
+
against the document, verify it builds. **The check is not optional** — it is
|
|
16
|
+
the step that keeps the record's guarantee true.
|
|
17
|
+
|
|
18
|
+
## The one rule everything else serves
|
|
19
|
+
|
|
20
|
+
**A slide may only say what the document says.**
|
|
21
|
+
|
|
22
|
+
The deck is a way of presenting the record, never a second source. A slide
|
|
23
|
+
asserting a threshold the document does not contain is a claim nothing governs
|
|
24
|
+
and no agent can cite — and because the deck is an attachment, the record now
|
|
25
|
+
stands behind it. Every number, date, name and rule is copied from the
|
|
26
|
+
document exactly, units included.
|
|
27
|
+
|
|
28
|
+
If the document does not say something you want on a slide, there are two
|
|
29
|
+
honest options: leave it out, or tell the owner the document is missing it.
|
|
30
|
+
Never a third.
|
|
31
|
+
|
|
32
|
+
## 1 · Read the document whole, first
|
|
33
|
+
|
|
34
|
+
Read `<doc>.md` completely before writing anything. Note as you go:
|
|
35
|
+
|
|
36
|
+
- **the decision it settles** — the reason it exists
|
|
37
|
+
- **the rule, in its own words** — usually one or two sentences
|
|
38
|
+
- **the numbers** — thresholds, deadlines, limits, and their units
|
|
39
|
+
- **the cases** — what happens in each situation it names
|
|
40
|
+
- **the boundary** — what it explicitly does NOT cover
|
|
41
|
+
- **its governance** — `owner`, `effective`, `status` from the frontmatter
|
|
42
|
+
|
|
43
|
+
If the document carries `<doc>.summary.md`, read that too: it is a reviewed
|
|
44
|
+
compression of the same thing, and it tells you what the author thought was
|
|
45
|
+
load-bearing.
|
|
46
|
+
|
|
47
|
+
## 2 · Write the deck
|
|
48
|
+
|
|
49
|
+
Write `<doc>.slides.yaml` beside the document:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
slides:
|
|
53
|
+
title: Expense approvals
|
|
54
|
+
description: The 15-minute version, for a room.
|
|
55
|
+
deck:
|
|
56
|
+
- heading: What this settles
|
|
57
|
+
lead: One sentence, in the document's own words.
|
|
58
|
+
note: What to say here. Spoken, never shown.
|
|
59
|
+
|
|
60
|
+
- heading: The rule
|
|
61
|
+
bullets:
|
|
62
|
+
- Two approvers above the threshold, always
|
|
63
|
+
- The threshold is per invoice, including tax
|
|
64
|
+
note: Pause here. This is the slide people remember wrong.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Per slide:**
|
|
68
|
+
|
|
69
|
+
| Field | Use |
|
|
70
|
+
| --------- | ------------------------------------------------------------ |
|
|
71
|
+
| `heading` | required — a statement, not a label. "The rule", not "Rules" |
|
|
72
|
+
| `lead` | one sentence, for a slide making a single point |
|
|
73
|
+
| `bullets` | three to five. Six is the cap, and six is already too many |
|
|
74
|
+
| `note` | what the presenter SAYS — never a repeat of the slide |
|
|
75
|
+
|
|
76
|
+
**Per deck** — 8 to 14 slides for an ordinary policy document:
|
|
77
|
+
|
|
78
|
+
1. What this settles, and for whom
|
|
79
|
+
2. Why it exists — the decision behind it
|
|
80
|
+
3. The rule itself, stated once
|
|
81
|
+
4. One slide per case, with the real numbers
|
|
82
|
+
5. What people get wrong, and what is true instead
|
|
83
|
+
6. The boundary — what this document does not cover
|
|
84
|
+
7. Where to find it: the route, the owner, the effective date
|
|
85
|
+
|
|
86
|
+
**Habits that decide whether it is any good:**
|
|
87
|
+
|
|
88
|
+
- **A heading is a claim.** "Recency is not authority" teaches; "Authority"
|
|
89
|
+
does not.
|
|
90
|
+
- **A bullet is one thought.** If it needs a comma splice, it is two bullets.
|
|
91
|
+
- **The note carries the argument.** The slide holds the shape; the presenter
|
|
92
|
+
holds the reasoning. A note repeating the bullets is a wasted field.
|
|
93
|
+
- **Do not pad to a target.** Five slides of substance beat twelve with three
|
|
94
|
+
that exist to reach twelve.
|
|
95
|
+
|
|
96
|
+
## 3 · Check every line against the document
|
|
97
|
+
|
|
98
|
+
Go back through slide by slide with the document open. For each:
|
|
99
|
+
|
|
100
|
+
- Is every claim in the document? Name where.
|
|
101
|
+
- Is every number identical, same units, same rounding?
|
|
102
|
+
- Does any slide imply a rule the document does not state?
|
|
103
|
+
- Does the boundary slide match what the document actually excludes?
|
|
104
|
+
|
|
105
|
+
This pass finds real errors, reliably. A transcription slip in a deck outlives
|
|
106
|
+
the session it was made for, because the next presenter trusts it.
|
|
107
|
+
|
|
108
|
+
## 4 · Verify it
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
pnpm check # refuses an orphan, frontmatter, or a malformed deck
|
|
112
|
+
pnpm dev # look at the page — the deck renders at the end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`pnpm build` refuses:
|
|
116
|
+
|
|
117
|
+
- `ksor-slides-empty` — neither `deck:` nor `slides.url:`; nothing to show
|
|
118
|
+
- `ksor-slides-two-sources` — both, so nothing says which one governs
|
|
119
|
+
- `ksor-attachment-orphan` — no `<doc>.md` beside it
|
|
120
|
+
- `ksor-attachment-frontmatter` — an attachment carries none of its own
|
|
121
|
+
|
|
122
|
+
## 5 · Tell the owner what you did
|
|
123
|
+
|
|
124
|
+
Which document, how many slides, and **anything you left out because the
|
|
125
|
+
document did not support it**. That last part is the useful half: it is how an
|
|
126
|
+
owner finds out their document has a gap.
|
|
127
|
+
|
|
128
|
+
## Embedding a deck made elsewhere
|
|
129
|
+
|
|
130
|
+
If the owner already has a deck in Google Slides, Canva or SlideShare, use the
|
|
131
|
+
linked mode — `slides.url:` and no `deck:`:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
slides:
|
|
135
|
+
title: Expense approvals
|
|
136
|
+
url: https://docs.google.com/presentation/d/<id>/edit?usp=sharing
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The embed url is derived for those three providers; for anything else add
|
|
140
|
+
`embed:` explicitly or it renders as a link. `url` must be https — a browser
|
|
141
|
+
blocks a mixed-content frame silently, so an http one publishes a panel that
|
|
142
|
+
never loads.
|
|
143
|
+
|
|
144
|
+
**Prefer the record-owned deck.** A linked deck is not reviewed in a pull
|
|
145
|
+
request, not versioned with its document, not withdrawn when the document is,
|
|
146
|
+
and can rot to a dead link with nothing going red. Use the link when the deck
|
|
147
|
+
already exists and somebody else maintains it — not as the default.
|
|
148
|
+
|
|
149
|
+
## What NOT to do
|
|
150
|
+
|
|
151
|
+
- **Do not put the deck in the document.** No `<iframe>`, no raw HTML.
|
|
152
|
+
`knowledge/` is CommonMark and must read cleanly in any markdown viewer.
|
|
153
|
+
- **Do not write a slide the document cannot support**, even a true one. If it
|
|
154
|
+
is not in the record, the record cannot stand behind it.
|
|
155
|
+
- **Do not make one deck for several documents.** A deck belongs to one
|
|
156
|
+
document, the way a summary does. A deck spanning five policies has no
|
|
157
|
+
document to be governed by and nothing to be withdrawn with.
|
|
158
|
+
- **Do not patch a stale deck.** When the document changes materially,
|
|
159
|
+
regenerate from it. Patching is how a deck and its document drift, and a
|
|
160
|
+
deck that drifts starts winning arguments it should lose.
|
|
@@ -41,7 +41,13 @@ const ASSET_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web
|
|
|
41
41
|
// rules below. This mirrors packages/content/src/lib/attachment-rule.ts, which
|
|
42
42
|
// this dependency-free file cannot import; ATTACHMENT_CASES there is the table
|
|
43
43
|
// both are held to.
|
|
44
|
-
const ATTACHMENT_SUFFIXES = [
|
|
44
|
+
const ATTACHMENT_SUFFIXES = [
|
|
45
|
+
".summary.md",
|
|
46
|
+
".summary.mdx",
|
|
47
|
+
".flashcards.yaml",
|
|
48
|
+
".quiz.yaml",
|
|
49
|
+
".slides.yaml",
|
|
50
|
+
];
|
|
45
51
|
// One character off a real attachment, refused BY NAME: `.yml` reaches the
|
|
46
52
|
// site bundler's `Unknown file type` throw, which names the path and nothing
|
|
47
53
|
// about the rule.
|
|
@@ -51,6 +57,8 @@ const ATTACHMENT_NEAR_MISSES = [
|
|
|
51
57
|
[".summary.markdown", ".summary.md"],
|
|
52
58
|
[".quiz.yml", ".quiz.yaml"],
|
|
53
59
|
[".quiz.json", ".quiz.yaml"],
|
|
60
|
+
[".slides.yml", ".slides.yaml"],
|
|
61
|
+
[".slides.json", ".slides.yaml"],
|
|
54
62
|
];
|
|
55
63
|
|
|
56
64
|
/** The attachment suffix this name carries, or null. A dotfile has no stem. */
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-slides
|
|
3
|
+
description: Generate a presentation from one document and attach it, so it renders on that document's page. Use when the owner says "make slides for X", "turn this into a deck", "I need to present this", asks for a teaching aid or a slideshow, or when onboarding needs a session rather than a page.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "2.0.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Generating a presentation for a document
|
|
9
|
+
|
|
10
|
+
You write the slides. Not an outline for somebody else to build — the actual
|
|
11
|
+
deck, into `<doc>.slides.yaml`, which the site renders on that document's page.
|
|
12
|
+
No browser, no third-party tool, no step where a human takes over.
|
|
13
|
+
|
|
14
|
+
Run it end to end: read the document, write the deck, check every line back
|
|
15
|
+
against the document, verify it builds. **The check is not optional** — it is
|
|
16
|
+
the step that keeps the record's guarantee true.
|
|
17
|
+
|
|
18
|
+
## The one rule everything else serves
|
|
19
|
+
|
|
20
|
+
**A slide may only say what the document says.**
|
|
21
|
+
|
|
22
|
+
The deck is a way of presenting the record, never a second source. A slide
|
|
23
|
+
asserting a threshold the document does not contain is a claim nothing governs
|
|
24
|
+
and no agent can cite — and because the deck is an attachment, the record now
|
|
25
|
+
stands behind it. Every number, date, name and rule is copied from the
|
|
26
|
+
document exactly, units included.
|
|
27
|
+
|
|
28
|
+
If the document does not say something you want on a slide, there are two
|
|
29
|
+
honest options: leave it out, or tell the owner the document is missing it.
|
|
30
|
+
Never a third.
|
|
31
|
+
|
|
32
|
+
## 1 · Read the document whole, first
|
|
33
|
+
|
|
34
|
+
Read `<doc>.md` completely before writing anything. Note as you go:
|
|
35
|
+
|
|
36
|
+
- **the decision it settles** — the reason it exists
|
|
37
|
+
- **the rule, in its own words** — usually one or two sentences
|
|
38
|
+
- **the numbers** — thresholds, deadlines, limits, and their units
|
|
39
|
+
- **the cases** — what happens in each situation it names
|
|
40
|
+
- **the boundary** — what it explicitly does NOT cover
|
|
41
|
+
- **its governance** — `owner`, `effective`, `status` from the frontmatter
|
|
42
|
+
|
|
43
|
+
If the document carries `<doc>.summary.md`, read that too: it is a reviewed
|
|
44
|
+
compression of the same thing, and it tells you what the author thought was
|
|
45
|
+
load-bearing.
|
|
46
|
+
|
|
47
|
+
## 2 · Write the deck
|
|
48
|
+
|
|
49
|
+
Write `<doc>.slides.yaml` beside the document:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
slides:
|
|
53
|
+
title: Expense approvals
|
|
54
|
+
description: The 15-minute version, for a room.
|
|
55
|
+
deck:
|
|
56
|
+
- heading: What this settles
|
|
57
|
+
lead: One sentence, in the document's own words.
|
|
58
|
+
note: What to say here. Spoken, never shown.
|
|
59
|
+
|
|
60
|
+
- heading: The rule
|
|
61
|
+
bullets:
|
|
62
|
+
- Two approvers above the threshold, always
|
|
63
|
+
- The threshold is per invoice, including tax
|
|
64
|
+
note: Pause here. This is the slide people remember wrong.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Per slide:**
|
|
68
|
+
|
|
69
|
+
| Field | Use |
|
|
70
|
+
| --------- | ------------------------------------------------------------ |
|
|
71
|
+
| `heading` | required — a statement, not a label. "The rule", not "Rules" |
|
|
72
|
+
| `lead` | one sentence, for a slide making a single point |
|
|
73
|
+
| `bullets` | three to five. Six is the cap, and six is already too many |
|
|
74
|
+
| `note` | what the presenter SAYS — never a repeat of the slide |
|
|
75
|
+
|
|
76
|
+
**Per deck** — 8 to 14 slides for an ordinary policy document:
|
|
77
|
+
|
|
78
|
+
1. What this settles, and for whom
|
|
79
|
+
2. Why it exists — the decision behind it
|
|
80
|
+
3. The rule itself, stated once
|
|
81
|
+
4. One slide per case, with the real numbers
|
|
82
|
+
5. What people get wrong, and what is true instead
|
|
83
|
+
6. The boundary — what this document does not cover
|
|
84
|
+
7. Where to find it: the route, the owner, the effective date
|
|
85
|
+
|
|
86
|
+
**Habits that decide whether it is any good:**
|
|
87
|
+
|
|
88
|
+
- **A heading is a claim.** "Recency is not authority" teaches; "Authority"
|
|
89
|
+
does not.
|
|
90
|
+
- **A bullet is one thought.** If it needs a comma splice, it is two bullets.
|
|
91
|
+
- **The note carries the argument.** The slide holds the shape; the presenter
|
|
92
|
+
holds the reasoning. A note repeating the bullets is a wasted field.
|
|
93
|
+
- **Do not pad to a target.** Five slides of substance beat twelve with three
|
|
94
|
+
that exist to reach twelve.
|
|
95
|
+
|
|
96
|
+
## 3 · Check every line against the document
|
|
97
|
+
|
|
98
|
+
Go back through slide by slide with the document open. For each:
|
|
99
|
+
|
|
100
|
+
- Is every claim in the document? Name where.
|
|
101
|
+
- Is every number identical, same units, same rounding?
|
|
102
|
+
- Does any slide imply a rule the document does not state?
|
|
103
|
+
- Does the boundary slide match what the document actually excludes?
|
|
104
|
+
|
|
105
|
+
This pass finds real errors, reliably. A transcription slip in a deck outlives
|
|
106
|
+
the session it was made for, because the next presenter trusts it.
|
|
107
|
+
|
|
108
|
+
## 4 · Verify it
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
pnpm check # refuses an orphan, frontmatter, or a malformed deck
|
|
112
|
+
pnpm dev # look at the page — the deck renders at the end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`pnpm build` refuses:
|
|
116
|
+
|
|
117
|
+
- `ksor-slides-empty` — neither `deck:` nor `slides.url:`; nothing to show
|
|
118
|
+
- `ksor-slides-two-sources` — both, so nothing says which one governs
|
|
119
|
+
- `ksor-attachment-orphan` — no `<doc>.md` beside it
|
|
120
|
+
- `ksor-attachment-frontmatter` — an attachment carries none of its own
|
|
121
|
+
|
|
122
|
+
## 5 · Tell the owner what you did
|
|
123
|
+
|
|
124
|
+
Which document, how many slides, and **anything you left out because the
|
|
125
|
+
document did not support it**. That last part is the useful half: it is how an
|
|
126
|
+
owner finds out their document has a gap.
|
|
127
|
+
|
|
128
|
+
## Embedding a deck made elsewhere
|
|
129
|
+
|
|
130
|
+
If the owner already has a deck in Google Slides, Canva or SlideShare, use the
|
|
131
|
+
linked mode — `slides.url:` and no `deck:`:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
slides:
|
|
135
|
+
title: Expense approvals
|
|
136
|
+
url: https://docs.google.com/presentation/d/<id>/edit?usp=sharing
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The embed url is derived for those three providers; for anything else add
|
|
140
|
+
`embed:` explicitly or it renders as a link. `url` must be https — a browser
|
|
141
|
+
blocks a mixed-content frame silently, so an http one publishes a panel that
|
|
142
|
+
never loads.
|
|
143
|
+
|
|
144
|
+
**Prefer the record-owned deck.** A linked deck is not reviewed in a pull
|
|
145
|
+
request, not versioned with its document, not withdrawn when the document is,
|
|
146
|
+
and can rot to a dead link with nothing going red. Use the link when the deck
|
|
147
|
+
already exists and somebody else maintains it — not as the default.
|
|
148
|
+
|
|
149
|
+
## What NOT to do
|
|
150
|
+
|
|
151
|
+
- **Do not put the deck in the document.** No `<iframe>`, no raw HTML.
|
|
152
|
+
`knowledge/` is CommonMark and must read cleanly in any markdown viewer.
|
|
153
|
+
- **Do not write a slide the document cannot support**, even a true one. If it
|
|
154
|
+
is not in the record, the record cannot stand behind it.
|
|
155
|
+
- **Do not make one deck for several documents.** A deck belongs to one
|
|
156
|
+
document, the way a summary does. A deck spanning five policies has no
|
|
157
|
+
document to be governed by and nothing to be withdrawn with.
|
|
158
|
+
- **Do not patch a stale deck.** When the document changes materially,
|
|
159
|
+
regenerate from it. Patching is how a deck and its document drift, and a
|
|
160
|
+
deck that drifts starts winning arguments it should lose.
|
|
@@ -506,12 +506,15 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
506
506
|
refused.
|
|
507
507
|
- Images and assets live in `knowledge/` beside the document that uses them,
|
|
508
508
|
referenced by relative links. A relative link must never leave `knowledge/`.
|
|
509
|
-
- **Study attachments.** A document may carry
|
|
509
|
+
- **Study attachments.** A document may carry four optional companions named
|
|
510
510
|
after it, in the same folder: `<doc>.summary.md` (a short précis),
|
|
511
|
-
`<doc>.
|
|
512
|
-
multiple-choice check). The summary appears as a
|
|
513
|
-
document's own words; the
|
|
514
|
-
|
|
511
|
+
`<doc>.slides.yaml` (a presentation), `<doc>.flashcards.yaml` (a recall deck)
|
|
512
|
+
and `<doc>.quiz.yaml` (a multiple-choice check). The summary appears as a
|
|
513
|
+
second tab beside the document's own words; the presentation appears at the
|
|
514
|
+
TOP of the page, before the document, because a deck is the shape of the
|
|
515
|
+
thing and gives the detail somewhere to land; the deck and the quiz appear at
|
|
516
|
+
the END, because those are used after reading. None of them appears anywhere
|
|
517
|
+
else in the site.
|
|
515
518
|
|
|
516
519
|
An attachment is **part of its document**, not a document. It has no URL of
|
|
517
520
|
its own, no sidebar row, no line in `llms.txt`, and no identity an agent can
|
|
@@ -595,6 +598,41 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
595
598
|
predecessor, where the same mistakes shipped and were found by readers — one
|
|
596
599
|
quiz had every correct answer in the same position across 451 questions.
|
|
597
600
|
|
|
601
|
+
A **presentation** is slides the record owns. Ask your coding agent for one
|
|
602
|
+
rather than writing it by hand — `make slides for knowledge/<path>.md` runs
|
|
603
|
+
the `make-slides` skill, which reads the document, writes the deck, checks
|
|
604
|
+
every claim back against it, and tells you what it left out:
|
|
605
|
+
|
|
606
|
+
```yaml
|
|
607
|
+
slides:
|
|
608
|
+
title: Expense approvals
|
|
609
|
+
description: The 15-minute version, for a room.
|
|
610
|
+
deck:
|
|
611
|
+
- heading: When two copies disagree, one wins
|
|
612
|
+
bullets:
|
|
613
|
+
- Two approvers above the threshold, always
|
|
614
|
+
- The threshold is per invoice, including tax
|
|
615
|
+
note: Spoken, never shown. Pause here — people remember this one wrong.
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
`heading` is required and should be a claim rather than a label; `lead` is
|
|
619
|
+
one sentence for a single-point slide; `bullets` caps at six because a slide
|
|
620
|
+
someone reads aloud is a slide nobody listens to; `note` is what the
|
|
621
|
+
presenter SAYS and never appears on the slide, including in fullscreen.
|
|
622
|
+
|
|
623
|
+
The slides render in the page — no third party, no request to anyone, and
|
|
624
|
+
every slide is in the shipped HTML, so a reader without JavaScript still gets
|
|
625
|
+
the whole deck. Because they live in the record they are reviewed in the same
|
|
626
|
+
pull request as the document, versioned with it, and withdrawn with it.
|
|
627
|
+
|
|
628
|
+
**A deck you keep elsewhere** can be pointed at instead — `slides.url:` and
|
|
629
|
+
no `deck:`. The embed url is derived for Google Slides, Canva and SlideShare;
|
|
630
|
+
anything else needs an explicit `embed:` or renders as a link. The url must
|
|
631
|
+
be `https` (a browser blocks a mixed-content frame silently), and the frame
|
|
632
|
+
loads on CLICK, so nothing is requested from the host until a reader asks.
|
|
633
|
+
Declaring both `deck:` and `slides.url:` is refused — two presentations with
|
|
634
|
+
nothing to say which one governs.
|
|
635
|
+
|
|
598
636
|
- Copy load-bearing values (numbers, thresholds, dates) exactly from their
|
|
599
637
|
source, and name the source in `provenance`.
|
|
600
638
|
|
|
@@ -617,6 +655,8 @@ CI — and a first deploy without it serves an empty record. Full walkthrough:
|
|
|
617
655
|
write `instance.md` together.
|
|
618
656
|
- `.agents/skills/add-sources/` — turn source material (documents, pages,
|
|
619
657
|
notes) into governed knowledge.
|
|
658
|
+
- `.agents/skills/make-slides/` — generate a presentation from one document
|
|
659
|
+
and attach it, so it renders on that document's page.
|
|
620
660
|
- `.agents/skills/format-checker/` — the rules above, as a program;
|
|
621
661
|
`pnpm check` runs it and its errors explain how to fix themselves.
|
|
622
662
|
|
|
@@ -23,6 +23,29 @@ versions that bundle corepack. The first `pnpm install` also fetches the
|
|
|
23
23
|
`ksor` tool (pinned in `package.json`) and writes it into your lockfile —
|
|
24
24
|
commit the updated lockfile.
|
|
25
25
|
|
|
26
|
+
### Presenting a document
|
|
27
|
+
|
|
28
|
+
Ask your coding agent for slides and it writes them, from the document, into
|
|
29
|
+
the record:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
make slides for knowledge/expenses/approvals.md
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The `make-slides` skill reads the document whole, writes the deck into
|
|
36
|
+
`knowledge/expenses/approvals.slides.yaml`, checks every claim and every
|
|
37
|
+
number back against the document, and tells you what it left out because the
|
|
38
|
+
document did not support it — which is usually how you find out a document has
|
|
39
|
+
a gap. The deck then renders at the top of that document's page: click through
|
|
40
|
+
it inline, or **Present** for fullscreen. Presenter notes stay off the screen.
|
|
41
|
+
|
|
42
|
+
The slides live in the record, so they are reviewed in the same pull request
|
|
43
|
+
as the document, versioned with it, and withdrawn when it is withdrawn. There
|
|
44
|
+
is no third party and no link to rot. If you already keep a deck in Google
|
|
45
|
+
Slides, Canva or SlideShare you can point at it instead — `slides.url:` rather
|
|
46
|
+
than `deck:` — and the page will offer it as a link with a frame the reader
|
|
47
|
+
loads on click, so nothing is requested from the host until somebody asks.
|
|
48
|
+
|
|
26
49
|
### Serving to agents
|
|
27
50
|
|
|
28
51
|
The record's other surface is an MCP server for AI agents — the same
|
|
@@ -128,7 +151,7 @@ different coding agent's way of finding the same working contract.
|
|
|
128
151
|
| `instance.md` | what this record is authoritative for; its `name:` is the identity every surface publishes (read at server/build start — restart `pnpm dev` after renaming). This prose IS the agent surface's system prompt — `ksor serve` wires it into the MCP server's instructions. |
|
|
129
152
|
| `AGENTS.md` | the working contract every coding agent reads first — the rules for writing knowledge here. |
|
|
130
153
|
| `CLAUDE.md` | one line, pointing at `AGENTS.md`. Claude Code looks for this filename, not that one. |
|
|
131
|
-
| `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `format-checker` (the rules, as a program). |
|
|
154
|
+
| `.agents/skills/` | the agent kit: `intake-interview` (define the record with you), `add-sources` (turn source material into governed documents), `make-slides` (generate a presentation from a document and attach it), `format-checker` (the rules, as a program). |
|
|
132
155
|
| `.claude/skills/` | byte-identical copies of the kit — Claude Code discovers skills only here. The checker enforces the mirror, so the two cannot drift. |
|
|
133
156
|
| `.gemini/settings.json` | points Gemini CLI at `AGENTS.md`; Gemini does not read that filename on its own. |
|
|
134
157
|
| `.github/workflows/validate.yml` | your CI: runs the same checker on every pull request and push to main. |
|