@lucasviola/specwiki 1.0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +226 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/generate.d.ts +5 -0
  8. package/dist/commands/generate.d.ts.map +1 -0
  9. package/dist/commands/generate.js +197 -0
  10. package/dist/commands/generate.js.map +1 -0
  11. package/dist/commands/init.d.ts +5 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +91 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/commands/open.d.ts +8 -0
  16. package/dist/commands/open.d.ts.map +1 -0
  17. package/dist/commands/open.js +150 -0
  18. package/dist/commands/open.js.map +1 -0
  19. package/dist/config/loader.d.ts +20 -0
  20. package/dist/config/loader.d.ts.map +1 -0
  21. package/dist/config/loader.js +143 -0
  22. package/dist/config/loader.js.map +1 -0
  23. package/dist/config/patterns.d.ts +9 -0
  24. package/dist/config/patterns.d.ts.map +1 -0
  25. package/dist/config/patterns.js +159 -0
  26. package/dist/config/patterns.js.map +1 -0
  27. package/dist/core/Logger.d.ts +8 -0
  28. package/dist/core/Logger.d.ts.map +1 -0
  29. package/dist/core/Logger.js +33 -0
  30. package/dist/core/Logger.js.map +1 -0
  31. package/dist/core/paths.d.ts +8 -0
  32. package/dist/core/paths.d.ts.map +1 -0
  33. package/dist/core/paths.js +45 -0
  34. package/dist/core/paths.js.map +1 -0
  35. package/dist/discover/specs.d.ts +11 -0
  36. package/dist/discover/specs.d.ts.map +1 -0
  37. package/dist/discover/specs.js +157 -0
  38. package/dist/discover/specs.js.map +1 -0
  39. package/dist/output/html/assets/highlight.css +101 -0
  40. package/dist/output/html/assets/search.js +368 -0
  41. package/dist/output/html/assets/specwiki.css +905 -0
  42. package/dist/output/html/renderer.d.ts +21 -0
  43. package/dist/output/html/renderer.d.ts.map +1 -0
  44. package/dist/output/html/renderer.js +203 -0
  45. package/dist/output/html/renderer.js.map +1 -0
  46. package/dist/output/html/search-index.d.ts +20 -0
  47. package/dist/output/html/search-index.d.ts.map +1 -0
  48. package/dist/output/html/search-index.js +46 -0
  49. package/dist/output/html/search-index.js.map +1 -0
  50. package/dist/output/html/templates/article.mustache +85 -0
  51. package/dist/output/html/templates/index.mustache +80 -0
  52. package/dist/output/html/templates/layout.mustache +205 -0
  53. package/dist/output/llms.d.ts +4 -0
  54. package/dist/output/llms.d.ts.map +1 -0
  55. package/dist/output/llms.js +44 -0
  56. package/dist/output/llms.js.map +1 -0
  57. package/dist/output/readme-index.d.ts +17 -0
  58. package/dist/output/readme-index.d.ts.map +1 -0
  59. package/dist/output/readme-index.js +71 -0
  60. package/dist/output/readme-index.js.map +1 -0
  61. package/dist/output/wiki.d.ts +11 -0
  62. package/dist/output/wiki.d.ts.map +1 -0
  63. package/dist/output/wiki.js +400 -0
  64. package/dist/output/wiki.js.map +1 -0
  65. package/dist/parse/markdown.d.ts +6 -0
  66. package/dist/parse/markdown.d.ts.map +1 -0
  67. package/dist/parse/markdown.js +225 -0
  68. package/dist/parse/markdown.js.map +1 -0
  69. package/dist/types.d.ts +96 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/package.json +67 -0
@@ -0,0 +1,21 @@
1
+ import type { WikiIndexMeta, WikiPage } from "../../types.js";
2
+ export interface HtmlRenderOptions {
3
+ includeSearch?: boolean;
4
+ searchIndexJson?: string;
5
+ }
6
+ export declare class HtmlRenderer {
7
+ private readonly layoutTemplate;
8
+ private readonly indexTemplate;
9
+ private readonly articleTemplate;
10
+ private constructor();
11
+ static create(): Promise<HtmlRenderer>;
12
+ renderIndex(pages: WikiPage[], indexMeta: WikiIndexMeta, renderOptions?: HtmlRenderOptions): string;
13
+ renderArticle(page: WikiPage, allPages: WikiPage[], contentHtml: string, renderOptions?: HtmlRenderOptions): string;
14
+ static bundleCss(): Promise<string>;
15
+ static bundledCssFilename(): string;
16
+ static highlightCssFilename(): string;
17
+ static readHighlightCss(): Promise<string>;
18
+ }
19
+ export declare function getHtmlRenderer(): Promise<HtmlRenderer>;
20
+ export declare function resetHtmlRendererCache(): void;
21
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/output/html/renderer.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAe,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA2C3E,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAQD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,OAAO;WAMM,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAS5C,WAAW,CACT,KAAK,EAAE,QAAQ,EAAE,EACjB,SAAS,EAAE,aAAa,EACxB,aAAa,GAAE,iBAAsB,GACpC,MAAM;IA+BT,aAAa,CACX,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,QAAQ,EAAE,EACpB,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,iBAAsB,GACpC,MAAM;WA8BI,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAWzC,MAAM,CAAC,kBAAkB,IAAI,MAAM;IAInC,MAAM,CAAC,oBAAoB,IAAI,MAAM;WAIxB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;CAIjD;AAmHD,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAK7D;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
@@ -0,0 +1,203 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { fileURLToPath } from "node:url";
5
+ import Mustache from "mustache";
6
+ import { CATEGORY_LABELS } from "../../config/patterns.js";
7
+ import { renderMarkdown } from "../../parse/markdown.js";
8
+ import { isReadmeFile } from "../readme-index.js";
9
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
10
+ const require = createRequire(import.meta.url);
11
+ const TEMPLATES_DIR = path.join(moduleDir, "templates");
12
+ const ASSETS_DIR = path.join(moduleDir, "assets");
13
+ const BUNDLED_CSS_FILENAME = "specwiki.css";
14
+ const HIGHLIGHT_CSS_FILENAME = "highlight.css";
15
+ export class HtmlRenderer {
16
+ layoutTemplate;
17
+ indexTemplate;
18
+ articleTemplate;
19
+ constructor(layout, index, article) {
20
+ this.layoutTemplate = layout;
21
+ this.indexTemplate = index;
22
+ this.articleTemplate = article;
23
+ }
24
+ static async create() {
25
+ const [layout, index, article] = await Promise.all([
26
+ fs.readFile(path.join(TEMPLATES_DIR, "layout.mustache"), "utf-8"),
27
+ fs.readFile(path.join(TEMPLATES_DIR, "index.mustache"), "utf-8"),
28
+ fs.readFile(path.join(TEMPLATES_DIR, "article.mustache"), "utf-8"),
29
+ ]);
30
+ return new HtmlRenderer(layout, index, article);
31
+ }
32
+ renderIndex(pages, indexMeta, renderOptions = {}) {
33
+ const categories = buildNavCategories(pages)
34
+ .filter((category) => categoryVisibleInIndex(pages, category.key))
35
+ .map((category) => {
36
+ const intro = indexMeta.categoryIntros.get(category.key);
37
+ return {
38
+ ...category,
39
+ hasIntro: Boolean(intro),
40
+ introHtml: intro ? renderMarkdown(intro.content) : "",
41
+ };
42
+ });
43
+ const pageCount = pages.length;
44
+ const allPages = buildAllPagesList(pages);
45
+ const hasRootIntro = Boolean(indexMeta.rootIntro);
46
+ const body = Mustache.render(this.indexTemplate, {
47
+ categories,
48
+ pageCount,
49
+ pageCountLabel: pageCount === 1 ? "spec file" : "spec files",
50
+ allPages,
51
+ hasRootIntro,
52
+ rootIntroHtml: hasRootIntro ? renderMarkdown(indexMeta.rootIntro) : "",
53
+ });
54
+ return Mustache.render(this.layoutTemplate, {
55
+ pageTitle: "Spec Wiki",
56
+ body,
57
+ includeHighlightCss: false,
58
+ includeSearch: Boolean(renderOptions.includeSearch),
59
+ searchIndexJson: renderOptions.searchIndexJson ?? "",
60
+ });
61
+ }
62
+ renderArticle(page, allPages, contentHtml, renderOptions = {}) {
63
+ validateArticlePage(page);
64
+ const categories = buildNavCategories(allPages, page.category);
65
+ const categoryLabel = categoryLabelFor(page.category);
66
+ const tocEntries = buildTocEntries(page.sections);
67
+ const breadcrumbs = buildBreadcrumbs(page, categoryLabel, allPages);
68
+ const body = Mustache.render(this.articleTemplate, {
69
+ categories,
70
+ breadcrumbs,
71
+ title: page.title,
72
+ categoryLabel,
73
+ sourcePath: page.sourcePath,
74
+ description: page.description,
75
+ hasDescription: Boolean(page.description),
76
+ content: contentHtml,
77
+ tocEntries,
78
+ hasToc: tocEntries.length > 0,
79
+ });
80
+ return Mustache.render(this.layoutTemplate, {
81
+ pageTitle: page.title,
82
+ body,
83
+ includeHighlightCss: true,
84
+ includeSearch: Boolean(renderOptions.includeSearch),
85
+ searchIndexJson: renderOptions.searchIndexJson ?? "",
86
+ });
87
+ }
88
+ static async bundleCss() {
89
+ const wikimediaPath = require.resolve("wikimedia-ui-base/wikimedia-ui-base.css");
90
+ const customPath = path.join(ASSETS_DIR, BUNDLED_CSS_FILENAME);
91
+ const [wikimedia, custom] = await Promise.all([
92
+ fs.readFile(wikimediaPath, "utf-8"),
93
+ fs.readFile(customPath, "utf-8"),
94
+ ]);
95
+ return `${wikimedia}\n\n${custom}`;
96
+ }
97
+ static bundledCssFilename() {
98
+ return BUNDLED_CSS_FILENAME;
99
+ }
100
+ static highlightCssFilename() {
101
+ return HIGHLIGHT_CSS_FILENAME;
102
+ }
103
+ static async readHighlightCss() {
104
+ const highlightPath = path.join(ASSETS_DIR, HIGHLIGHT_CSS_FILENAME);
105
+ return fs.readFile(highlightPath, "utf-8");
106
+ }
107
+ }
108
+ function categoryLabelFor(category) {
109
+ return CATEGORY_LABELS[category] ?? category;
110
+ }
111
+ function categoryAnchor(category) {
112
+ return `category-${category}`;
113
+ }
114
+ function buildAllPagesList(pages) {
115
+ return [...pages]
116
+ .sort((a, b) => a.title.localeCompare(b.title))
117
+ .map((page) => ({
118
+ title: page.title,
119
+ slug: page.slug,
120
+ href: `${page.slug}.html`,
121
+ }));
122
+ }
123
+ function categoryVisibleInIndex(pages, category) {
124
+ return pages.some((page) => page.category === category && !isReadmeFile(page.sourcePath));
125
+ }
126
+ function buildNavCategories(pages, activeCategory) {
127
+ const byCategory = new Map();
128
+ for (const page of pages) {
129
+ const list = byCategory.get(page.category) ?? [];
130
+ list.push(page);
131
+ byCategory.set(page.category, list);
132
+ }
133
+ const sortedKeys = [...byCategory.keys()].sort((a, b) => categoryLabelFor(a).localeCompare(categoryLabelFor(b)));
134
+ return sortedKeys
135
+ .filter((key) => categoryVisibleInIndex(pages, key))
136
+ .map((key) => ({
137
+ key,
138
+ label: categoryLabelFor(key),
139
+ anchor: categoryAnchor(key),
140
+ active: key === activeCategory,
141
+ open: key === activeCategory,
142
+ pageCount: (byCategory.get(key) ?? []).length,
143
+ collapsible: (byCategory.get(key) ?? []).length > 1,
144
+ pages: (byCategory.get(key) ?? []).map((page) => ({
145
+ title: page.title,
146
+ slug: page.slug,
147
+ href: `${page.slug}.html`,
148
+ sourcePath: page.sourcePath,
149
+ })),
150
+ }));
151
+ }
152
+ function buildTocEntries(sections) {
153
+ return sections
154
+ .filter((section) => section.level >= 2 && section.level <= 6)
155
+ .map((section) => ({
156
+ title: section.title,
157
+ href: `#${section.anchor}`,
158
+ level: section.level,
159
+ }));
160
+ }
161
+ function buildBreadcrumbs(page, categoryLabel, allPages) {
162
+ const segments = [
163
+ { label: "Main Page", href: "index.html", first: true },
164
+ ];
165
+ if (categoryVisibleInIndex(allPages, page.category)) {
166
+ segments.push({
167
+ label: categoryLabel,
168
+ href: `index.html#${categoryAnchor(page.category)}`,
169
+ first: false,
170
+ });
171
+ }
172
+ else {
173
+ segments.push({ label: categoryLabel, first: false });
174
+ }
175
+ segments.push({ label: page.title, first: false });
176
+ return segments;
177
+ }
178
+ function validateArticlePage(page) {
179
+ const missing = [];
180
+ if (!page.title) {
181
+ missing.push("title");
182
+ }
183
+ if (!page.category) {
184
+ missing.push("category");
185
+ }
186
+ if (!page.sourcePath) {
187
+ missing.push("sourcePath");
188
+ }
189
+ if (missing.length > 0) {
190
+ throw new Error(`Missing required template fields: ${missing.join(", ")}`);
191
+ }
192
+ }
193
+ let cachedRenderer = null;
194
+ export async function getHtmlRenderer() {
195
+ if (!cachedRenderer) {
196
+ cachedRenderer = await HtmlRenderer.create();
197
+ }
198
+ return cachedRenderer;
199
+ }
200
+ export function resetHtmlRendererCache() {
201
+ cachedRenderer = null;
202
+ }
203
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../../src/output/html/renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { WikiPage } from "../../types.js";
2
+ export declare const SEARCH_INDEX_VERSION = 1;
3
+ export declare const BODY_EXCERPT_MAX = 2000;
4
+ export interface SearchIndexDocument {
5
+ slug: string;
6
+ title: string;
7
+ category: string;
8
+ categoryLabel: string;
9
+ description: string;
10
+ body: string;
11
+ }
12
+ export interface SearchIndex {
13
+ version: number;
14
+ documents: SearchIndexDocument[];
15
+ }
16
+ export declare function stripMarkdown(text: string): string;
17
+ export declare function extractBodyExcerpt(content: string, maxLength?: number): string;
18
+ export declare function buildSearchIndex(pages: WikiPage[]): SearchIndex;
19
+ export declare function serializeSearchIndexForInlineScript(index: SearchIndex): string;
20
+ //# sourceMappingURL=search-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index.d.ts","sourceRoot":"","sources":["../../../src/output/html/search-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,mBAAmB,EAAE,CAAC;CAClC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBlD;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,SAAmB,GAC3B,MAAM,CAMR;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,WAAW,CAY/D;AAED,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,WAAW,GACjB,MAAM,CAER"}
@@ -0,0 +1,46 @@
1
+ import { CATEGORY_LABELS } from "../../config/patterns.js";
2
+ export const SEARCH_INDEX_VERSION = 1;
3
+ export const BODY_EXCERPT_MAX = 2000;
4
+ export function stripMarkdown(text) {
5
+ return text
6
+ .replace(/^---[\s\S]*?---\n/m, "")
7
+ .replace(/```[\s\S]*?```/g, " ")
8
+ .replace(/`([^`]+)`/g, "$1")
9
+ .replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
10
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
11
+ .replace(/^#{1,6}\s+/gm, "")
12
+ .replace(/^\s*[-*+]\s+/gm, "")
13
+ .replace(/^\s*\d+\.\s+/gm, "")
14
+ .replace(/^\s*>+\s?/gm, "")
15
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
16
+ .replace(/\*([^*]+)\*/g, "$1")
17
+ .replace(/__([^_]+)__/g, "$1")
18
+ .replace(/_([^_]+)_/g, "$1")
19
+ .replace(/<\/?[^>]+(>|$)/g, " ")
20
+ .replace(/\s+/g, " ")
21
+ .trim();
22
+ }
23
+ export function extractBodyExcerpt(content, maxLength = BODY_EXCERPT_MAX) {
24
+ const plain = stripMarkdown(content);
25
+ if (plain.length <= maxLength) {
26
+ return plain;
27
+ }
28
+ return plain.slice(0, maxLength);
29
+ }
30
+ export function buildSearchIndex(pages) {
31
+ return {
32
+ version: SEARCH_INDEX_VERSION,
33
+ documents: pages.map((page) => ({
34
+ slug: page.slug,
35
+ title: page.title,
36
+ category: page.category,
37
+ categoryLabel: CATEGORY_LABELS[page.category] ?? page.category,
38
+ description: page.description,
39
+ body: extractBodyExcerpt(page.content),
40
+ })),
41
+ };
42
+ }
43
+ export function serializeSearchIndexForInlineScript(index) {
44
+ return JSON.stringify(index).replace(/</g, "\\u003c");
45
+ }
46
+ //# sourceMappingURL=search-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index.js","sourceRoot":"","sources":["../../../src/output/html/search-index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAgBrC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI;SACR,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SACjC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC;SAC3B,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC;SACrC,OAAO,CAAC,wBAAwB,EAAE,IAAI,CAAC;SACvC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,kBAAkB,EAAE,IAAI,CAAC;SACjC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC;SAC7B,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC;SAC7B,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC;SAC3B,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,SAAS,GAAG,gBAAgB;IAE5B,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO;QACL,OAAO,EAAE,oBAAoB;QAC7B,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,aAAa,EAAE,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ;YAC9D,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;SACvC,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mCAAmC,CACjD,KAAkB;IAElB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,85 @@
1
+ <div class="specwiki-layout specwiki-layout-article">
2
+ <aside id="specwiki-nav-drawer" class="specwiki-nav-drawer">
3
+ <button
4
+ type="button"
5
+ id="specwiki-nav-close"
6
+ hidden
7
+ class="specwiki-nav-close"
8
+ aria-label="Close category navigation"
9
+ >
10
+ <span aria-hidden="true">×</span>
11
+ </button>
12
+ <nav class="category-nav" aria-label="Categories">
13
+ {{#categories}}
14
+ {{#collapsible}}
15
+ <details class="category-nav-group{{#active}} category-nav-active{{/active}}" data-category="{{key}}" {{#open}}open{{/open}}>
16
+ <summary class="category-nav-summary">
17
+ <a href="index.html#{{anchor}}" class="category-nav-heading">{{label}}</a>
18
+ <span class="category-nav-count" aria-label="{{pageCount}} pages">{{pageCount}}</span>
19
+ </summary>
20
+ <ul class="category-nav-pages">
21
+ {{#pages}}
22
+ <li><a href="{{href}}">{{title}}</a></li>
23
+ {{/pages}}
24
+ </ul>
25
+ </details>
26
+ {{/collapsible}}
27
+ {{^collapsible}}
28
+ <div class="category-nav-group{{#active}} category-nav-active{{/active}}" data-category="{{key}}">
29
+ <a href="index.html#{{anchor}}" class="category-nav-heading">{{label}}</a>
30
+ <ul class="category-nav-pages">
31
+ {{#pages}}
32
+ <li><a href="{{href}}">{{title}}</a></li>
33
+ {{/pages}}
34
+ </ul>
35
+ </div>
36
+ {{/collapsible}}
37
+ {{/categories}}
38
+ </nav>
39
+ </aside>
40
+ <div class="specwiki-center">
41
+ <nav class="breadcrumb" aria-label="Breadcrumb">
42
+ {{#breadcrumbs}}
43
+ {{^first}} › {{/first}}
44
+ {{#href}}<a href="{{href}}">{{label}}</a>{{/href}}
45
+ {{^href}}<span aria-current="page">{{label}}</span>{{/href}}
46
+ {{/breadcrumbs}}
47
+ </nav>
48
+ <aside class="infobox">
49
+ <h2 class="infobox-title">{{title}}</h2>
50
+ <table class="infobox-table">
51
+ <tbody>
52
+ <tr>
53
+ <th scope="row">Category</th>
54
+ <td>{{categoryLabel}}</td>
55
+ </tr>
56
+ <tr>
57
+ <th scope="row">Source</th>
58
+ <td><code>{{sourcePath}}</code></td>
59
+ </tr>
60
+ {{#hasDescription}}
61
+ <tr>
62
+ <th scope="row">Summary</th>
63
+ <td>{{description}}</td>
64
+ </tr>
65
+ {{/hasDescription}}
66
+ </tbody>
67
+ </table>
68
+ </aside>
69
+ <main id="content" class="specwiki-article-body">
70
+ <div class="mw-parser-output">
71
+ {{{content}}}
72
+ </div>
73
+ </main>
74
+ </div>
75
+ {{#hasToc}}
76
+ <nav class="toc" aria-label="Table of contents">
77
+ <h2 class="toc-heading">Contents</h2>
78
+ <ul class="toc-list">
79
+ {{#tocEntries}}
80
+ <li class="toc-level-{{level}}"><a href="{{href}}">{{title}}</a></li>
81
+ {{/tocEntries}}
82
+ </ul>
83
+ </nav>
84
+ {{/hasToc}}
85
+ </div>
@@ -0,0 +1,80 @@
1
+ <div class="specwiki-layout">
2
+ <aside id="specwiki-nav-drawer" class="specwiki-nav-drawer">
3
+ <button
4
+ type="button"
5
+ id="specwiki-nav-close"
6
+ hidden
7
+ class="specwiki-nav-close"
8
+ aria-label="Close category navigation"
9
+ >
10
+ <span aria-hidden="true">×</span>
11
+ </button>
12
+ <nav class="category-nav" aria-label="Categories">
13
+ {{#categories}}
14
+ {{#collapsible}}
15
+ <details class="category-nav-group" data-category="{{key}}" {{#open}}open{{/open}}>
16
+ <summary class="category-nav-summary">
17
+ <a href="index.html#{{anchor}}" class="category-nav-heading">{{label}}</a>
18
+ <span class="category-nav-count" aria-label="{{pageCount}} pages">{{pageCount}}</span>
19
+ </summary>
20
+ <ul class="category-nav-pages">
21
+ {{#pages}}
22
+ <li><a href="{{href}}">{{title}}</a></li>
23
+ {{/pages}}
24
+ </ul>
25
+ </details>
26
+ {{/collapsible}}
27
+ {{^collapsible}}
28
+ <div class="category-nav-group" data-category="{{key}}">
29
+ <a href="index.html#{{anchor}}" class="category-nav-heading">{{label}}</a>
30
+ <ul class="category-nav-pages">
31
+ {{#pages}}
32
+ <li><a href="{{href}}">{{title}}</a></li>
33
+ {{/pages}}
34
+ </ul>
35
+ </div>
36
+ {{/collapsible}}
37
+ {{/categories}}
38
+ </nav>
39
+ </aside>
40
+ <div class="specwiki-center">
41
+ <article class="specwiki-portal" id="content">
42
+ <h1>Main Page</h1>
43
+ {{#hasRootIntro}}
44
+ <div class="specwiki-portal-intro mw-parser-output">
45
+ {{{rootIntroHtml}}}
46
+ </div>
47
+ {{/hasRootIntro}}
48
+ {{^hasRootIntro}}
49
+ <p class="specwiki-portal-intro">
50
+ Structured documentation generated from AI specs, agent instructions,
51
+ and spec-driven development files in this project.
52
+ </p>
53
+ {{/hasRootIntro}}
54
+ <p><strong>{{pageCount}}</strong> {{pageCountLabel}} indexed.</p>
55
+ {{#categories}}
56
+ <section id="{{anchor}}" class="specwiki-category-section">
57
+ <h2>{{label}}</h2>
58
+ {{#hasIntro}}
59
+ <div class="specwiki-category-intro mw-parser-output">
60
+ {{{introHtml}}}
61
+ </div>
62
+ {{/hasIntro}}
63
+ <ul class="specwiki-portal-pages">
64
+ {{#pages}}
65
+ <li><a href="{{href}}">{{title}}</a> — <code>{{sourcePath}}</code></li>
66
+ {{/pages}}
67
+ </ul>
68
+ </section>
69
+ {{/categories}}
70
+ <section id="all-pages" class="specwiki-all-pages">
71
+ <h2>All pages</h2>
72
+ <ul class="specwiki-portal-pages">
73
+ {{#allPages}}
74
+ <li><a href="{{href}}">{{title}}</a></li>
75
+ {{/allPages}}
76
+ </ul>
77
+ </section>
78
+ </article>
79
+ </div>
80
+ </div>