@ulysses-ai/create-workspace 0.13.0-beta.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/bin/create.mjs +79 -0
  4. package/lib/git.mjs +26 -0
  5. package/lib/init.mjs +129 -0
  6. package/lib/payload.mjs +44 -0
  7. package/lib/prompts.mjs +113 -0
  8. package/lib/scaffold.mjs +84 -0
  9. package/lib/upgrade.mjs +42 -0
  10. package/package.json +43 -0
  11. package/template/.claude/agents/aside-researcher.md +48 -0
  12. package/template/.claude/agents/implementer.md +39 -0
  13. package/template/.claude/agents/researcher.md +40 -0
  14. package/template/.claude/agents/reviewer.md +47 -0
  15. package/template/.claude/hooks/_utils.mjs +196 -0
  16. package/template/.claude/hooks/_utils.test.mjs +99 -0
  17. package/template/.claude/hooks/post-compact.mjs +7 -0
  18. package/template/.claude/hooks/pre-compact.mjs +34 -0
  19. package/template/.claude/hooks/repo-write-detection.mjs +107 -0
  20. package/template/.claude/hooks/session-end.mjs +91 -0
  21. package/template/.claude/hooks/session-start.mjs +150 -0
  22. package/template/.claude/hooks/subagent-start.mjs +44 -0
  23. package/template/.claude/hooks/workspace-update-check.mjs +42 -0
  24. package/template/.claude/hooks/worktree-create.mjs +53 -0
  25. package/template/.claude/lib/session-frontmatter.mjs +265 -0
  26. package/template/.claude/lib/session-frontmatter.test.mjs +242 -0
  27. package/template/.claude/recipes/migrate-from-notion.md +120 -0
  28. package/template/.claude/rules/agent-rules.md.skip +32 -0
  29. package/template/.claude/rules/cloud-infrastructure.md.skip +15 -0
  30. package/template/.claude/rules/coherent-revisions.md +24 -0
  31. package/template/.claude/rules/documentation.md.skip +13 -0
  32. package/template/.claude/rules/git-conventions.md +34 -0
  33. package/template/.claude/rules/honest-pushback.md +56 -0
  34. package/template/.claude/rules/local-dev-environment.md.skip +60 -0
  35. package/template/.claude/rules/memory-guidance.md +26 -0
  36. package/template/.claude/rules/product-integrity.md.skip +24 -0
  37. package/template/.claude/rules/scope-guard.md.skip +22 -0
  38. package/template/.claude/rules/superpowers-workflow.md.skip +22 -0
  39. package/template/.claude/rules/token-economics.md.skip +31 -0
  40. package/template/.claude/rules/work-item-tracking.md +90 -0
  41. package/template/.claude/rules/workspace-structure.md +69 -0
  42. package/template/.claude/scripts/add-repo-to-session.mjs +78 -0
  43. package/template/.claude/scripts/cleanup-work-session.mjs +108 -0
  44. package/template/.claude/scripts/create-work-session.mjs +124 -0
  45. package/template/.claude/scripts/migrate-open-work.mjs +91 -0
  46. package/template/.claude/scripts/migrate-session-layout.mjs +236 -0
  47. package/template/.claude/scripts/migrate-session-layout.test.mjs +144 -0
  48. package/template/.claude/scripts/trackers/github-issues.mjs +170 -0
  49. package/template/.claude/scripts/trackers/github-issues.test.mjs +190 -0
  50. package/template/.claude/scripts/trackers/interface.mjs +25 -0
  51. package/template/.claude/scripts/trackers/interface.test.mjs +40 -0
  52. package/template/.claude/settings.json +107 -0
  53. package/template/.claude/skills/aside/SKILL.md +125 -0
  54. package/template/.claude/skills/braindump/SKILL.md +96 -0
  55. package/template/.claude/skills/build-docs-site/SKILL.md +323 -0
  56. package/template/.claude/skills/build-docs-site/checklists/framing.md +221 -0
  57. package/template/.claude/skills/build-docs-site/checklists/pitfalls.md +228 -0
  58. package/template/.claude/skills/build-docs-site/checklists/review.md +130 -0
  59. package/template/.claude/skills/build-docs-site/scripts/bulk-fill-migration.py +393 -0
  60. package/template/.claude/skills/build-docs-site/scripts/forbidden-word-grep.mjs +159 -0
  61. package/template/.claude/skills/build-docs-site/scripts/leak-grep.mjs +328 -0
  62. package/template/.claude/skills/build-docs-site/templates/custom.css.tmpl +212 -0
  63. package/template/.claude/skills/build-docs-site/templates/docusaurus.config.ts.tmpl +95 -0
  64. package/template/.claude/skills/build-docs-site/templates/primitives/Arrow.tsx +87 -0
  65. package/template/.claude/skills/build-docs-site/templates/primitives/Box.tsx +90 -0
  66. package/template/.claude/skills/build-docs-site/templates/primitives/DiagramContainer.tsx +46 -0
  67. package/template/.claude/skills/build-docs-site/templates/primitives/Region.tsx +68 -0
  68. package/template/.claude/skills/build-docs-site/templates/primitives/SectionTitle.tsx +42 -0
  69. package/template/.claude/skills/build-docs-site/templates/primitives/tokens.ts +67 -0
  70. package/template/.claude/skills/build-docs-site/templates/sidebars.ts.tmpl +89 -0
  71. package/template/.claude/skills/build-docs-site/templates/spec.md.tmpl +119 -0
  72. package/template/.claude/skills/complete-work/SKILL.md +369 -0
  73. package/template/.claude/skills/handoff/SKILL.md +98 -0
  74. package/template/.claude/skills/maintenance/SKILL.md +116 -0
  75. package/template/.claude/skills/pause-work/SKILL.md +98 -0
  76. package/template/.claude/skills/promote/SKILL.md +77 -0
  77. package/template/.claude/skills/release/SKILL.md +126 -0
  78. package/template/.claude/skills/setup-tracker/SKILL.md +117 -0
  79. package/template/.claude/skills/start-work/SKILL.md +234 -0
  80. package/template/.claude/skills/sync-work/SKILL.md +73 -0
  81. package/template/.claude/skills/workspace-init/SKILL.md +420 -0
  82. package/template/.claude/skills/workspace-update/SKILL.md +108 -0
  83. package/template/.mcp.json +12 -0
  84. package/template/CLAUDE.md.tmpl +32 -0
  85. package/template/_gitignore +28 -0
  86. package/template/workspace.json.tmpl +15 -0
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * leak-grep.mjs — implementation-detail leak detector for the build-docs-site skill.
4
+ *
5
+ * Scans a project's dependency manifests to derive a list of installed
6
+ * package names, then greps documentation content for any mention of
7
+ * those names. The list is project-accurate — no hardcoded master list.
8
+ *
9
+ * Manifests scanned:
10
+ * - package.json (Node)
11
+ * - pyproject.toml (Python)
12
+ * - requirements.txt (Python)
13
+ * - Cargo.toml (Rust)
14
+ * - Gemfile (Ruby)
15
+ * - go.mod (Go)
16
+ *
17
+ * Excludes Tech Stack appendix files (where implementation choices are
18
+ * the subject) by file path filter.
19
+ *
20
+ * Usage:
21
+ * node leak-grep.mjs <project-root> <docs-path> [--exclude path1,path2]
22
+ *
23
+ * Output: JSON to stdout
24
+ * {
25
+ * manifestsScanned: [...],
26
+ * packagesFound: [...],
27
+ * hits: [{file, line, term, context}],
28
+ * excluded: [...]
29
+ * }
30
+ */
31
+
32
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
33
+ import { join, relative, basename } from 'node:path';
34
+
35
+ // ---------- CLI parsing ----------
36
+
37
+ const args = process.argv.slice(2);
38
+ if (args.length < 2) {
39
+ console.error('Usage: leak-grep.mjs <project-root> <docs-path> [--exclude path1,path2]');
40
+ process.exit(1);
41
+ }
42
+
43
+ const projectRoot = args[0];
44
+ const docsPath = args[1];
45
+ const excludeFlag = args.indexOf('--exclude');
46
+ const userExcludes = excludeFlag >= 0 && args[excludeFlag + 1]
47
+ ? args[excludeFlag + 1].split(',')
48
+ : [];
49
+
50
+ // Default excludes — the Tech Stack appendix is where implementation
51
+ // details are the subject. Match common naming conventions.
52
+ const defaultExcludes = [
53
+ 'tech-stack',
54
+ 'appendix-tech-stack',
55
+ 'appendix/tech-stack',
56
+ 'technology',
57
+ 'stack',
58
+ ];
59
+ const excludes = [...defaultExcludes, ...userExcludes];
60
+
61
+ // ---------- Manifest scanning ----------
62
+
63
+ const packageNames = new Set();
64
+ const manifestsScanned = [];
65
+
66
+ function scanManifests(dir) {
67
+ let entries;
68
+ try {
69
+ entries = readdirSync(dir);
70
+ } catch {
71
+ return;
72
+ }
73
+ for (const entry of entries) {
74
+ const full = join(dir, entry);
75
+ let stat;
76
+ try {
77
+ stat = statSync(full);
78
+ } catch {
79
+ continue;
80
+ }
81
+ if (stat.isDirectory()) {
82
+ // Skip common ignored directories
83
+ if (['node_modules', '.git', 'dist', 'build', '.next', '.venv', 'venv', '__pycache__'].includes(entry)) {
84
+ continue;
85
+ }
86
+ scanManifests(full);
87
+ } else {
88
+ const name = basename(full);
89
+ if (['package.json', 'pyproject.toml', 'requirements.txt', 'Cargo.toml', 'Gemfile', 'go.mod'].includes(name)) {
90
+ const extracted = extractPackages(name, full);
91
+ if (extracted.length > 0) {
92
+ manifestsScanned.push(relative(projectRoot, full));
93
+ extracted.forEach((p) => packageNames.add(p));
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ function extractPackages(manifestName, fullPath) {
101
+ let content;
102
+ try {
103
+ content = readFileSync(fullPath, 'utf8');
104
+ } catch {
105
+ return [];
106
+ }
107
+
108
+ switch (manifestName) {
109
+ case 'package.json':
110
+ return extractFromPackageJson(content);
111
+ case 'pyproject.toml':
112
+ return extractFromPyprojectToml(content);
113
+ case 'requirements.txt':
114
+ return extractFromRequirementsTxt(content);
115
+ case 'Cargo.toml':
116
+ return extractFromCargoToml(content);
117
+ case 'Gemfile':
118
+ return extractFromGemfile(content);
119
+ case 'go.mod':
120
+ return extractFromGoMod(content);
121
+ default:
122
+ return [];
123
+ }
124
+ }
125
+
126
+ function extractFromPackageJson(content) {
127
+ try {
128
+ const parsed = JSON.parse(content);
129
+ const all = {
130
+ ...parsed.dependencies,
131
+ ...parsed.devDependencies,
132
+ ...parsed.peerDependencies,
133
+ ...parsed.optionalDependencies,
134
+ };
135
+ return Object.keys(all).map((name) => {
136
+ // Strip scope: @scope/name → name (also keep both for matching)
137
+ const stripped = name.startsWith('@') ? name.split('/')[1] || name : name;
138
+ return stripped;
139
+ });
140
+ } catch {
141
+ return [];
142
+ }
143
+ }
144
+
145
+ function extractFromPyprojectToml(content) {
146
+ // Naive TOML parsing — look for dependency lines like:
147
+ // name = "..." or "package-name>=1.0" or package-name = "1.0"
148
+ const names = new Set();
149
+ const lines = content.split('\n');
150
+ let inDeps = false;
151
+ for (const line of lines) {
152
+ const trimmed = line.trim();
153
+ if (trimmed.startsWith('[')) {
154
+ inDeps = /\[(tool\.poetry\.|project\.|tool\.pdm\.)?(dev-)?dependencies\]/.test(trimmed);
155
+ continue;
156
+ }
157
+ if (inDeps) {
158
+ // poetry style: name = "1.0" or {version = "..."}
159
+ const poetryMatch = trimmed.match(/^([a-zA-Z0-9][a-zA-Z0-9_-]*)\s*=/);
160
+ if (poetryMatch) names.add(poetryMatch[1]);
161
+ // pep621 style in dependencies array: "name>=1.0",
162
+ const pep621Match = trimmed.match(/^["']([a-zA-Z0-9][a-zA-Z0-9_-]*)/);
163
+ if (pep621Match) names.add(pep621Match[1]);
164
+ }
165
+ }
166
+ return [...names];
167
+ }
168
+
169
+ function extractFromRequirementsTxt(content) {
170
+ const names = [];
171
+ for (const line of content.split('\n')) {
172
+ const trimmed = line.trim();
173
+ if (!trimmed || trimmed.startsWith('#')) continue;
174
+ const match = trimmed.match(/^([a-zA-Z0-9][a-zA-Z0-9_-]*)/);
175
+ if (match) names.push(match[1]);
176
+ }
177
+ return names;
178
+ }
179
+
180
+ function extractFromCargoToml(content) {
181
+ const names = new Set();
182
+ const lines = content.split('\n');
183
+ let inDeps = false;
184
+ for (const line of lines) {
185
+ const trimmed = line.trim();
186
+ if (trimmed.startsWith('[')) {
187
+ inDeps = /\[(dev-|build-)?dependencies\]/.test(trimmed);
188
+ continue;
189
+ }
190
+ if (inDeps) {
191
+ const match = trimmed.match(/^([a-zA-Z0-9][a-zA-Z0-9_-]*)\s*=/);
192
+ if (match) names.add(match[1]);
193
+ }
194
+ }
195
+ return [...names];
196
+ }
197
+
198
+ function extractFromGemfile(content) {
199
+ const names = [];
200
+ for (const line of content.split('\n')) {
201
+ const match = line.match(/^\s*gem\s+["']([a-zA-Z0-9][a-zA-Z0-9_-]*)["']/);
202
+ if (match) names.push(match[1]);
203
+ }
204
+ return names;
205
+ }
206
+
207
+ function extractFromGoMod(content) {
208
+ const names = new Set();
209
+ const lines = content.split('\n');
210
+ let inRequire = false;
211
+ for (const line of lines) {
212
+ const trimmed = line.trim();
213
+ if (trimmed.startsWith('require (')) {
214
+ inRequire = true;
215
+ continue;
216
+ }
217
+ if (inRequire && trimmed === ')') {
218
+ inRequire = false;
219
+ continue;
220
+ }
221
+ const lineToCheck = trimmed.startsWith('require ') ? trimmed.replace(/^require\s+/, '') : (inRequire ? trimmed : null);
222
+ if (lineToCheck) {
223
+ // last path segment as the package "name"
224
+ const match = lineToCheck.match(/^[a-zA-Z0-9./_-]+/);
225
+ if (match) {
226
+ const segments = match[0].split('/');
227
+ const last = segments[segments.length - 1];
228
+ if (last) names.add(last);
229
+ }
230
+ }
231
+ }
232
+ return [...names];
233
+ }
234
+
235
+ // ---------- Doc walking and grepping ----------
236
+
237
+ const hits = [];
238
+ const excludedFiles = [];
239
+
240
+ function isExcluded(filePath) {
241
+ const rel = relative(docsPath, filePath).replace(/\\/g, '/');
242
+ return excludes.some((e) => rel.includes(e));
243
+ }
244
+
245
+ function walkDocs(dir) {
246
+ let entries;
247
+ try {
248
+ entries = readdirSync(dir);
249
+ } catch {
250
+ return;
251
+ }
252
+ for (const entry of entries) {
253
+ const full = join(dir, entry);
254
+ let stat;
255
+ try {
256
+ stat = statSync(full);
257
+ } catch {
258
+ continue;
259
+ }
260
+ if (stat.isDirectory()) {
261
+ walkDocs(full);
262
+ } else if (/\.(md|mdx)$/.test(entry)) {
263
+ if (isExcluded(full)) {
264
+ excludedFiles.push(relative(projectRoot, full));
265
+ continue;
266
+ }
267
+ grepFile(full);
268
+ }
269
+ }
270
+ }
271
+
272
+ function grepFile(filePath) {
273
+ let content;
274
+ try {
275
+ content = readFileSync(filePath, 'utf8');
276
+ } catch {
277
+ return;
278
+ }
279
+ const lines = content.split('\n');
280
+ const relPath = relative(projectRoot, filePath);
281
+
282
+ let inCodeBlock = false;
283
+ for (let i = 0; i < lines.length; i++) {
284
+ const line = lines[i];
285
+ if (line.trim().startsWith('```')) {
286
+ inCodeBlock = !inCodeBlock;
287
+ continue;
288
+ }
289
+ if (inCodeBlock) continue; // Don't flag terms in code blocks
290
+
291
+ for (const pkg of packageNames) {
292
+ // Word boundary match, case-sensitive
293
+ // Skip very short package names (< 3 chars) to reduce noise
294
+ if (pkg.length < 3) continue;
295
+ const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
296
+ const regex = new RegExp(`\\b${escaped}\\b`);
297
+ if (regex.test(line)) {
298
+ hits.push({
299
+ file: relPath,
300
+ line: i + 1,
301
+ term: pkg,
302
+ context: line.trim().slice(0, 200),
303
+ });
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ // ---------- Run ----------
310
+
311
+ scanManifests(projectRoot);
312
+ walkDocs(docsPath);
313
+
314
+ const result = {
315
+ manifestsScanned,
316
+ packagesFound: [...packageNames].sort(),
317
+ excluded: excludedFiles,
318
+ hits,
319
+ summary: {
320
+ manifestCount: manifestsScanned.length,
321
+ packageCount: packageNames.size,
322
+ excludedFileCount: excludedFiles.length,
323
+ hitCount: hits.length,
324
+ },
325
+ };
326
+
327
+ console.log(JSON.stringify(result, null, 2));
328
+ process.exit(hits.length > 0 ? 1 : 0);
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Documentation site custom CSS.
3
+ *
4
+ * This file is a template — the build-docs-site skill fills in the
5
+ * brand placeholders (marked with {{...}}) with values from the
6
+ * Phase 1 brand identity question. After substitution the file is a
7
+ * valid Docusaurus custom.css.
8
+ *
9
+ * Sections:
10
+ * 1. Brand fonts (imports)
11
+ * 2. Infima theme overrides (light)
12
+ * 3. Infima theme overrides (dark)
13
+ * 4. Diagram tokens (--dx-*)
14
+ * 5. Diagram class layer (.dx-fill-* / .dx-stroke-*)
15
+ * 6. Diagram container styling
16
+ * 7. Typography polish (justified body, heading spacing, column width)
17
+ */
18
+
19
+ /* ============================================================
20
+ * 1. Brand fonts
21
+ * Replace with @import or @font-face declarations for your fonts.
22
+ * Example: @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
23
+ * ============================================================ */
24
+ {{BRAND_FONTS_IMPORT}}
25
+
26
+ /* ============================================================
27
+ * 2. Infima theme overrides (light mode)
28
+ * ============================================================ */
29
+ :root {
30
+ --ifm-color-primary: {{BRAND_PRIMARY}};
31
+ --ifm-color-primary-dark: {{BRAND_PRIMARY_DARK}};
32
+ --ifm-color-primary-darker: {{BRAND_PRIMARY_DARKER}};
33
+ --ifm-color-primary-darkest: {{BRAND_PRIMARY_DARKEST}};
34
+ --ifm-color-primary-light: {{BRAND_PRIMARY_LIGHT}};
35
+ --ifm-color-primary-lighter: {{BRAND_PRIMARY_LIGHTER}};
36
+ --ifm-color-primary-lightest: {{BRAND_PRIMARY_LIGHTEST}};
37
+
38
+ --ifm-background-color: {{BRAND_BACKGROUND}};
39
+ --ifm-font-color-base: {{BRAND_TEXT}};
40
+
41
+ --ifm-font-family-base: {{BRAND_FONT_BODY}}, system-ui, -apple-system, sans-serif;
42
+ --ifm-heading-font-family: {{BRAND_FONT_HEADING}}, system-ui, -apple-system, sans-serif;
43
+ --ifm-font-family-monospace: {{BRAND_FONT_MONO}}, 'SF Mono', Consolas, monospace;
44
+
45
+ --ifm-code-font-size: 90%;
46
+ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
47
+ }
48
+
49
+ /* ============================================================
50
+ * 3. Infima theme overrides (dark mode)
51
+ * ============================================================ */
52
+ [data-theme='dark'] {
53
+ --ifm-color-primary: {{BRAND_PRIMARY_DARK_MODE}};
54
+ --ifm-color-primary-dark: {{BRAND_PRIMARY_DARK_MODE_DARK}};
55
+ --ifm-color-primary-darker: {{BRAND_PRIMARY_DARK_MODE_DARKER}};
56
+ --ifm-color-primary-darkest: {{BRAND_PRIMARY_DARK_MODE_DARKEST}};
57
+ --ifm-color-primary-light: {{BRAND_PRIMARY_DARK_MODE_LIGHT}};
58
+ --ifm-color-primary-lighter: {{BRAND_PRIMARY_DARK_MODE_LIGHTER}};
59
+ --ifm-color-primary-lightest: {{BRAND_PRIMARY_DARK_MODE_LIGHTEST}};
60
+
61
+ --ifm-background-color: {{BRAND_BACKGROUND_DARK}};
62
+ --ifm-font-color-base: {{BRAND_TEXT_DARK}};
63
+
64
+ --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.1);
65
+ }
66
+
67
+ /* ============================================================
68
+ * 4. Diagram tokens (--dx-*)
69
+ *
70
+ * Semantic colors used by the diagram primitives library.
71
+ * The class layer below resolves these for fill/stroke.
72
+ * Keep in sync with primitives/tokens.ts hex values.
73
+ * ============================================================ */
74
+ :root {
75
+ --dx-primary: {{BRAND_PRIMARY}};
76
+ --dx-accent: {{BRAND_ACCENT}};
77
+ --dx-surface: #FFFFFF;
78
+ --dx-surface-strong: #F2F2F2;
79
+ --dx-text: #1A1A1A;
80
+ --dx-text-muted: #666666;
81
+ --dx-stroke: #CCCCCC;
82
+ --dx-stroke-strong: #888888;
83
+ --dx-diagram-bg: {{BRAND_BACKGROUND}};
84
+ }
85
+
86
+ [data-theme='dark'] {
87
+ --dx-primary: {{BRAND_PRIMARY_DARK_MODE}};
88
+ --dx-accent: {{BRAND_ACCENT_DARK_MODE}};
89
+ --dx-surface: #1A1A1A;
90
+ --dx-surface-strong: #2A2A2A;
91
+ --dx-text: #EDEDED;
92
+ --dx-text-muted: #999999;
93
+ --dx-stroke: #444444;
94
+ --dx-stroke-strong: #777777;
95
+ --dx-diagram-bg: {{BRAND_BACKGROUND_DARK}};
96
+ }
97
+
98
+ /* ============================================================
99
+ * 5. Diagram class layer
100
+ *
101
+ * CSS variables in `fill="var(--x)"` or inline style do not paint
102
+ * reliably in Chromium's SVG pipeline. The variables resolve correctly
103
+ * through class selectors. Apply via className on SVG elements:
104
+ *
105
+ * <rect className="dx-fill-primary dx-stroke-stroke" />
106
+ * ============================================================ */
107
+ .dx-fill-primary { fill: var(--dx-primary); }
108
+ .dx-fill-accent { fill: var(--dx-accent); }
109
+ .dx-fill-surface { fill: var(--dx-surface); }
110
+ .dx-fill-surface-strong { fill: var(--dx-surface-strong); }
111
+ .dx-fill-text { fill: var(--dx-text); }
112
+ .dx-fill-text-muted { fill: var(--dx-text-muted); }
113
+ .dx-fill-stroke { fill: var(--dx-stroke); }
114
+ .dx-fill-stroke-strong { fill: var(--dx-stroke-strong); }
115
+
116
+ .dx-stroke-primary { stroke: var(--dx-primary); }
117
+ .dx-stroke-accent { stroke: var(--dx-accent); }
118
+ .dx-stroke-surface { stroke: var(--dx-surface); }
119
+ .dx-stroke-surface-strong { stroke: var(--dx-surface-strong); }
120
+ .dx-stroke-text { stroke: var(--dx-text); }
121
+ .dx-stroke-text-muted { stroke: var(--dx-text-muted); }
122
+ .dx-stroke-stroke { stroke: var(--dx-stroke); }
123
+ .dx-stroke-stroke-strong { stroke: var(--dx-stroke-strong); }
124
+
125
+ /* ============================================================
126
+ * 6. Diagram container styling
127
+ * ============================================================ */
128
+ .dx-diagram-container {
129
+ background: var(--dx-diagram-bg);
130
+ border-radius: 8px;
131
+ padding: 24px;
132
+ margin: 2rem 0;
133
+ border: 1px solid var(--dx-stroke);
134
+ }
135
+
136
+ .dx-diagram-container svg {
137
+ width: 100%;
138
+ height: auto;
139
+ display: block;
140
+ }
141
+
142
+ .dx-diagram-caption {
143
+ margin-top: 1rem;
144
+ font-size: 0.875rem;
145
+ color: var(--dx-text-muted);
146
+ text-align: center;
147
+ font-style: italic;
148
+ }
149
+
150
+ /* ============================================================
151
+ * 7. Typography polish
152
+ *
153
+ * Captured defaults that produce comfortable long-form reading.
154
+ * The chapter-length question in Phase 1 may inform whether you
155
+ * adjust these — these are the Linear/Together defaults.
156
+ * ============================================================ */
157
+
158
+ /* Justified body text with hyphenation */
159
+ .markdown p,
160
+ .markdown li {
161
+ text-align: justify;
162
+ hyphens: auto;
163
+ -webkit-hyphens: auto;
164
+ -ms-hyphens: auto;
165
+ }
166
+
167
+ /* Heading spacing — generous top, controlled bottom */
168
+ .markdown h2 {
169
+ margin-top: 3.5rem;
170
+ margin-bottom: 1.25rem;
171
+ }
172
+
173
+ .markdown h3 {
174
+ margin-top: 2.5rem;
175
+ margin-bottom: 1rem;
176
+ }
177
+
178
+ .markdown h4 {
179
+ margin-top: 2rem;
180
+ margin-bottom: 0.75rem;
181
+ }
182
+
183
+ /* Collapse spacing between adjacent headings (avoid double-stacking) */
184
+ .markdown h2 + h3,
185
+ .markdown h3 + h4 {
186
+ margin-top: 1rem;
187
+ }
188
+
189
+ /* Paragraph and list-item bottom spacing */
190
+ .markdown p {
191
+ margin-bottom: 1.25rem;
192
+ }
193
+
194
+ .markdown li {
195
+ margin-bottom: 0.4rem;
196
+ }
197
+
198
+ .markdown li:last-child {
199
+ margin-bottom: 1.25rem;
200
+ }
201
+
202
+ /* Content column width — wide enough for justified text to look even */
203
+ .markdown {
204
+ max-width: 760px;
205
+ }
206
+
207
+ /* Code blocks should not be justified */
208
+ .markdown pre,
209
+ .markdown pre code {
210
+ text-align: left;
211
+ hyphens: none;
212
+ }
@@ -0,0 +1,95 @@
1
+ import { themes as prismThemes } from 'prism-react-renderer';
2
+ import type { Config } from '@docusaurus/types';
3
+ import type * as Preset from '@docusaurus/preset-classic';
4
+
5
+ /**
6
+ * Docusaurus configuration for the documentation site.
7
+ * Generated by the build-docs-site skill from a template.
8
+ *
9
+ * Placeholders to substitute:
10
+ * {{SITE_TITLE}} — the site's display title
11
+ * {{SITE_TAGLINE}} — short tagline shown in the navbar/footer
12
+ * {{SITE_URL}} — production URL (e.g., https://docs.example.com)
13
+ * {{SITE_BASE_URL}} — base path (usually '/')
14
+ * {{ORG_NAME}} — GitHub org or owner
15
+ * {{REPO_NAME}} — GitHub repo name (for edit links)
16
+ * {{FAVICON_PATH}} — path to favicon under static/
17
+ * {{COPYRIGHT_OWNER}} — name shown in the footer copyright
18
+ */
19
+
20
+ const config: Config = {
21
+ title: '{{SITE_TITLE}}',
22
+ tagline: '{{SITE_TAGLINE}}',
23
+ favicon: '{{FAVICON_PATH}}',
24
+
25
+ url: '{{SITE_URL}}',
26
+ baseUrl: '{{SITE_BASE_URL}}',
27
+
28
+ organizationName: '{{ORG_NAME}}',
29
+ projectName: '{{REPO_NAME}}',
30
+
31
+ onBrokenLinks: 'throw',
32
+ onBrokenMarkdownLinks: 'warn',
33
+
34
+ i18n: {
35
+ defaultLocale: 'en',
36
+ locales: ['en'],
37
+ },
38
+
39
+ presets: [
40
+ [
41
+ 'classic',
42
+ {
43
+ docs: {
44
+ // Docs at the site root — no /docs/ prefix
45
+ routeBasePath: '/',
46
+ sidebarPath: './sidebars.ts',
47
+ editUrl: 'https://github.com/{{ORG_NAME}}/{{REPO_NAME}}/tree/main/',
48
+ },
49
+ // Blog disabled — this is a docs-only site
50
+ blog: false,
51
+ theme: {
52
+ customCss: './src/css/custom.css',
53
+ },
54
+ } satisfies Preset.Options,
55
+ ],
56
+ ],
57
+
58
+ themeConfig: {
59
+ colorMode: {
60
+ defaultMode: 'light',
61
+ disableSwitch: false,
62
+ respectPrefersColorScheme: true,
63
+ },
64
+ docs: {
65
+ sidebar: {
66
+ hideable: true,
67
+ autoCollapseCategories: true,
68
+ },
69
+ },
70
+ navbar: {
71
+ title: '{{SITE_TITLE}}',
72
+ logo: {
73
+ alt: '{{SITE_TITLE}} Logo',
74
+ src: '{{FAVICON_PATH}}',
75
+ },
76
+ items: [
77
+ {
78
+ href: 'https://github.com/{{ORG_NAME}}/{{REPO_NAME}}',
79
+ label: 'GitHub',
80
+ position: 'right',
81
+ },
82
+ ],
83
+ },
84
+ footer: {
85
+ style: 'dark',
86
+ copyright: `Copyright © ${new Date().getFullYear()} {{COPYRIGHT_OWNER}}.`,
87
+ },
88
+ prism: {
89
+ theme: prismThemes.github,
90
+ darkTheme: prismThemes.dracula,
91
+ },
92
+ } satisfies Preset.ThemeConfig,
93
+ };
94
+
95
+ export default config;