@kontourai/corpus 0.0.0-bootstrap.0 → 0.8.0
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/LICENSE +201 -0
- package/README.md +497 -0
- package/bin/corpus.mjs +4 -0
- package/bin/docs-kit.mjs +4 -0
- package/package.json +58 -3
- package/schemas/decision-record.schema.json +85 -0
- package/src/assets.mjs +67 -0
- package/src/build.mjs +535 -0
- package/src/built-on.mjs +238 -0
- package/src/bundle.mjs +174 -0
- package/src/check.mjs +520 -0
- package/src/cli.mjs +226 -0
- package/src/content-freshness.mjs +37 -0
- package/src/content.mjs +222 -0
- package/src/decisions.mjs +317 -0
- package/src/errors.mjs +24 -0
- package/src/git.mjs +171 -0
- package/src/hub.mjs +198 -0
- package/src/index.mjs +12 -0
- package/src/init.mjs +63 -0
- package/src/locate.mjs +341 -0
- package/src/manifest.mjs +589 -0
- package/src/markdown.mjs +547 -0
- package/src/mcp.mjs +583 -0
- package/src/paths.mjs +122 -0
- package/src/search.mjs +508 -0
- package/src/site-client.js +622 -0
- package/src/site-template.mjs +191 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const HUB_URL = 'https://kontourai.github.io/';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The selection toolbar and optional WebMCP tools, written once per site as
|
|
7
|
+
* a static file and loaded by every page. It is template output, never
|
|
8
|
+
* document content; it reads its configuration from the data-* attributes
|
|
9
|
+
* on <main> and puts selected text in the page only through textContent.
|
|
10
|
+
*/
|
|
11
|
+
export const SITE_SCRIPT_FILE = 'docs-kit.js';
|
|
12
|
+
export const SITE_SCRIPT = readFileSync(new URL('./site-client.js', import.meta.url), 'utf8');
|
|
13
|
+
|
|
14
|
+
export function escapeHtml(value) {
|
|
15
|
+
return String(value)
|
|
16
|
+
.replaceAll('&', '&')
|
|
17
|
+
.replaceAll('<', '<')
|
|
18
|
+
.replaceAll('>', '>')
|
|
19
|
+
.replaceAll('"', '"')
|
|
20
|
+
.replaceAll("'", ''');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Shared palette and type for product sites and the hub. Light and dark follow the OS. */
|
|
24
|
+
export const BASE_CSS = `:root {
|
|
25
|
+
color-scheme: light dark;
|
|
26
|
+
--bg: #fbfaf7; --surface: #ffffff; --fg: #1b1f24; --muted: #59636e;
|
|
27
|
+
--border: #d9dde2; --accent: #0b5cad; --accent-fg: #ffffff; --code-bg: #f1f2f4;
|
|
28
|
+
--ok: #1a7f37; --warn: #9a6700; --bad: #b42318; --info: #5a4ab5;
|
|
29
|
+
--radius: 10px;
|
|
30
|
+
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
31
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
32
|
+
}
|
|
33
|
+
@media (prefers-color-scheme: dark) {
|
|
34
|
+
:root {
|
|
35
|
+
--bg: #0e1116; --surface: #161b22; --fg: #e6edf3; --muted: #9da7b3;
|
|
36
|
+
--border: #30363d; --accent: #6cb6ff; --accent-fg: #0e1116; --code-bg: #1f252d;
|
|
37
|
+
--ok: #56d364; --warn: #e3b341; --bad: #ff7b72; --info: #b8a8ff;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
* { box-sizing: border-box; }
|
|
41
|
+
html { background: var(--bg); color: var(--fg); font: 16px/1.6 var(--font); }
|
|
42
|
+
body { margin: 0; }
|
|
43
|
+
a { color: var(--accent); text-underline-offset: 2px; }
|
|
44
|
+
a:focus-visible, button:focus-visible, [tabindex]:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
|
|
45
|
+
.skip-link { position: absolute; left: -999px; top: 0; background: var(--accent); color: var(--accent-fg); padding: .5rem 1rem; z-index: 10; }
|
|
46
|
+
.skip-link:focus { left: 1rem; top: 1rem; }
|
|
47
|
+
.site-header { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.5rem; padding: .85rem 1.5rem; border-bottom: 1px solid var(--border); background: var(--surface); }
|
|
48
|
+
.brand { font-weight: 700; font-size: 1.1rem; color: var(--fg); text-decoration: none; }
|
|
49
|
+
.brand small { font-weight: 500; color: var(--muted); margin-left: .5rem; font-size: .85rem; }
|
|
50
|
+
.site-header nav { display: flex; gap: 1.25rem; margin-left: auto; flex-wrap: wrap; }
|
|
51
|
+
.site-header nav a { color: var(--muted); text-decoration: none; font-weight: 500; }
|
|
52
|
+
.site-header nav a:hover { color: var(--fg); text-decoration: underline; }
|
|
53
|
+
code, pre { font-family: var(--mono); font-size: .9em; }
|
|
54
|
+
:not(pre) > code { background: var(--code-bg); padding: .1em .35em; border-radius: 4px; }
|
|
55
|
+
pre { background: var(--code-bg); padding: 1rem; border-radius: var(--radius); overflow-x: auto; line-height: 1.45; }
|
|
56
|
+
footer.site-footer { color: var(--muted); font-size: .875rem; border-top: 1px solid var(--border); padding: 1.25rem 1.5rem; }
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
export const SITE_CSS = `${BASE_CSS}
|
|
60
|
+
.shell { display: grid; grid-template-columns: minmax(0, 1fr); max-width: 1180px; margin: 0 auto; }
|
|
61
|
+
@media (min-width: 900px) { .shell { grid-template-columns: 250px minmax(0, 1fr); } }
|
|
62
|
+
.site-nav { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border); }
|
|
63
|
+
@media (min-width: 900px) { .site-nav { border-bottom: 0; border-right: 1px solid var(--border); position: sticky; top: 0; align-self: start; max-height: 100vh; overflow-y: auto; } }
|
|
64
|
+
.site-nav h2 { font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 1.25rem 0 .4rem; }
|
|
65
|
+
.site-nav h2:first-child { margin-top: 0; }
|
|
66
|
+
.site-nav ul { list-style: none; margin: 0; padding: 0; }
|
|
67
|
+
.site-nav li a { display: block; padding: .25rem .5rem; margin-left: -.5rem; border-radius: 6px; color: var(--fg); text-decoration: none; font-size: .95rem; }
|
|
68
|
+
.site-nav li a:hover { background: var(--code-bg); }
|
|
69
|
+
.site-nav li a[aria-current="page"] { background: var(--code-bg); color: var(--accent); font-weight: 600; }
|
|
70
|
+
main.content { padding: 1.5rem; min-width: 0; }
|
|
71
|
+
@media (min-width: 900px) { main.content { padding: 2rem 3rem; } }
|
|
72
|
+
.doc { max-width: 78ch; overflow-wrap: anywhere; }
|
|
73
|
+
.doc h1 { font-size: 2rem; line-height: 1.25; margin-top: 0; }
|
|
74
|
+
.doc h2 { margin-top: 2.25rem; padding-bottom: .3rem; border-bottom: 1px solid var(--border); }
|
|
75
|
+
.doc h1, .doc h2, .doc h3, .doc h4 { scroll-margin-top: 1rem; }
|
|
76
|
+
.doc .anchor { margin-left: .4rem; color: var(--muted); text-decoration: none; opacity: 0; font-weight: 400; }
|
|
77
|
+
.doc :is(h1, h2, h3, h4, h5, h6):hover .anchor, .doc .anchor:focus { opacity: 1; }
|
|
78
|
+
.doc img { max-width: 100%; height: auto; }
|
|
79
|
+
.doc pre.mermaid { background: transparent; text-align: center; }
|
|
80
|
+
.doc blockquote { margin: 1rem 0; padding: .25rem 1rem; border-left: 4px solid var(--border); color: var(--muted); }
|
|
81
|
+
.doc table { border-collapse: collapse; width: 100%; overflow-wrap: normal; }
|
|
82
|
+
.doc th, .doc td { border: 1px solid var(--border); padding: .45rem .7rem; text-align: left; vertical-align: top; }
|
|
83
|
+
.doc th { background: var(--code-bg); }
|
|
84
|
+
.table-scroll { overflow-x: auto; margin: 1rem 0; }
|
|
85
|
+
.doc-meta { color: var(--muted); font-size: .875rem; margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--border); }
|
|
86
|
+
.hero { margin-bottom: 2rem; }
|
|
87
|
+
.hero .tagline { font-size: 1.15rem; color: var(--muted); margin: .25rem 0 0; }
|
|
88
|
+
.section-cards { max-width: 78ch; margin-top: 2.5rem; }
|
|
89
|
+
.section-cards > h2 { margin: 0 0 .5rem; padding-bottom: .3rem; border-bottom: 1px solid var(--border); }
|
|
90
|
+
.section-cards h3 { font-size: 1rem; margin: 1.25rem 0 .6rem; color: var(--muted); }
|
|
91
|
+
.cards { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
|
|
92
|
+
.card { display: block; padding: .9rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--fg); text-decoration: none; }
|
|
93
|
+
.card:hover { border-color: var(--accent); }
|
|
94
|
+
.card strong { display: block; color: var(--accent); margin-bottom: .2rem; }
|
|
95
|
+
.card span { color: var(--muted); font-size: .9rem; }
|
|
96
|
+
.dk-toolbar { position: absolute; z-index: 20; display: flex; gap: .25rem; padding: .3rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: 0 6px 24px rgb(0 0 0 / .18); font: 500 .875rem/1.2 var(--font); opacity: 1; transform: translateY(0); transition: opacity .12s ease, transform .12s ease; }
|
|
97
|
+
.dk-toolbar[hidden] { display: none; }
|
|
98
|
+
.dk-toolbar.dk-entering { opacity: 0; transform: translateY(4px); }
|
|
99
|
+
.dk-toolbar button, .dk-dialog button { font: inherit; color: var(--fg); background: transparent; border: 1px solid transparent; border-radius: 6px; padding: .45rem .7rem; min-height: 2.25rem; cursor: pointer; }
|
|
100
|
+
.dk-toolbar button:hover { background: var(--code-bg); }
|
|
101
|
+
.dk-toolbar button.dk-primary, .dk-dialog button.dk-primary { background: var(--accent); color: var(--accent-fg); }
|
|
102
|
+
.dk-toolbar button.dk-primary:hover { filter: brightness(1.08); }
|
|
103
|
+
.dk-toolbar kbd { font: inherit; font-size: .75em; opacity: .7; margin-left: .3rem; }
|
|
104
|
+
@media (pointer: coarse) { .dk-toolbar button { min-height: 2.75rem; padding: .6rem .85rem; } .dk-toolbar kbd { display: none; } }
|
|
105
|
+
.dk-toast { position: fixed; left: 50%; bottom: 1.5rem; z-index: 30; max-width: min(90vw, 36rem); transform: translate(-50%, 0); padding: .6rem 1rem; border-radius: var(--radius); background: var(--fg); color: var(--bg); font-size: .9rem; box-shadow: 0 6px 24px rgb(0 0 0 / .25); opacity: 0; pointer-events: none; transition: opacity .15s ease, transform .15s ease; }
|
|
106
|
+
.dk-toast.dk-visible { opacity: 1; transform: translate(-50%, -.25rem); }
|
|
107
|
+
.dk-dialog { max-width: min(92vw, 44rem); width: 100%; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--fg); padding: 1.25rem; }
|
|
108
|
+
.dk-dialog::backdrop { background: rgb(0 0 0 / .45); }
|
|
109
|
+
.dk-dialog h2 { margin: 0 0 .5rem; font-size: 1.1rem; }
|
|
110
|
+
.dk-dialog p { margin: 0 0 .75rem; color: var(--muted); }
|
|
111
|
+
.dk-dialog textarea { width: 100%; min-height: 14rem; font: .85rem/1.45 var(--mono); color: var(--fg); background: var(--code-bg); border: 1px solid var(--border); border-radius: 6px; padding: .6rem; resize: vertical; }
|
|
112
|
+
.dk-dialog .dk-actions { display: flex; justify-content: flex-end; margin-top: .75rem; }
|
|
113
|
+
@media (prefers-reduced-motion: reduce) { .dk-toolbar, .dk-toast { transition: none; } .dk-toolbar.dk-entering { transform: none; } .dk-toast.dk-visible { transform: translate(-50%, 0); } }
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mermaid for pages with a ```mermaid fence, pinned to one exact file by SRI.
|
|
118
|
+
* The integrity hash is sha384 of
|
|
119
|
+
* https://cdn.jsdelivr.net/npm/mermaid@12.1.0/dist/mermaid.min.js, which is
|
|
120
|
+
* byte-identical to dist/mermaid.min.js in the npm tarball. Changing the
|
|
121
|
+
* version means recomputing the hash from the fetched file.
|
|
122
|
+
*/
|
|
123
|
+
export const MERMAID_VERSION = '12.1.0';
|
|
124
|
+
export const MERMAID_SRC = `https://cdn.jsdelivr.net/npm/mermaid@${MERMAID_VERSION}/dist/mermaid.min.js`;
|
|
125
|
+
export const MERMAID_INTEGRITY = 'sha384-EbBpjO7rlR6eqZEcG7GaPpyk9H9WrMyPWX4d3KvPYltgt8Z8l0z6R56B1qP40pR4';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The template's own scripts, outside the sanitized document content.
|
|
129
|
+
* securityLevel 'strict' disables click handlers. Strict mode still renders
|
|
130
|
+
* HTML inside labels (through Mermaid's own DOMPurify), which would bypass
|
|
131
|
+
* this site's sanitizer and link checks, so htmlLabels is off and labels are
|
|
132
|
+
* SVG text. The theme is chosen once from prefers-color-scheme at load.
|
|
133
|
+
*/
|
|
134
|
+
const MERMAID_SCRIPTS = `<script src="${MERMAID_SRC}" integrity="${MERMAID_INTEGRITY}" crossorigin="anonymous"></script>
|
|
135
|
+
<script>mermaid.initialize({ startOnLoad: true, securityLevel: 'strict', htmlLabels: false, flowchart: { htmlLabels: false }, theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default' });</script>
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
/** Provenance on <main>, read by the selection toolbar: one place per page, not per block. */
|
|
139
|
+
function mainAttributes(manifest, sha, sourcePath) {
|
|
140
|
+
return Object.entries({
|
|
141
|
+
'data-src-path': sourcePath,
|
|
142
|
+
'data-src-sha': sha,
|
|
143
|
+
'data-repo': manifest.repo,
|
|
144
|
+
'data-product': manifest.product,
|
|
145
|
+
'data-title': manifest.title,
|
|
146
|
+
'data-branch': manifest.branch,
|
|
147
|
+
'data-selection-primary': manifest.selectionActions.primary,
|
|
148
|
+
})
|
|
149
|
+
.map(([name, value]) => `${name}="${escapeHtml(value)}"`)
|
|
150
|
+
.join(' ');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** One page of a product site. `toRoot` is the relative prefix back to the site root. */
|
|
154
|
+
export function sitePage({ manifest, title, description, navHtml, content, toRoot, sha, sourcePath, mermaid = false }) {
|
|
155
|
+
const repoUrl = `https://github.com/${manifest.repo}`;
|
|
156
|
+
return `<!doctype html>
|
|
157
|
+
<html lang="en">
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="utf-8">
|
|
160
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
161
|
+
<title>${escapeHtml(title)}</title>
|
|
162
|
+
${description ? `<meta name="description" content="${escapeHtml(description)}">\n` : ''}<meta name="generator" content="@kontourai/corpus">
|
|
163
|
+
<link rel="stylesheet" href="${toRoot}docs-kit.css">
|
|
164
|
+
<script src="${toRoot}${SITE_SCRIPT_FILE}" defer></script>
|
|
165
|
+
</head>
|
|
166
|
+
<body>
|
|
167
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
168
|
+
<header class="site-header">
|
|
169
|
+
<a class="brand" href="${toRoot}index.html">${escapeHtml(manifest.title)}${manifest.tagline ? `<small>${escapeHtml(manifest.tagline)}</small>` : ''}</a>
|
|
170
|
+
<nav aria-label="Site">
|
|
171
|
+
<a href="${HUB_URL}">All Kontour docs</a>
|
|
172
|
+
<a href="${escapeHtml(repoUrl)}">GitHub</a>
|
|
173
|
+
</nav>
|
|
174
|
+
</header>
|
|
175
|
+
<div class="shell">
|
|
176
|
+
<nav class="site-nav" aria-label="Documentation">
|
|
177
|
+
${navHtml}
|
|
178
|
+
</nav>
|
|
179
|
+
<main id="main" class="content" ${mainAttributes(manifest, sha, sourcePath)}>
|
|
180
|
+
${content}
|
|
181
|
+
</main>
|
|
182
|
+
</div>
|
|
183
|
+
<footer class="site-footer">
|
|
184
|
+
Built from <a href="${escapeHtml(`${repoUrl}/commit/${sha}`)}"><code>${escapeHtml(sha.slice(0, 7))}</code></a> of
|
|
185
|
+
<a href="${escapeHtml(repoUrl)}">${escapeHtml(manifest.repo)}</a> with Corpus.
|
|
186
|
+
Part of the <a href="${HUB_URL}">Kontour documentation hub</a>.
|
|
187
|
+
</footer>
|
|
188
|
+
${mermaid ? MERMAID_SCRIPTS : ''}</body>
|
|
189
|
+
</html>
|
|
190
|
+
`;
|
|
191
|
+
}
|