@diff-review-system/drs 4.1.0 → 5.0.0-rc.1

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 (145) hide show
  1. package/.pi/agents/task/okf-wiki-maintainer.md +72 -0
  2. package/.pi/workflows/github-pr-fix-review-issues-stacked.yaml +2 -0
  3. package/.pi/workflows/github-pr-review-post.yaml +29 -19
  4. package/.pi/workflows/github-pr-review.yaml +23 -0
  5. package/.pi/workflows/github-pr-update-agents-md-stacked.yaml +2 -0
  6. package/.pi/workflows/gitlab-mr-fix-review-issues-stacked.yaml +2 -0
  7. package/.pi/workflows/gitlab-mr-review.yaml +6 -0
  8. package/.pi/workflows/gitlab-mr-update-agents-md-stacked.yaml +2 -0
  9. package/.pi/workflows/release-changelog-finalize.yaml +10 -0
  10. package/.pi/workflows/repository-wiki-check.yaml +40 -0
  11. package/.pi/workflows/repository-wiki-sync.yaml +187 -0
  12. package/.wiki-site/.vitepress/config.mts +452 -0
  13. package/.wiki-site/.vitepress/theme/PageLead.vue +122 -0
  14. package/.wiki-site/.vitepress/theme/index.ts +12 -0
  15. package/.wiki-site/.vitepress/theme/style.css +270 -0
  16. package/README.md +38 -4
  17. package/dist/cli/index.js +3 -127
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/init.d.ts.map +1 -1
  20. package/dist/cli/init.js +0 -3
  21. package/dist/cli/init.js.map +1 -1
  22. package/dist/cli/run-agent.d.ts +3 -0
  23. package/dist/cli/run-agent.d.ts.map +1 -1
  24. package/dist/cli/run-agent.js +56 -5
  25. package/dist/cli/run-agent.js.map +1 -1
  26. package/dist/cli/wiki.d.ts +3 -0
  27. package/dist/cli/wiki.d.ts.map +1 -0
  28. package/dist/cli/wiki.js +174 -0
  29. package/dist/cli/wiki.js.map +1 -0
  30. package/dist/cli/workflow.d.ts +2 -0
  31. package/dist/cli/workflow.d.ts.map +1 -1
  32. package/dist/cli/workflow.js +344 -31
  33. package/dist/cli/workflow.js.map +1 -1
  34. package/dist/github/client.d.ts.map +1 -1
  35. package/dist/github/client.js +2 -2
  36. package/dist/github/client.js.map +1 -1
  37. package/dist/github/platform-adapter.d.ts.map +1 -1
  38. package/dist/github/platform-adapter.js +15 -1
  39. package/dist/github/platform-adapter.js.map +1 -1
  40. package/dist/gitlab/client.d.ts +4 -0
  41. package/dist/gitlab/client.d.ts.map +1 -1
  42. package/dist/gitlab/client.js +17 -1
  43. package/dist/gitlab/client.js.map +1 -1
  44. package/dist/gitlab/platform-adapter.d.ts.map +1 -1
  45. package/dist/gitlab/platform-adapter.js +17 -1
  46. package/dist/gitlab/platform-adapter.js.map +1 -1
  47. package/dist/lib/agent-permissions.d.ts +44 -0
  48. package/dist/lib/agent-permissions.d.ts.map +1 -0
  49. package/dist/lib/agent-permissions.js +358 -0
  50. package/dist/lib/agent-permissions.js.map +1 -0
  51. package/dist/lib/agent-result.d.ts +2 -0
  52. package/dist/lib/agent-result.d.ts.map +1 -1
  53. package/dist/lib/comment-poster.d.ts +1 -1
  54. package/dist/lib/comment-poster.d.ts.map +1 -1
  55. package/dist/lib/comment-poster.js +38 -16
  56. package/dist/lib/comment-poster.js.map +1 -1
  57. package/dist/lib/config.d.ts +6 -1
  58. package/dist/lib/config.d.ts.map +1 -1
  59. package/dist/lib/config.js +17 -0
  60. package/dist/lib/config.js.map +1 -1
  61. package/dist/lib/error-comment-poster.d.ts +1 -1
  62. package/dist/lib/error-comment-poster.d.ts.map +1 -1
  63. package/dist/lib/error-comment-poster.js +15 -7
  64. package/dist/lib/error-comment-poster.js.map +1 -1
  65. package/dist/lib/okf-wiki.d.ts +65 -0
  66. package/dist/lib/okf-wiki.d.ts.map +1 -0
  67. package/dist/lib/okf-wiki.js +647 -0
  68. package/dist/lib/okf-wiki.js.map +1 -0
  69. package/dist/lib/platform-client.d.ts +2 -0
  70. package/dist/lib/platform-client.d.ts.map +1 -1
  71. package/dist/lib/project-setup.d.ts +0 -3
  72. package/dist/lib/project-setup.d.ts.map +1 -1
  73. package/dist/lib/project-setup.js +1 -15
  74. package/dist/lib/project-setup.js.map +1 -1
  75. package/dist/lib/review-artifact.d.ts +11 -1
  76. package/dist/lib/review-artifact.d.ts.map +1 -1
  77. package/dist/lib/review-artifact.js +202 -0
  78. package/dist/lib/review-artifact.js.map +1 -1
  79. package/dist/lib/review-orchestrator.d.ts +6 -1
  80. package/dist/lib/review-orchestrator.d.ts.map +1 -1
  81. package/dist/lib/review-orchestrator.js +3 -1
  82. package/dist/lib/review-orchestrator.js.map +1 -1
  83. package/dist/lib/wiki-delta.d.ts +71 -0
  84. package/dist/lib/wiki-delta.d.ts.map +1 -0
  85. package/dist/lib/wiki-delta.js +524 -0
  86. package/dist/lib/wiki-delta.js.map +1 -0
  87. package/dist/lib/wiki-run-summary.d.ts +45 -0
  88. package/dist/lib/wiki-run-summary.d.ts.map +1 -0
  89. package/dist/lib/wiki-run-summary.js +168 -0
  90. package/dist/lib/wiki-run-summary.js.map +1 -0
  91. package/dist/lib/wiki-search.d.ts +24 -0
  92. package/dist/lib/wiki-search.d.ts.map +1 -0
  93. package/dist/lib/wiki-search.js +200 -0
  94. package/dist/lib/wiki-search.js.map +1 -0
  95. package/dist/lib/wiki-site-graph.d.ts +33 -0
  96. package/dist/lib/wiki-site-graph.d.ts.map +1 -0
  97. package/dist/lib/wiki-site-graph.js +446 -0
  98. package/dist/lib/wiki-site-graph.js.map +1 -0
  99. package/dist/lib/wiki-site-safety.d.ts +6 -0
  100. package/dist/lib/wiki-site-safety.d.ts.map +1 -0
  101. package/dist/lib/wiki-site-safety.js +83 -0
  102. package/dist/lib/wiki-site-safety.js.map +1 -0
  103. package/dist/lib/wiki-site-smoke.d.ts +16 -0
  104. package/dist/lib/wiki-site-smoke.d.ts.map +1 -0
  105. package/dist/lib/wiki-site-smoke.js +270 -0
  106. package/dist/lib/wiki-site-smoke.js.map +1 -0
  107. package/dist/lib/wiki-site.d.ts +26 -0
  108. package/dist/lib/wiki-site.d.ts.map +1 -0
  109. package/dist/lib/wiki-site.js +221 -0
  110. package/dist/lib/wiki-site.js.map +1 -0
  111. package/dist/lib/workflow/planning.d.ts +1 -0
  112. package/dist/lib/workflow/planning.d.ts.map +1 -1
  113. package/dist/lib/workflow/planning.js +53 -2
  114. package/dist/lib/workflow/planning.js.map +1 -1
  115. package/dist/lib/workflow/types.d.ts +5 -0
  116. package/dist/lib/workflow/types.d.ts.map +1 -1
  117. package/dist/lib/workflow-artifacts.d.ts.map +1 -1
  118. package/dist/lib/workflow-artifacts.js +23 -1
  119. package/dist/lib/workflow-artifacts.js.map +1 -1
  120. package/dist/pi/sdk.d.ts.map +1 -1
  121. package/dist/pi/sdk.js +270 -17
  122. package/dist/pi/sdk.js.map +1 -1
  123. package/dist/runtime/built-in-paths.d.ts +0 -1
  124. package/dist/runtime/built-in-paths.d.ts.map +1 -1
  125. package/dist/runtime/built-in-paths.js +0 -7
  126. package/dist/runtime/built-in-paths.js.map +1 -1
  127. package/dist/runtime/client.d.ts +3 -0
  128. package/dist/runtime/client.d.ts.map +1 -1
  129. package/dist/runtime/client.js +4 -0
  130. package/dist/runtime/client.js.map +1 -1
  131. package/dist/temporal/executor.d.ts.map +1 -1
  132. package/dist/temporal/executor.js +9 -2
  133. package/dist/temporal/executor.js.map +1 -1
  134. package/dist/temporal/retry-policy.d.ts +1 -1
  135. package/dist/temporal/retry-policy.d.ts.map +1 -1
  136. package/dist/temporal/retry-policy.js +6 -1
  137. package/dist/temporal/retry-policy.js.map +1 -1
  138. package/dist/temporal/workflows.d.ts.map +1 -1
  139. package/dist/temporal/workflows.js +16 -3
  140. package/dist/temporal/workflows.js.map +1 -1
  141. package/package.json +23 -13
  142. package/dist/lib/skills.d.ts +0 -16
  143. package/dist/lib/skills.d.ts.map +0 -1
  144. package/dist/lib/skills.js +0 -140
  145. package/dist/lib/skills.js.map +0 -1
@@ -0,0 +1,452 @@
1
+ import { cp, lstat, mkdir, readdir, readFile, realpath, rm, writeFile } from 'fs/promises';
2
+ import { lstatSync } from 'fs';
3
+ import { createRequire } from 'module';
4
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { defineConfig, type DefaultTheme } from 'vitepress';
7
+ import { parse as parseYaml } from 'yaml';
8
+ import {
9
+ isSafeWikiSiteRemoteUrl,
10
+ neutralizeWikiSiteMarkdown,
11
+ normalizeWikiSiteBase,
12
+ readWikiSiteOkfVersion,
13
+ sanitizeWikiSiteFrontmatter,
14
+ } from '../../dist/lib/wiki-site-safety.js';
15
+ import {
16
+ createWikiSiteGraphHtml,
17
+ encodeWikiSiteConceptId,
18
+ extractWikiSiteConceptLinks,
19
+ } from '../../dist/lib/wiki-site-graph.js';
20
+
21
+ interface OkfConcept {
22
+ description: string;
23
+ id: string;
24
+ links: string[];
25
+ title: string;
26
+ type: string;
27
+ }
28
+
29
+ interface WikiThemeConfig extends DefaultTheme.Config {
30
+ startConcept?: { link: string; text: string };
31
+ sourceRepository?: string;
32
+ }
33
+
34
+ const configDirectory = dirname(fileURLToPath(import.meta.url));
35
+ const require = createRequire(import.meta.url);
36
+ const bundledRepositoryRoot = resolve(configDirectory, '../..');
37
+ const siteRoot = resolve(configDirectory, '..');
38
+ const projectRoot = resolve(process.env.DRS_WIKI_SITE_PROJECT_ROOT ?? bundledRepositoryRoot);
39
+ const wikiRoot = resolve(projectRoot, process.env.DRS_WIKI_SITE_SOURCE ?? 'wiki');
40
+ const outputRoot = resolve(process.env.DRS_WIKI_SITE_OUTPUT ?? join(siteRoot, 'dist'));
41
+ const repository =
42
+ process.env.DRS_WIKI_SITE_REPOSITORY !== undefined
43
+ ? process.env.DRS_WIKI_SITE_REPOSITORY
44
+ : process.env.GITHUB_REPOSITORY || 'manojlds/drs';
45
+ const [repositoryOwner = 'localhost', configuredRepositoryName] = repository.split('/');
46
+ const repositoryName = configuredRepositoryName || basename(projectRoot);
47
+ const base = normalizeWikiSiteBase(
48
+ process.env.WIKI_SITE_BASE ??
49
+ (process.env.GITHUB_ACTIONS === 'true' ? `/${repositoryName}/` : '/')
50
+ );
51
+ const siteUrl = (
52
+ process.env.WIKI_SITE_URL ?? `https://${repositoryOwner}.github.io/${repositoryName}`
53
+ ).replace(/\/+$/, '');
54
+ const siteTitle = process.env.DRS_WIKI_SITE_TITLE ?? `${repositoryName} Knowledge Map`;
55
+ const siteDescription = `Open Knowledge Format concepts and relationships for ${repositoryName}.`;
56
+ const sourcePath = relative(projectRoot, wikiRoot).split(sep).join('/');
57
+ const markdownFiles = await listMarkdownFiles(wikiRoot);
58
+ const concepts = await loadConcepts(wikiRoot, markdownFiles);
59
+ const startConcept = concepts.find((concept) => concept.id === 'quickstart') ?? concepts[0];
60
+ const hasLog = markdownFiles.includes('log.md');
61
+
62
+ export default defineConfig({
63
+ title: siteTitle,
64
+ titleTemplate: `:title · ${siteTitle}`,
65
+ description: siteDescription,
66
+ lang: 'en-US',
67
+ base,
68
+ srcDir: relative(siteRoot, wikiRoot).split(sep).join('/'),
69
+ outDir: outputRoot,
70
+ cacheDir: resolve(projectRoot, '.drs/wiki-site-cache'),
71
+ cleanUrls: false,
72
+ lastUpdated: true,
73
+ sitemap: {
74
+ hostname: `${siteUrl}/`,
75
+ },
76
+ head: [
77
+ ['meta', { name: 'theme-color', content: '#16213d' }],
78
+ ['meta', { name: 'color-scheme', content: 'light dark' }],
79
+ ],
80
+ vite: {
81
+ logLevel: process.env.DRS_WIKI_SITE_QUIET === 'true' ? 'silent' : 'info',
82
+ resolve: {
83
+ alias: [
84
+ { find: /^vue$/, replacement: require.resolve('vue/dist/vue.runtime.esm-bundler.js') },
85
+ {
86
+ find: /^vue\/server-renderer$/,
87
+ replacement: require.resolve('@vue/server-renderer/dist/server-renderer.esm-bundler.js'),
88
+ },
89
+ ],
90
+ },
91
+ plugins: [
92
+ {
93
+ name: 'okf-safe-markdown-source',
94
+ enforce: 'pre',
95
+ transform(source, id) {
96
+ if (!id.endsWith('.md')) return null;
97
+ return neutralizeWikiSiteMarkdown(source);
98
+ },
99
+ },
100
+ {
101
+ name: 'okf-development-artifacts',
102
+ configureServer(server) {
103
+ server.middlewares.use(async (request, response, next) => {
104
+ try {
105
+ const requestUrl = new URL(request.url ?? '/', 'http://localhost');
106
+ if (base !== '/' && !requestUrl.pathname.startsWith(base)) {
107
+ next();
108
+ return;
109
+ }
110
+ const sitePath =
111
+ base !== '/' && requestUrl.pathname.startsWith(base)
112
+ ? `/${requestUrl.pathname.slice(base.length)}`
113
+ : requestUrl.pathname;
114
+ if (sitePath === '/graph.html') {
115
+ const currentConcepts = await loadConcepts(wikiRoot);
116
+ sendDevelopmentArtifact(
117
+ response,
118
+ createWikiSiteGraphHtml(currentConcepts, { base, siteTitle }),
119
+ 'text/html; charset=utf-8'
120
+ );
121
+ return;
122
+ }
123
+ if (sitePath === '/llms.txt') {
124
+ const currentConcepts = await loadConcepts(wikiRoot);
125
+ sendDevelopmentArtifact(
126
+ response,
127
+ createLlmsText(currentConcepts),
128
+ 'text/plain; charset=utf-8'
129
+ );
130
+ return;
131
+ }
132
+ if (sitePath.startsWith('/okf/')) {
133
+ const rawPath = resolve(
134
+ wikiRoot,
135
+ decodeURIComponent(sitePath.slice('/okf/'.length))
136
+ );
137
+ const relativePath = relative(wikiRoot, rawPath);
138
+ if (
139
+ extname(rawPath) !== '.md' ||
140
+ isAbsolute(relativePath) ||
141
+ relativePath === '..' ||
142
+ relativePath.startsWith(`..${sep}`)
143
+ ) {
144
+ next();
145
+ return;
146
+ }
147
+ if (!(await isSafeRawWikiPath(rawPath, relativePath))) {
148
+ next();
149
+ return;
150
+ }
151
+ sendDevelopmentArtifact(
152
+ response,
153
+ await readFile(rawPath, 'utf-8'),
154
+ 'text/markdown; charset=utf-8'
155
+ );
156
+ return;
157
+ }
158
+ next();
159
+ } catch (error) {
160
+ next(error);
161
+ }
162
+ });
163
+ },
164
+ },
165
+ ],
166
+ },
167
+ markdown: {
168
+ html: false,
169
+ config(markdown) {
170
+ const render = markdown.renderer.render.bind(markdown.renderer);
171
+ const renderImage = markdown.renderer.rules.image;
172
+ markdown.renderer.rules.image = (tokens, index, options, environment, renderer) => {
173
+ const source = tokens[index]?.attrGet('src') ?? '';
174
+ if (isSafeWikiSiteRemoteUrl(source) && renderImage) {
175
+ return renderImage(tokens, index, options, environment, renderer);
176
+ }
177
+ const alternative = markdown.utils.escapeHtml(tokens[index]?.content || 'image');
178
+ return `<span class="okf-image-placeholder">[Image: ${alternative}]</span>`;
179
+ };
180
+ markdown.renderer.render = (tokens, options, environment) =>
181
+ render(tokens, options, environment)
182
+ .replaceAll('{{', '&#123;&#123;')
183
+ .replaceAll('}}', '&#125;&#125;');
184
+ },
185
+ },
186
+ transformPageData(pageData) {
187
+ pageData.frontmatter = sanitizeWikiSiteFrontmatter(pageData.frontmatter);
188
+ },
189
+ themeConfig: {
190
+ nav: [
191
+ ...(startConcept
192
+ ? [{ text: 'Start here', link: `/${encodeWikiSiteConceptId(startConcept.id)}` }]
193
+ : []),
194
+ { text: 'Concepts', link: '/' },
195
+ { text: 'Graph', link: `${siteUrl}/graph.html`, target: '_self' },
196
+ { text: 'Raw OKF', link: `${siteUrl}/okf/index.md` },
197
+ {
198
+ text: 'OKF v0.1',
199
+ link: 'https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md',
200
+ },
201
+ ],
202
+ sidebar: createSidebar(concepts, hasLog),
203
+ ...(startConcept
204
+ ? {
205
+ startConcept: {
206
+ link: `/${encodeWikiSiteConceptId(startConcept.id)}`,
207
+ text: startConcept.title,
208
+ },
209
+ }
210
+ : {}),
211
+ ...(repository ? { sourceRepository: repository } : {}),
212
+ search: {
213
+ provider: 'local',
214
+ },
215
+ outline: {
216
+ level: 'deep',
217
+ label: 'On this concept',
218
+ },
219
+ editLink: repository
220
+ ? {
221
+ pattern: `https://github.com/${repository}/edit/main/${sourcePath}/:path`,
222
+ text: 'Edit this concept on GitHub',
223
+ }
224
+ : undefined,
225
+ lastUpdated: {
226
+ text: 'Last source update',
227
+ formatOptions: {
228
+ dateStyle: 'medium',
229
+ timeStyle: 'short',
230
+ },
231
+ },
232
+ socialLinks: repository ? [{ icon: 'github', link: `https://github.com/${repository}` }] : [],
233
+ docFooter: {
234
+ prev: 'Previous concept',
235
+ next: 'Next concept',
236
+ },
237
+ } satisfies WikiThemeConfig,
238
+ async buildEnd(siteConfig) {
239
+ const rawBundleOutput = join(siteConfig.outDir, 'okf');
240
+ await rm(rawBundleOutput, { recursive: true, force: true });
241
+ await cp(wikiRoot, rawBundleOutput, {
242
+ recursive: true,
243
+ filter: (source) => lstatSync(source).isDirectory() || extname(source) === '.md',
244
+ });
245
+ await mkdir(siteConfig.outDir, { recursive: true });
246
+ await Promise.all([
247
+ writeFile(join(siteConfig.outDir, 'llms.txt'), createLlmsText(concepts), 'utf-8'),
248
+ writeFile(
249
+ join(siteConfig.outDir, 'graph.html'),
250
+ createWikiSiteGraphHtml(concepts, { base, siteTitle }),
251
+ 'utf-8'
252
+ ),
253
+ writeFile(join(siteConfig.outDir, '.nojekyll'), '', 'utf-8'),
254
+ ]);
255
+ await assertSiteOutput(siteConfig.outDir);
256
+ },
257
+ });
258
+
259
+ function sendDevelopmentArtifact(
260
+ response: { end(content: string): void; setHeader(name: string, value: string): void },
261
+ content: string,
262
+ contentType: string
263
+ ): void {
264
+ response.setHeader('Cache-Control', 'no-store');
265
+ response.setHeader('Content-Type', contentType);
266
+ response.end(content);
267
+ }
268
+
269
+ async function assertSiteOutput(outputDirectory: string): Promise<void> {
270
+ const [indexHtml, graphHtml, rawIndex, llmsText] = await Promise.all([
271
+ readFile(join(outputDirectory, 'index.html'), 'utf-8'),
272
+ readFile(join(outputDirectory, 'graph.html'), 'utf-8'),
273
+ readFile(join(outputDirectory, 'okf', 'index.md'), 'utf-8'),
274
+ readFile(join(outputDirectory, 'llms.txt'), 'utf-8'),
275
+ ]);
276
+ const expectedLinks = [
277
+ `href="${base}assets/`,
278
+ `href="${siteUrl}/graph.html`,
279
+ ...(startConcept ? [`href="${base}${encodeWikiSiteConceptId(startConcept.id)}.html`] : []),
280
+ ];
281
+ const missingLink = expectedLinks.find((link) => !indexHtml.includes(link));
282
+ if (missingLink) {
283
+ throw new Error(`Generated wiki site is missing expected base-path link ${missingLink}`);
284
+ }
285
+ if (readWikiSiteOkfVersion(rawIndex) !== '0.1') {
286
+ throw new Error('Generated wiki site is missing the unchanged OKF bundle index');
287
+ }
288
+ if (
289
+ startConcept &&
290
+ !llmsText.includes(`${siteUrl}/${encodeWikiSiteConceptId(startConcept.id)}.html`)
291
+ ) {
292
+ throw new Error('Generated wiki site is missing public concept URLs in llms.txt');
293
+ }
294
+ if (!graphHtml.includes(`href="${base}"`) || !graphHtml.includes('id="graph-data"')) {
295
+ throw new Error('Generated wiki site is missing the base-aware concept graph');
296
+ }
297
+ }
298
+
299
+ async function loadConcepts(directory: string, markdownFiles?: string[]): Promise<OkfConcept[]> {
300
+ const concepts: Array<Omit<OkfConcept, 'links'> & { content: string }> = [];
301
+ for (const filePath of markdownFiles ?? (await listMarkdownFiles(directory))) {
302
+ const name = filePath.split('/').at(-1);
303
+ if (name === 'index.md' || name === 'log.md') continue;
304
+ const content = await readFile(join(directory, ...filePath.split('/')), 'utf-8');
305
+ const frontmatter = parseFrontmatter(content);
306
+ if (!frontmatter?.type) continue;
307
+ const id = filePath.replace(/\.md$/, '');
308
+ concepts.push({
309
+ id,
310
+ type: String(frontmatter.type),
311
+ title: String(frontmatter.title ?? titleFromId(id)),
312
+ description: String(frontmatter.description ?? ''),
313
+ content,
314
+ });
315
+ }
316
+ const conceptIds = new Set(concepts.map((concept) => concept.id));
317
+ return concepts
318
+ .map(({ content, ...concept }) => ({
319
+ ...concept,
320
+ links: extractWikiSiteConceptLinks(content, concept.id, conceptIds),
321
+ }))
322
+ .sort((left, right) => compareStrings(left.id, right.id));
323
+ }
324
+
325
+ async function listMarkdownFiles(directory: string, current = ''): Promise<string[]> {
326
+ const absoluteDirectory = join(directory, current);
327
+ const entries = await readdir(absoluteDirectory, { withFileTypes: true });
328
+ const files: string[] = [];
329
+ for (const entry of entries.sort((left, right) => compareStrings(left.name, right.name))) {
330
+ if (entry.name.startsWith('.')) continue;
331
+ const relativePath = current ? `${current}/${entry.name}` : entry.name;
332
+ if (entry.isDirectory()) {
333
+ files.push(...(await listMarkdownFiles(directory, relativePath)));
334
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
335
+ files.push(relativePath);
336
+ }
337
+ }
338
+ return files;
339
+ }
340
+
341
+ function parseFrontmatter(content: string): Record<string, unknown> | null {
342
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content.replace(/^\uFEFF/u, ''));
343
+ if (!match?.[1]) return null;
344
+ const parsed = parseYaml(match[1]);
345
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
346
+ ? (parsed as Record<string, unknown>)
347
+ : null;
348
+ }
349
+
350
+ function createSidebar(concepts: OkfConcept[], hasLog: boolean): DefaultTheme.SidebarItem[] {
351
+ const typeOrder = [
352
+ 'Quickstart',
353
+ 'Architecture',
354
+ 'Workflow',
355
+ 'Configuration',
356
+ 'Integration',
357
+ 'Operations',
358
+ ];
359
+ const groups = new Map<string, OkfConcept[]>();
360
+ for (const concept of concepts) {
361
+ const group = groups.get(concept.type) ?? [];
362
+ group.push(concept);
363
+ groups.set(concept.type, group);
364
+ }
365
+ const orderedTypes = [...groups.keys()].sort((left, right) => {
366
+ const leftIndex = typeOrder.indexOf(left);
367
+ const rightIndex = typeOrder.indexOf(right);
368
+ if (leftIndex < 0 && rightIndex < 0) return compareStrings(left, right);
369
+ if (leftIndex < 0) return 1;
370
+ if (rightIndex < 0) return -1;
371
+ return leftIndex - rightIndex;
372
+ });
373
+
374
+ return [
375
+ {
376
+ text: 'Bundle',
377
+ items: [
378
+ { text: 'Concept index', link: '/' },
379
+ ...(hasLog ? [{ text: 'Update log', link: '/log' }] : []),
380
+ ],
381
+ },
382
+ ...orderedTypes.map((type) => ({
383
+ text: type,
384
+ collapsed: false,
385
+ items: (groups.get(type) ?? []).map((concept) => ({
386
+ text: concept.title,
387
+ link: `/${encodeWikiSiteConceptId(concept.id)}`,
388
+ })),
389
+ })),
390
+ ];
391
+ }
392
+
393
+ function createLlmsText(items: OkfConcept[]): string {
394
+ const lines = [
395
+ `# ${inlineMarkdownText(siteTitle)}`,
396
+ '',
397
+ `> ${siteDescription}`,
398
+ '',
399
+ '## Concepts',
400
+ '',
401
+ ];
402
+ for (const concept of items) {
403
+ const suffix = concept.description ? `: ${inlineMarkdownText(concept.description)}` : '';
404
+ lines.push(
405
+ `- [${markdownLinkText(concept.title)}](${siteUrl}/${encodeWikiSiteConceptId(concept.id)}.html)${suffix}`
406
+ );
407
+ }
408
+ lines.push(
409
+ '',
410
+ '## Explore',
411
+ '',
412
+ `- [Concept graph](${siteUrl}/graph.html): Interactive view of relationships between concepts.`,
413
+ '',
414
+ '## Raw OKF bundle',
415
+ '',
416
+ `- [Bundle index](${siteUrl}/okf/index.md)`,
417
+ ''
418
+ );
419
+ return lines.join('\n');
420
+ }
421
+
422
+ function inlineMarkdownText(value: string): string {
423
+ return value.replace(/\s+/g, ' ').trim();
424
+ }
425
+
426
+ function markdownLinkText(value: string): string {
427
+ return inlineMarkdownText(value).replace(/[\\\[\]]/g, '\\$&');
428
+ }
429
+
430
+ async function isSafeRawWikiPath(filePath: string, relativePath: string): Promise<boolean> {
431
+ try {
432
+ const status = await lstat(filePath);
433
+ if (!status.isFile()) return false;
434
+ const [realRoot, realFile] = await Promise.all([realpath(wikiRoot), realpath(filePath)]);
435
+ return realFile === resolve(realRoot, relativePath);
436
+ } catch {
437
+ return false;
438
+ }
439
+ }
440
+
441
+ function titleFromId(id: string): string {
442
+ const name = id.split('/').at(-1) ?? id;
443
+ return name
444
+ .split('-')
445
+ .filter(Boolean)
446
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
447
+ .join(' ');
448
+ }
449
+
450
+ function compareStrings(left: string, right: string): number {
451
+ return left < right ? -1 : left > right ? 1 : 0;
452
+ }
@@ -0,0 +1,122 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { type DefaultTheme, useData, withBase } from 'vitepress';
4
+
5
+ interface WikiThemeConfig extends DefaultTheme.Config {
6
+ startConcept?: { link: string; text: string };
7
+ sourceRepository?: string;
8
+ }
9
+
10
+ interface WikiSourceEntry {
11
+ path: string;
12
+ symbols: string[];
13
+ url?: string;
14
+ }
15
+
16
+ const { page, frontmatter, site, theme } = useData<WikiThemeConfig>();
17
+ const isIndex = computed(() => page.value.relativePath === 'index.md');
18
+ const startConcept = computed(() => theme.value.startConcept);
19
+ const type = computed(() => stringValue(frontmatter.value.type));
20
+ const description = computed(() => stringValue(frontmatter.value.description));
21
+ const resource = computed(() => stringValue(frontmatter.value.resource));
22
+ const resourceUrl = computed(() => safeRemoteUrl(resource.value));
23
+ const timestamp = computed(() => stringValue(frontmatter.value.timestamp));
24
+ const tags = computed(() => {
25
+ const value = frontmatter.value.tags;
26
+ if (Array.isArray(value)) return value.map(String);
27
+ return value ? [String(value)] : [];
28
+ });
29
+ const sources = computed<WikiSourceEntry[]>(() => {
30
+ const value = frontmatter.value.drs_sources;
31
+ if (!Array.isArray(value)) return [];
32
+ const repository = theme.value.sourceRepository;
33
+ return value.flatMap((entry) => {
34
+ if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) return [];
35
+ const record = entry as Record<string, unknown>;
36
+ const sourcePath = stringValue(record.path);
37
+ if (!sourcePath) return [];
38
+ const symbols = Array.isArray(record.symbols)
39
+ ? record.symbols.map(stringValue).filter(Boolean)
40
+ : [];
41
+ const url = repository
42
+ ? `https://github.com/${repository}/blob/main/${sourcePath
43
+ .split('/')
44
+ .map(encodeURIComponent)
45
+ .join('/')}`
46
+ : undefined;
47
+ return [{ path: sourcePath, symbols, ...(url ? { url } : {}) }];
48
+ });
49
+ });
50
+
51
+ function stringValue(value: unknown): string {
52
+ return typeof value === 'string' ? value : value == null ? '' : String(value);
53
+ }
54
+
55
+ function safeRemoteUrl(value: string): string {
56
+ try {
57
+ const url = new URL(value);
58
+ return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '';
59
+ } catch {
60
+ return '';
61
+ }
62
+ }
63
+ </script>
64
+
65
+ <template>
66
+ <section v-if="isIndex" class="bundle-lead">
67
+ <p class="bundle-lead__eyebrow">OPEN KNOWLEDGE FORMAT · 0.1</p>
68
+ <h1>{{ site.title }}</h1>
69
+ <p>
70
+ {{ site.description }} Every page below is both human-readable documentation and an
71
+ agent-readable OKF concept.
72
+ </p>
73
+ <div class="bundle-lead__actions">
74
+ <a v-if="startConcept" class="bundle-lead__primary" :href="withBase(startConcept.link)"
75
+ >Start with {{ startConcept.text }}</a
76
+ >
77
+ <a :href="withBase('/okf/index.md')">Read the raw bundle</a>
78
+ </div>
79
+ </section>
80
+
81
+ <aside v-else-if="type" class="concept-meta" aria-label="OKF concept metadata">
82
+ <div class="concept-meta__topline">
83
+ <span class="concept-meta__label">OKF concept</span>
84
+ <span class="concept-meta__type">{{ type }}</span>
85
+ </div>
86
+ <p v-if="description" class="concept-meta__description">{{ description }}</p>
87
+ <div v-if="tags.length || resource || timestamp" class="concept-meta__details">
88
+ <div v-if="tags.length" class="concept-meta__tags" aria-label="Tags">
89
+ <span v-for="tag in tags" :key="tag">{{ tag }}</span>
90
+ </div>
91
+ <a
92
+ v-if="resourceUrl"
93
+ class="concept-meta__resource"
94
+ :href="resourceUrl"
95
+ rel="noreferrer"
96
+ target="_blank"
97
+ >Canonical resource ↗</a
98
+ >
99
+ <code v-else-if="resource" class="concept-meta__resource-text">{{ resource }}</code>
100
+ <time v-if="timestamp" :datetime="timestamp">Updated {{ timestamp }}</time>
101
+ </div>
102
+ <div v-if="sources.length" class="concept-meta__sources">
103
+ <span class="concept-meta__sources-label">Sources</span>
104
+ <ul>
105
+ <li v-for="source in sources" :key="source.path">
106
+ <a
107
+ v-if="source.url"
108
+ :href="source.url"
109
+ rel="noreferrer"
110
+ target="_blank"
111
+ class="concept-meta__source-link"
112
+ ><code>{{ source.path }}</code></a
113
+ >
114
+ <code v-else>{{ source.path }}</code>
115
+ <span v-if="source.symbols.length" class="concept-meta__symbols">
116
+ <code v-for="symbol in source.symbols" :key="symbol">{{ symbol }}</code>
117
+ </span>
118
+ </li>
119
+ </ul>
120
+ </div>
121
+ </aside>
122
+ </template>
@@ -0,0 +1,12 @@
1
+ import { h } from 'vue';
2
+ import DefaultTheme from 'vitepress/theme';
3
+ import PageLead from './PageLead.vue';
4
+ import './style.css';
5
+
6
+ export default {
7
+ extends: DefaultTheme,
8
+ Layout: () =>
9
+ h(DefaultTheme.Layout, null, {
10
+ 'doc-before': () => h(PageLead),
11
+ }),
12
+ };