This is page 23 of 29. Use http://codebase.md/tosin2013/documcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .dockerignore
├── .eslintignore
├── .eslintrc.json
├── .github
│ ├── agents
│ │ ├── documcp-ast.md
│ │ ├── documcp-deploy.md
│ │ ├── documcp-memory.md
│ │ ├── documcp-test.md
│ │ └── documcp-tool.md
│ ├── copilot-instructions.md
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── automated-changelog.md
│ │ ├── bug_report.md
│ │ ├── bug_report.yml
│ │ ├── documentation_issue.md
│ │ ├── feature_request.md
│ │ ├── feature_request.yml
│ │ ├── npm-publishing-fix.md
│ │ └── release_improvements.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── release-drafter.yml
│ └── workflows
│ ├── auto-merge.yml
│ ├── ci.yml
│ ├── codeql.yml
│ ├── dependency-review.yml
│ ├── deploy-docs.yml
│ ├── README.md
│ ├── release-drafter.yml
│ └── release.yml
├── .gitignore
├── .husky
│ ├── commit-msg
│ └── pre-commit
├── .linkcheck.config.json
├── .markdown-link-check.json
├── .nvmrc
├── .pre-commit-config.yaml
├── .versionrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── CONTRIBUTING.md
├── docker-compose.docs.yml
├── Dockerfile.docs
├── docs
│ ├── .docusaurus
│ │ ├── docusaurus-plugin-content-docs
│ │ │ └── default
│ │ │ └── __mdx-loader-dependency.json
│ │ └── docusaurus-plugin-content-pages
│ │ └── default
│ │ └── __plugin.json
│ ├── adrs
│ │ ├── 001-mcp-server-architecture.md
│ │ ├── 002-repository-analysis-engine.md
│ │ ├── 003-static-site-generator-recommendation-engine.md
│ │ ├── 004-diataxis-framework-integration.md
│ │ ├── 005-github-pages-deployment-automation.md
│ │ ├── 006-mcp-tools-api-design.md
│ │ ├── 007-mcp-prompts-and-resources-integration.md
│ │ ├── 008-intelligent-content-population-engine.md
│ │ ├── 009-content-accuracy-validation-framework.md
│ │ ├── 010-mcp-resource-pattern-redesign.md
│ │ └── README.md
│ ├── api
│ │ ├── .nojekyll
│ │ ├── assets
│ │ │ ├── hierarchy.js
│ │ │ ├── highlight.css
│ │ │ ├── icons.js
│ │ │ ├── icons.svg
│ │ │ ├── main.js
│ │ │ ├── navigation.js
│ │ │ ├── search.js
│ │ │ └── style.css
│ │ ├── hierarchy.html
│ │ ├── index.html
│ │ ├── modules.html
│ │ └── variables
│ │ └── TOOLS.html
│ ├── assets
│ │ └── logo.svg
│ ├── development
│ │ └── MCP_INSPECTOR_TESTING.md
│ ├── docusaurus.config.js
│ ├── explanation
│ │ ├── architecture.md
│ │ └── index.md
│ ├── guides
│ │ ├── link-validation.md
│ │ ├── playwright-integration.md
│ │ └── playwright-testing-workflow.md
│ ├── how-to
│ │ ├── analytics-setup.md
│ │ ├── custom-domains.md
│ │ ├── documentation-freshness-tracking.md
│ │ ├── github-pages-deployment.md
│ │ ├── index.md
│ │ ├── local-testing.md
│ │ ├── performance-optimization.md
│ │ ├── prompting-guide.md
│ │ ├── repository-analysis.md
│ │ ├── seo-optimization.md
│ │ ├── site-monitoring.md
│ │ ├── troubleshooting.md
│ │ └── usage-examples.md
│ ├── index.md
│ ├── knowledge-graph.md
│ ├── package-lock.json
│ ├── package.json
│ ├── phase-2-intelligence.md
│ ├── reference
│ │ ├── api-overview.md
│ │ ├── cli.md
│ │ ├── configuration.md
│ │ ├── deploy-pages.md
│ │ ├── index.md
│ │ ├── mcp-tools.md
│ │ └── prompt-templates.md
│ ├── research
│ │ ├── cross-domain-integration
│ │ │ └── README.md
│ │ ├── domain-1-mcp-architecture
│ │ │ ├── index.md
│ │ │ └── mcp-performance-research.md
│ │ ├── domain-2-repository-analysis
│ │ │ └── README.md
│ │ ├── domain-3-ssg-recommendation
│ │ │ ├── index.md
│ │ │ └── ssg-performance-analysis.md
│ │ ├── domain-4-diataxis-integration
│ │ │ └── README.md
│ │ ├── domain-5-github-deployment
│ │ │ ├── github-pages-security-analysis.md
│ │ │ └── index.md
│ │ ├── domain-6-api-design
│ │ │ └── README.md
│ │ ├── README.md
│ │ ├── research-integration-summary-2025-01-14.md
│ │ ├── research-progress-template.md
│ │ └── research-questions-2025-01-14.md
│ ├── robots.txt
│ ├── sidebars.js
│ ├── sitemap.xml
│ ├── src
│ │ └── css
│ │ └── custom.css
│ └── tutorials
│ ├── development-setup.md
│ ├── environment-setup.md
│ ├── first-deployment.md
│ ├── getting-started.md
│ ├── index.md
│ ├── memory-workflows.md
│ └── user-onboarding.md
├── jest.config.js
├── LICENSE
├── Makefile
├── MCP_PHASE2_IMPLEMENTATION.md
├── mcp-config-example.json
├── mcp.json
├── package-lock.json
├── package.json
├── README.md
├── release.sh
├── scripts
│ └── check-package-structure.cjs
├── SECURITY.md
├── setup-precommit.sh
├── src
│ ├── benchmarks
│ │ └── performance.ts
│ ├── index.ts
│ ├── memory
│ │ ├── contextual-retrieval.ts
│ │ ├── deployment-analytics.ts
│ │ ├── enhanced-manager.ts
│ │ ├── export-import.ts
│ │ ├── freshness-kg-integration.ts
│ │ ├── index.ts
│ │ ├── integration.ts
│ │ ├── kg-code-integration.ts
│ │ ├── kg-health.ts
│ │ ├── kg-integration.ts
│ │ ├── kg-link-validator.ts
│ │ ├── kg-storage.ts
│ │ ├── knowledge-graph.ts
│ │ ├── learning.ts
│ │ ├── manager.ts
│ │ ├── multi-agent-sharing.ts
│ │ ├── pruning.ts
│ │ ├── schemas.ts
│ │ ├── storage.ts
│ │ ├── temporal-analysis.ts
│ │ ├── user-preferences.ts
│ │ └── visualization.ts
│ ├── prompts
│ │ └── technical-writer-prompts.ts
│ ├── scripts
│ │ └── benchmark.ts
│ ├── templates
│ │ └── playwright
│ │ ├── accessibility.spec.template.ts
│ │ ├── Dockerfile.template
│ │ ├── docs-e2e.workflow.template.yml
│ │ ├── link-validation.spec.template.ts
│ │ └── playwright.config.template.ts
│ ├── tools
│ │ ├── analyze-deployments.ts
│ │ ├── analyze-readme.ts
│ │ ├── analyze-repository.ts
│ │ ├── check-documentation-links.ts
│ │ ├── deploy-pages.ts
│ │ ├── detect-gaps.ts
│ │ ├── evaluate-readme-health.ts
│ │ ├── generate-config.ts
│ │ ├── generate-contextual-content.ts
│ │ ├── generate-llm-context.ts
│ │ ├── generate-readme-template.ts
│ │ ├── generate-technical-writer-prompts.ts
│ │ ├── kg-health-check.ts
│ │ ├── manage-preferences.ts
│ │ ├── manage-sitemap.ts
│ │ ├── optimize-readme.ts
│ │ ├── populate-content.ts
│ │ ├── readme-best-practices.ts
│ │ ├── recommend-ssg.ts
│ │ ├── setup-playwright-tests.ts
│ │ ├── setup-structure.ts
│ │ ├── sync-code-to-docs.ts
│ │ ├── test-local-deployment.ts
│ │ ├── track-documentation-freshness.ts
│ │ ├── update-existing-documentation.ts
│ │ ├── validate-content.ts
│ │ ├── validate-documentation-freshness.ts
│ │ ├── validate-readme-checklist.ts
│ │ └── verify-deployment.ts
│ ├── types
│ │ └── api.ts
│ ├── utils
│ │ ├── ast-analyzer.ts
│ │ ├── code-scanner.ts
│ │ ├── content-extractor.ts
│ │ ├── drift-detector.ts
│ │ ├── freshness-tracker.ts
│ │ ├── language-parsers-simple.ts
│ │ ├── permission-checker.ts
│ │ └── sitemap-generator.ts
│ └── workflows
│ └── documentation-workflow.ts
├── test-docs-local.sh
├── tests
│ ├── api
│ │ └── mcp-responses.test.ts
│ ├── benchmarks
│ │ └── performance.test.ts
│ ├── edge-cases
│ │ └── error-handling.test.ts
│ ├── functional
│ │ └── tools.test.ts
│ ├── integration
│ │ ├── kg-documentation-workflow.test.ts
│ │ ├── knowledge-graph-workflow.test.ts
│ │ ├── mcp-readme-tools.test.ts
│ │ ├── memory-mcp-tools.test.ts
│ │ ├── readme-technical-writer.test.ts
│ │ └── workflow.test.ts
│ ├── memory
│ │ ├── contextual-retrieval.test.ts
│ │ ├── enhanced-manager.test.ts
│ │ ├── export-import.test.ts
│ │ ├── freshness-kg-integration.test.ts
│ │ ├── kg-code-integration.test.ts
│ │ ├── kg-health.test.ts
│ │ ├── kg-link-validator.test.ts
│ │ ├── kg-storage-validation.test.ts
│ │ ├── kg-storage.test.ts
│ │ ├── knowledge-graph-enhanced.test.ts
│ │ ├── knowledge-graph.test.ts
│ │ ├── learning.test.ts
│ │ ├── manager-advanced.test.ts
│ │ ├── manager.test.ts
│ │ ├── mcp-resource-integration.test.ts
│ │ ├── mcp-tool-persistence.test.ts
│ │ ├── schemas.test.ts
│ │ ├── storage.test.ts
│ │ ├── temporal-analysis.test.ts
│ │ └── user-preferences.test.ts
│ ├── performance
│ │ ├── memory-load-testing.test.ts
│ │ └── memory-stress-testing.test.ts
│ ├── prompts
│ │ ├── guided-workflow-prompts.test.ts
│ │ └── technical-writer-prompts.test.ts
│ ├── server.test.ts
│ ├── setup.ts
│ ├── tools
│ │ ├── all-tools.test.ts
│ │ ├── analyze-coverage.test.ts
│ │ ├── analyze-deployments.test.ts
│ │ ├── analyze-readme.test.ts
│ │ ├── analyze-repository.test.ts
│ │ ├── check-documentation-links.test.ts
│ │ ├── deploy-pages-kg-retrieval.test.ts
│ │ ├── deploy-pages-tracking.test.ts
│ │ ├── deploy-pages.test.ts
│ │ ├── detect-gaps.test.ts
│ │ ├── evaluate-readme-health.test.ts
│ │ ├── generate-contextual-content.test.ts
│ │ ├── generate-llm-context.test.ts
│ │ ├── generate-readme-template.test.ts
│ │ ├── generate-technical-writer-prompts.test.ts
│ │ ├── kg-health-check.test.ts
│ │ ├── manage-sitemap.test.ts
│ │ ├── optimize-readme.test.ts
│ │ ├── readme-best-practices.test.ts
│ │ ├── recommend-ssg-historical.test.ts
│ │ ├── recommend-ssg-preferences.test.ts
│ │ ├── recommend-ssg.test.ts
│ │ ├── simple-coverage.test.ts
│ │ ├── sync-code-to-docs.test.ts
│ │ ├── test-local-deployment.test.ts
│ │ ├── tool-error-handling.test.ts
│ │ ├── track-documentation-freshness.test.ts
│ │ ├── validate-content.test.ts
│ │ ├── validate-documentation-freshness.test.ts
│ │ └── validate-readme-checklist.test.ts
│ ├── types
│ │ └── type-safety.test.ts
│ └── utils
│ ├── ast-analyzer.test.ts
│ ├── content-extractor.test.ts
│ ├── drift-detector.test.ts
│ ├── freshness-tracker.test.ts
│ └── sitemap-generator.test.ts
├── tsconfig.json
└── typedoc.json
```
# Files
--------------------------------------------------------------------------------
/tests/tools/sync-code-to-docs.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Sync Code to Docs Tool Tests (Phase 3)
3 | */
4 |
5 | import { handleSyncCodeToDocs } from "../../src/tools/sync-code-to-docs.js";
6 | import { promises as fs } from "fs";
7 | import { tmpdir } from "os";
8 | import { join } from "path";
9 | import { mkdtemp, rm } from "fs/promises";
10 | import { DriftDetector } from "../../src/utils/drift-detector.js";
11 |
12 | describe("sync_code_to_docs tool", () => {
13 | let tempDir: string;
14 | let projectPath: string;
15 | let docsPath: string;
16 |
17 | beforeEach(async () => {
18 | tempDir = await mkdtemp(join(tmpdir(), "sync-test-"));
19 | projectPath = join(tempDir, "project");
20 | docsPath = join(tempDir, "docs");
21 |
22 | await fs.mkdir(join(projectPath, "src"), { recursive: true });
23 | await fs.mkdir(docsPath, { recursive: true });
24 | });
25 |
26 | afterEach(async () => {
27 | await rm(tempDir, { recursive: true, force: true });
28 | });
29 |
30 | describe("Detect Mode", () => {
31 | test("should detect drift without making changes", async () => {
32 | // Create source file
33 | const sourceCode = `
34 | export function calculate(x: number): number {
35 | return x * 2;
36 | }
37 | `.trim();
38 |
39 | await fs.writeFile(join(projectPath, "src", "calc.ts"), sourceCode);
40 |
41 | // Create documentation
42 | const docContent = `
43 | # Calculator
44 |
45 | ## calculate(x: number): number
46 |
47 | Doubles the input.
48 | `.trim();
49 |
50 | await fs.writeFile(join(docsPath, "calc.md"), docContent);
51 |
52 | // Run in detect mode
53 | const result = await handleSyncCodeToDocs({
54 | projectPath,
55 | docsPath,
56 | mode: "detect",
57 | createSnapshot: true,
58 | });
59 |
60 | expect(result).toBeDefined();
61 | expect(result.content).toBeDefined();
62 | expect(result.content[0]).toBeDefined();
63 |
64 | const data = JSON.parse(result.content[0].text);
65 | expect(data.success).toBe(true);
66 | expect(data.data.mode).toBe("detect");
67 |
68 | // Verify no changes were made
69 | const docAfter = await fs.readFile(join(docsPath, "calc.md"), "utf-8");
70 | expect(docAfter).toBe(docContent);
71 | });
72 |
73 | test("should create baseline snapshot on first run", async () => {
74 | const sourceCode = `export function test(): void {}`;
75 | await fs.writeFile(join(projectPath, "src", "test.ts"), sourceCode);
76 |
77 | const result = await handleSyncCodeToDocs({
78 | projectPath,
79 | docsPath,
80 | mode: "detect",
81 | createSnapshot: true,
82 | });
83 |
84 | expect(result).toBeDefined();
85 |
86 | const data = JSON.parse(result.content[0].text);
87 | expect(data.success).toBe(true);
88 | expect(data.data.snapshotId).toBeTruthy();
89 |
90 | // Check snapshot was created
91 | const snapshotDir = join(tempDir, "project", ".documcp", "snapshots");
92 | const files = await fs.readdir(snapshotDir);
93 | expect(files.length).toBeGreaterThan(0);
94 | });
95 |
96 | test("should report drift statistics", async () => {
97 | // Create initial snapshot
98 | const oldCode = `
99 | export function oldFunction(): void {}
100 | `.trim();
101 |
102 | await fs.writeFile(join(projectPath, "src", "changes.ts"), oldCode);
103 |
104 | await handleSyncCodeToDocs({
105 | projectPath,
106 | docsPath,
107 | mode: "detect",
108 | createSnapshot: true,
109 | });
110 |
111 | // Make changes
112 | const newCode = `
113 | export function newFunction(): void {}
114 | `.trim();
115 |
116 | await fs.writeFile(join(projectPath, "src", "changes.ts"), newCode);
117 |
118 | // Detect drift
119 | const result = await handleSyncCodeToDocs({
120 | projectPath,
121 | docsPath,
122 | mode: "detect",
123 | createSnapshot: true,
124 | });
125 |
126 | const data = JSON.parse(result.content[0].text);
127 | expect(data.success).toBe(true);
128 | expect(data.data.stats).toBeDefined();
129 | expect(data.data.stats.filesAnalyzed).toBeGreaterThanOrEqual(0);
130 | });
131 | });
132 |
133 | describe("Apply Mode", () => {
134 | test("should apply high-confidence changes automatically", async () => {
135 | // Create code with JSDoc
136 | const sourceCode = `
137 | /**
138 | * Calculates the sum of two numbers
139 | * @param a First number
140 | * @param b Second number
141 | * @returns The sum
142 | */
143 | export function add(a: number, b: number): number {
144 | return a + b;
145 | }
146 | `.trim();
147 |
148 | await fs.writeFile(join(projectPath, "src", "math.ts"), sourceCode);
149 |
150 | // Create minimal documentation
151 | const docContent = `
152 | # Math Module
153 |
154 | Documentation needed.
155 | `.trim();
156 |
157 | await fs.writeFile(join(docsPath, "math.md"), docContent);
158 |
159 | // Create baseline
160 | await handleSyncCodeToDocs({
161 | projectPath,
162 | docsPath,
163 | mode: "detect",
164 | createSnapshot: true,
165 | });
166 |
167 | // Run in apply mode with high threshold
168 | const result = await handleSyncCodeToDocs({
169 | projectPath,
170 | docsPath,
171 | mode: "apply",
172 | autoApplyThreshold: 0.9,
173 | createSnapshot: true,
174 | });
175 |
176 | const data = JSON.parse(result.content[0].text);
177 | expect(data.success).toBe(true);
178 | expect(data.data.mode).toBe("apply");
179 |
180 | // Stats should show applied or pending changes
181 | const stats = data.data.stats;
182 | expect(
183 | stats.changesApplied + stats.changesPending,
184 | ).toBeGreaterThanOrEqual(0);
185 | });
186 |
187 | test("should respect confidence threshold", async () => {
188 | // Setup code and docs
189 | const sourceCode = `export function test(): void {}`;
190 | await fs.writeFile(join(projectPath, "src", "test.ts"), sourceCode);
191 |
192 | const docContent = `# Test`;
193 | await fs.writeFile(join(docsPath, "test.md"), docContent);
194 |
195 | // Create baseline
196 | await handleSyncCodeToDocs({
197 | projectPath,
198 | docsPath,
199 | mode: "detect",
200 | });
201 |
202 | // Apply with very high threshold (most changes won't meet it)
203 | const result = await handleSyncCodeToDocs({
204 | projectPath,
205 | docsPath,
206 | mode: "apply",
207 | autoApplyThreshold: 0.99,
208 | });
209 |
210 | const data = JSON.parse(result.content[0].text);
211 | expect(data.success).toBe(true);
212 |
213 | // With high threshold, most changes should be pending
214 | if (data.data.stats.driftsDetected > 0) {
215 | expect(data.data.pendingChanges.length).toBeGreaterThanOrEqual(0);
216 | }
217 | });
218 |
219 | test("should create snapshot before applying changes", async () => {
220 | const sourceCode = `export function test(): void {}`;
221 | await fs.writeFile(
222 | join(projectPath, "src", "snapshot-test.ts"),
223 | sourceCode,
224 | );
225 |
226 | await handleSyncCodeToDocs({
227 | projectPath,
228 | docsPath,
229 | mode: "apply",
230 | createSnapshot: true,
231 | });
232 |
233 | // Verify snapshot exists
234 | const snapshotDir = join(projectPath, ".documcp", "snapshots");
235 | const files = await fs.readdir(snapshotDir);
236 | expect(files.length).toBeGreaterThan(0);
237 | });
238 | });
239 |
240 | describe("Auto Mode", () => {
241 | test("should apply all changes in auto mode", async () => {
242 | const sourceCode = `
243 | export function autoFunction(param: string): string {
244 | return param.toUpperCase();
245 | }
246 | `.trim();
247 |
248 | await fs.writeFile(join(projectPath, "src", "auto.ts"), sourceCode);
249 |
250 | const result = await handleSyncCodeToDocs({
251 | projectPath,
252 | docsPath,
253 | mode: "auto",
254 | createSnapshot: true,
255 | });
256 |
257 | const data = JSON.parse(result.content[0].text);
258 | expect(data.success).toBe(true);
259 | expect(data.data.mode).toBe("auto");
260 | });
261 | });
262 |
263 | describe("Error Handling", () => {
264 | test("should handle invalid project path", async () => {
265 | const result = await handleSyncCodeToDocs({
266 | projectPath: "/nonexistent/path",
267 | docsPath,
268 | mode: "detect",
269 | });
270 |
271 | expect(result).toBeDefined();
272 | expect(result.content).toBeDefined();
273 |
274 | const data = JSON.parse(result.content[0].text);
275 | // Should either fail gracefully or handle missing path
276 | expect(data).toBeDefined();
277 | });
278 |
279 | test("should handle invalid docs path", async () => {
280 | const sourceCode = `export function test(): void {}`;
281 | await fs.writeFile(join(projectPath, "src", "test.ts"), sourceCode);
282 |
283 | const result = await handleSyncCodeToDocs({
284 | projectPath,
285 | docsPath: "/nonexistent/docs",
286 | mode: "detect",
287 | });
288 |
289 | expect(result).toBeDefined();
290 | const data = JSON.parse(result.content[0].text);
291 | expect(data).toBeDefined();
292 | });
293 |
294 | test("should handle empty project", async () => {
295 | // Empty project directory
296 | const result = await handleSyncCodeToDocs({
297 | projectPath,
298 | docsPath,
299 | mode: "detect",
300 | });
301 |
302 | expect(result).toBeDefined();
303 | const data = JSON.parse(result.content[0].text);
304 | expect(data.success).toBe(true);
305 | expect(data.data.stats.filesAnalyzed).toBe(0);
306 | });
307 | });
308 |
309 | describe("Recommendations and Next Steps", () => {
310 | test("should provide recommendations based on results", async () => {
311 | const sourceCode = `
312 | export function critical(param: number): void {}
313 | `.trim();
314 |
315 | await fs.writeFile(join(projectPath, "src", "critical.ts"), sourceCode);
316 |
317 | // Create baseline
318 | await handleSyncCodeToDocs({
319 | projectPath,
320 | docsPath,
321 | mode: "detect",
322 | });
323 |
324 | // Make breaking change
325 | const newCode = `
326 | export function critical(param: string, extra: boolean): void {}
327 | `.trim();
328 |
329 | await fs.writeFile(join(projectPath, "src", "critical.ts"), newCode);
330 |
331 | // Detect changes
332 | const result = await handleSyncCodeToDocs({
333 | projectPath,
334 | docsPath,
335 | mode: "detect",
336 | });
337 |
338 | const data = JSON.parse(result.content[0].text);
339 | expect(data.success).toBe(true);
340 | expect(data.recommendations).toBeDefined();
341 | expect(Array.isArray(data.recommendations)).toBe(true);
342 | });
343 |
344 | test("should provide next steps", async () => {
345 | const result = await handleSyncCodeToDocs({
346 | projectPath,
347 | docsPath,
348 | mode: "detect",
349 | });
350 |
351 | const data = JSON.parse(result.content[0].text);
352 | expect(data.success).toBe(true);
353 | expect(data.nextSteps).toBeDefined();
354 | expect(Array.isArray(data.nextSteps)).toBe(true);
355 | });
356 | });
357 |
358 | describe("Integration with Knowledge Graph", () => {
359 | test("should store sync events", async () => {
360 | const sourceCode = `export function kgTest(): void {}`;
361 | await fs.writeFile(join(projectPath, "src", "kg-test.ts"), sourceCode);
362 |
363 | const result = await handleSyncCodeToDocs({
364 | projectPath,
365 | docsPath,
366 | mode: "detect",
367 | });
368 |
369 | const data = JSON.parse(result.content[0].text);
370 | expect(data.success).toBe(true);
371 |
372 | // Sync event should be created (even if storage fails, shouldn't error)
373 | expect(data.data).toBeDefined();
374 | });
375 | });
376 |
377 | describe("Preview Mode", () => {
378 | test("should show changes in preview mode without applying", async () => {
379 | const sourceCode = `
380 | export function previewFunc(x: number): number {
381 | return x * 3;
382 | }
383 | `.trim();
384 |
385 | await fs.writeFile(join(projectPath, "src", "preview.ts"), sourceCode);
386 |
387 | const docContent = `
388 | # Preview
389 |
390 | Old documentation.
391 | `.trim();
392 |
393 | await fs.writeFile(join(docsPath, "preview.md"), docContent);
394 |
395 | // Create baseline
396 | await handleSyncCodeToDocs({
397 | projectPath,
398 | docsPath,
399 | mode: "detect",
400 | });
401 |
402 | // Change code
403 | const newCode = `
404 | export function previewFunc(x: number, y: number): number {
405 | return x * y;
406 | }
407 | `.trim();
408 |
409 | await fs.writeFile(join(projectPath, "src", "preview.ts"), newCode);
410 |
411 | // Preview changes
412 | const result = await handleSyncCodeToDocs({
413 | projectPath,
414 | docsPath,
415 | mode: "preview",
416 | });
417 |
418 | const data = JSON.parse(result.content[0].text);
419 | expect(data.success).toBe(true);
420 | expect(data.data.mode).toBe("preview");
421 |
422 | // Verify documentation wasn't changed
423 | const docAfter = await fs.readFile(join(docsPath, "preview.md"), "utf-8");
424 | expect(docAfter).toBe(docContent);
425 | });
426 | });
427 |
428 | describe("Documentation Change Application", () => {
429 | test("should apply changes when low-confidence changes exist in auto mode", async () => {
430 | // Create a source file with documentation
431 | const sourceCode = `
432 | /**
433 | * Multiplies two numbers together
434 | * @param x First number
435 | * @param y Second number
436 | */
437 | export function multiply(x: number, y: number): number {
438 | return x * y;
439 | }
440 | `.trim();
441 |
442 | await fs.writeFile(join(projectPath, "src", "math.ts"), sourceCode);
443 |
444 | // Create outdated documentation
445 | const docContent = `
446 | # Math Module
447 |
448 | ## multiply
449 |
450 | Adds two numbers.
451 | `.trim();
452 |
453 | await fs.writeFile(join(docsPath, "math.md"), docContent);
454 |
455 | // Create baseline
456 | await handleSyncCodeToDocs({
457 | projectPath,
458 | docsPath,
459 | mode: "detect",
460 | });
461 |
462 | // Run in auto mode (applies all changes)
463 | const result = await handleSyncCodeToDocs({
464 | projectPath,
465 | docsPath,
466 | mode: "auto",
467 | });
468 |
469 | const data = JSON.parse(result.content[0].text);
470 | expect(data.success).toBe(true);
471 | expect(data.data.mode).toBe("auto");
472 | });
473 |
474 | test("should handle apply errors gracefully", async () => {
475 | // Create source file
476 | const sourceCode = `export function testFunc(): void {}`;
477 | await fs.writeFile(join(projectPath, "src", "test.ts"), sourceCode);
478 |
479 | // Create documentation in a read-only parent directory would fail
480 | // But for this test, we'll just verify the error handling path exists
481 | const docContent = `# Test`;
482 | await fs.writeFile(join(docsPath, "test.md"), docContent);
483 |
484 | // Create baseline
485 | await handleSyncCodeToDocs({
486 | projectPath,
487 | docsPath,
488 | mode: "detect",
489 | });
490 |
491 | // Modify code
492 | const newCode = `export function testFunc(param: string): void {}`;
493 | await fs.writeFile(join(projectPath, "src", "test.ts"), newCode);
494 |
495 | // Try to apply changes
496 | const result = await handleSyncCodeToDocs({
497 | projectPath,
498 | docsPath,
499 | mode: "apply",
500 | autoApplyThreshold: 0.0, // Very low threshold
501 | });
502 |
503 | // Should complete without crashing
504 | expect(result).toBeDefined();
505 | const data = JSON.parse(result.content[0].text);
506 | expect(data.success).toBe(true);
507 | });
508 | });
509 |
510 | describe("Recommendation Edge Cases", () => {
511 | test("should recommend review for breaking changes", async () => {
512 | // Create initial code
513 | const oldCode = `
514 | export function oldApi(x: number): string {
515 | return x.toString();
516 | }
517 | `.trim();
518 |
519 | await fs.writeFile(join(projectPath, "src", "api.ts"), oldCode);
520 |
521 | // Create baseline
522 | await handleSyncCodeToDocs({
523 | projectPath,
524 | docsPath,
525 | mode: "detect",
526 | });
527 |
528 | // Make breaking change
529 | const newCode = `
530 | export function newApi(x: number, y: string): boolean {
531 | return x > 0;
532 | }
533 | `.trim();
534 |
535 | await fs.writeFile(join(projectPath, "src", "api.ts"), newCode);
536 |
537 | // Detect changes
538 | const result = await handleSyncCodeToDocs({
539 | projectPath,
540 | docsPath,
541 | mode: "detect",
542 | });
543 |
544 | const data = JSON.parse(result.content[0].text);
545 | expect(data.success).toBe(true);
546 |
547 | // Should have recommendations
548 | expect(data.recommendations).toBeDefined();
549 | expect(Array.isArray(data.recommendations)).toBe(true);
550 | });
551 |
552 | test("should show info when no drift detected", async () => {
553 | // Create code
554 | const sourceCode = `export function stable(): void {}`;
555 | await fs.writeFile(join(projectPath, "src", "stable.ts"), sourceCode);
556 |
557 | // Create baseline
558 | await handleSyncCodeToDocs({
559 | projectPath,
560 | docsPath,
561 | mode: "detect",
562 | });
563 |
564 | // Run again without changes
565 | const result = await handleSyncCodeToDocs({
566 | projectPath,
567 | docsPath,
568 | mode: "detect",
569 | });
570 |
571 | const data = JSON.parse(result.content[0].text);
572 | expect(data.success).toBe(true);
573 | expect(data.recommendations).toBeDefined();
574 |
575 | // Should have "No Drift Detected" recommendation
576 | const noDriftRec = data.recommendations.find(
577 | (r: any) => r.title?.includes("No Drift"),
578 | );
579 | expect(noDriftRec).toBeDefined();
580 | });
581 |
582 | test("should recommend validation after applying changes", async () => {
583 | const sourceCode = `
584 | /**
585 | * Test function
586 | */
587 | export function test(): void {}
588 | `.trim();
589 |
590 | await fs.writeFile(join(projectPath, "src", "validated.ts"), sourceCode);
591 |
592 | // Create baseline
593 | await handleSyncCodeToDocs({
594 | projectPath,
595 | docsPath,
596 | mode: "detect",
597 | });
598 |
599 | // Modify code
600 | const newCode = `
601 | /**
602 | * Modified test function
603 | */
604 | export function test(param: string): void {}
605 | `.trim();
606 |
607 | await fs.writeFile(join(projectPath, "src", "validated.ts"), newCode);
608 |
609 | // Apply changes
610 | const result = await handleSyncCodeToDocs({
611 | projectPath,
612 | docsPath,
613 | mode: "auto",
614 | });
615 |
616 | const data = JSON.parse(result.content[0].text);
617 | expect(data.success).toBe(true);
618 |
619 | // Should have next steps
620 | expect(data.nextSteps).toBeDefined();
621 | expect(Array.isArray(data.nextSteps)).toBe(true);
622 | });
623 | });
624 |
625 | describe("Next Steps Generation", () => {
626 | test("should suggest apply mode when in detect mode with pending changes", async () => {
627 | const sourceCode = `export function needsSync(): void {}`;
628 | await fs.writeFile(join(projectPath, "src", "sync.ts"), sourceCode);
629 |
630 | // Create baseline
631 | await handleSyncCodeToDocs({
632 | projectPath,
633 | docsPath,
634 | mode: "detect",
635 | });
636 |
637 | // Change code
638 | const newCode = `export function needsSync(param: number): void {}`;
639 | await fs.writeFile(join(projectPath, "src", "sync.ts"), newCode);
640 |
641 | // Detect in detect mode
642 | const result = await handleSyncCodeToDocs({
643 | projectPath,
644 | docsPath,
645 | mode: "detect",
646 | });
647 |
648 | const data = JSON.parse(result.content[0].text);
649 | expect(data.success).toBe(true);
650 | expect(data.nextSteps).toBeDefined();
651 |
652 | // If there are pending changes, should suggest apply mode
653 | if (data.data.pendingChanges?.length > 0) {
654 | const applyStep = data.nextSteps.find(
655 | (s: any) => s.action?.includes("Apply"),
656 | );
657 | expect(applyStep).toBeDefined();
658 | }
659 | });
660 |
661 | test("should suggest review for pending manual changes", async () => {
662 | const sourceCode = `export function complex(): void {}`;
663 | await fs.writeFile(join(projectPath, "src", "complex.ts"), sourceCode);
664 |
665 | // Create baseline
666 | await handleSyncCodeToDocs({
667 | projectPath,
668 | docsPath,
669 | mode: "detect",
670 | });
671 |
672 | // Change code
673 | const newCode = `export function complex(a: number, b: string): boolean { return true; }`;
674 | await fs.writeFile(join(projectPath, "src", "complex.ts"), newCode);
675 |
676 | // Detect with very high threshold (forces manual review)
677 | const result = await handleSyncCodeToDocs({
678 | projectPath,
679 | docsPath,
680 | mode: "apply",
681 | autoApplyThreshold: 0.99,
682 | });
683 |
684 | const data = JSON.parse(result.content[0].text);
685 | expect(data.success).toBe(true);
686 | expect(data.nextSteps).toBeDefined();
687 | });
688 | });
689 |
690 | describe("Snapshot Management", () => {
691 | test("should not create snapshot when createSnapshot is false in detect mode", async () => {
692 | const sourceCode = `export function noSnapshot(): void {}`;
693 | await fs.writeFile(join(projectPath, "src", "nosnapshot.ts"), sourceCode);
694 |
695 | await handleSyncCodeToDocs({
696 | projectPath,
697 | docsPath,
698 | mode: "detect",
699 | createSnapshot: false,
700 | });
701 |
702 | // Should still work even without snapshot
703 | const result = await handleSyncCodeToDocs({
704 | projectPath,
705 | docsPath,
706 | mode: "detect",
707 | createSnapshot: false,
708 | });
709 |
710 | const data = JSON.parse(result.content[0].text);
711 | expect(data.success).toBe(true);
712 | });
713 | });
714 |
715 | describe("Error Path Coverage", () => {
716 | test("should handle KG storage failures gracefully", async () => {
717 | const sourceCode = `export function kgError(): void {}`;
718 | await fs.writeFile(join(projectPath, "src", "kg-error.ts"), sourceCode);
719 |
720 | // The tool should complete even if KG storage fails
721 | const result = await handleSyncCodeToDocs({
722 | projectPath,
723 | docsPath,
724 | mode: "detect",
725 | });
726 |
727 | const data = JSON.parse(result.content[0].text);
728 | expect(data.success).toBe(true);
729 | });
730 |
731 | test("should handle zero drift detections", async () => {
732 | const sourceCode = `export function stable(): void {}`;
733 | await fs.writeFile(join(projectPath, "src", "stable.ts"), sourceCode);
734 |
735 | // Create baseline
736 | await handleSyncCodeToDocs({
737 | projectPath,
738 | docsPath,
739 | mode: "detect",
740 | });
741 |
742 | // Run again with no changes
743 | const result = await handleSyncCodeToDocs({
744 | projectPath,
745 | docsPath,
746 | mode: "detect",
747 | });
748 |
749 | const data = JSON.parse(result.content[0].text);
750 | expect(data.success).toBe(true);
751 | expect(data.data.stats.driftsDetected).toBe(0);
752 | });
753 |
754 | test("should handle files with no drift suggestions", async () => {
755 | const sourceCode = `export function noDrift(): void {}`;
756 | await fs.writeFile(join(projectPath, "src", "nodrift.ts"), sourceCode);
757 |
758 | const result = await handleSyncCodeToDocs({
759 | projectPath,
760 | docsPath,
761 | mode: "apply",
762 | autoApplyThreshold: 0.5,
763 | });
764 |
765 | const data = JSON.parse(result.content[0].text);
766 | expect(data.success).toBe(true);
767 | // Should handle case with no drift gracefully
768 | expect(data.data.appliedChanges).toBeDefined();
769 | expect(data.data.pendingChanges).toBeDefined();
770 | });
771 |
772 | test("should handle recommendations with zero breaking changes", async () => {
773 | const sourceCode = `export function minor(): void {}`;
774 | await fs.writeFile(join(projectPath, "src", "minor.ts"), sourceCode);
775 |
776 | const result = await handleSyncCodeToDocs({
777 | projectPath,
778 | docsPath,
779 | mode: "detect",
780 | });
781 |
782 | const data = JSON.parse(result.content[0].text);
783 | expect(data.success).toBe(true);
784 | // Should not have critical recommendations for no breaking changes
785 | const criticalRecs = data.recommendations?.filter(
786 | (r: any) => r.type === "critical",
787 | );
788 | expect(criticalRecs || []).toHaveLength(0);
789 | });
790 |
791 | test("should handle pending changes without manual review", async () => {
792 | const sourceCode = `export function autoApply(): void {}`;
793 | await fs.writeFile(join(projectPath, "src", "autoapply.ts"), sourceCode);
794 |
795 | // Create baseline
796 | await handleSyncCodeToDocs({
797 | projectPath,
798 | docsPath,
799 | mode: "detect",
800 | });
801 |
802 | // Modify
803 | const newCode = `export function autoApply(x: number): number { return x; }`;
804 | await fs.writeFile(join(projectPath, "src", "autoapply.ts"), newCode);
805 |
806 | const result = await handleSyncCodeToDocs({
807 | projectPath,
808 | docsPath,
809 | mode: "auto", // Auto applies all
810 | });
811 |
812 | const data = JSON.parse(result.content[0].text);
813 | expect(data.success).toBe(true);
814 | });
815 |
816 | test("should handle next steps when no breaking changes exist", async () => {
817 | const sourceCode = `export function noBreaking(): void {}`;
818 | await fs.writeFile(join(projectPath, "src", "nobreaking.ts"), sourceCode);
819 |
820 | const result = await handleSyncCodeToDocs({
821 | projectPath,
822 | docsPath,
823 | mode: "detect",
824 | });
825 |
826 | const data = JSON.parse(result.content[0].text);
827 | expect(data.success).toBe(true);
828 | // Should not suggest reviewing breaking changes when there are none
829 | const breakingStep = data.nextSteps?.find(
830 | (s: any) => s.action?.toLowerCase().includes("breaking"),
831 | );
832 | expect(breakingStep).toBeUndefined();
833 | });
834 |
835 | test("should handle next steps when no changes were applied", async () => {
836 | const sourceCode = `export function noApplied(): void {}`;
837 | await fs.writeFile(join(projectPath, "src", "noapplied.ts"), sourceCode);
838 |
839 | const result = await handleSyncCodeToDocs({
840 | projectPath,
841 | docsPath,
842 | mode: "detect", // Detect mode doesn't apply
843 | });
844 |
845 | const data = JSON.parse(result.content[0].text);
846 | expect(data.success).toBe(true);
847 | expect(data.data.appliedChanges).toHaveLength(0);
848 | });
849 |
850 | test("should handle next steps when no pending changes require review", async () => {
851 | const sourceCode = `export function noPending(): void {}`;
852 | await fs.writeFile(join(projectPath, "src", "nopending.ts"), sourceCode);
853 |
854 | const result = await handleSyncCodeToDocs({
855 | projectPath,
856 | docsPath,
857 | mode: "auto", // Auto applies everything
858 | });
859 |
860 | const data = JSON.parse(result.content[0].text);
861 | expect(data.success).toBe(true);
862 | });
863 |
864 | test("should handle apply mode with suggestions below threshold", async () => {
865 | const sourceCode = `export function lowConfidence(): void {}`;
866 | await fs.writeFile(
867 | join(projectPath, "src", "lowconfidence.ts"),
868 | sourceCode,
869 | );
870 |
871 | // Create baseline
872 | await handleSyncCodeToDocs({
873 | projectPath,
874 | docsPath,
875 | mode: "detect",
876 | });
877 |
878 | // Modify
879 | const newCode = `export function lowConfidence(param: string): void {}`;
880 | await fs.writeFile(join(projectPath, "src", "lowconfidence.ts"), newCode);
881 |
882 | // Very high threshold - suggestions won't meet it
883 | const result = await handleSyncCodeToDocs({
884 | projectPath,
885 | docsPath,
886 | mode: "apply",
887 | autoApplyThreshold: 1.0,
888 | });
889 |
890 | const data = JSON.parse(result.content[0].text);
891 | expect(data.success).toBe(true);
892 | });
893 |
894 | test("should handle context parameter with info logging", async () => {
895 | const sourceCode = `export function withContext(): void {}`;
896 | await fs.writeFile(join(projectPath, "src", "context.ts"), sourceCode);
897 |
898 | const mockContext = {
899 | info: jest.fn(),
900 | warn: jest.fn(),
901 | };
902 |
903 | const result = await handleSyncCodeToDocs(
904 | {
905 | projectPath,
906 | docsPath,
907 | mode: "detect",
908 | },
909 | mockContext,
910 | );
911 |
912 | const data = JSON.parse(result.content[0].text);
913 | expect(data.success).toBe(true);
914 | // Context info should have been called
915 | expect(mockContext.info).toHaveBeenCalled();
916 | });
917 |
918 | test("should handle snapshot creation in non-detect modes", async () => {
919 | const sourceCode = `export function modeSnapshot(): void {}`;
920 | await fs.writeFile(
921 | join(projectPath, "src", "modesnapshot.ts"),
922 | sourceCode,
923 | );
924 |
925 | // Apply mode should create snapshot even if createSnapshot not specified
926 | const result = await handleSyncCodeToDocs({
927 | projectPath,
928 | docsPath,
929 | mode: "apply",
930 | createSnapshot: false, // But mode !== "detect" overrides this
931 | });
932 |
933 | const data = JSON.parse(result.content[0].text);
934 | expect(data.success).toBe(true);
935 | });
936 | });
937 |
938 | describe("Mocked Drift Detector Tests", () => {
939 | let mockDetector: jest.Mocked<DriftDetector>;
940 |
941 | beforeEach(() => {
942 | // Create a real detector instance but spy on its methods
943 | mockDetector = new DriftDetector(
944 | projectPath,
945 | ) as jest.Mocked<DriftDetector>;
946 | });
947 |
948 | test("should apply high-confidence suggestions automatically", async () => {
949 | // Create real documentation file
950 | const docPath = join(docsPath, "api.md");
951 | const originalDoc = `# API
952 |
953 | ## oldFunction
954 |
955 | This is outdated.`;
956 | await fs.writeFile(docPath, originalDoc);
957 |
958 | // Mock drift detector to return suggestions
959 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
960 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
961 | timestamp: "2025-01-01T00:00:00.000Z",
962 | projectPath,
963 | files: new Map([
964 | [
965 | "src/api.ts",
966 | {
967 | filePath: "src/api.ts",
968 | language: "typescript",
969 | functions: [],
970 | classes: [],
971 | interfaces: [],
972 | types: [],
973 | imports: [],
974 | exports: [],
975 | contentHash: "abc123",
976 | lastModified: "2025-01-01T00:00:00.000Z",
977 | linesOfCode: 10,
978 | complexity: 1,
979 | },
980 | ],
981 | ]),
982 | documentation: new Map(),
983 | });
984 |
985 | jest
986 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
987 | .mockResolvedValue({
988 | timestamp: "2025-01-01T00:00:00.000Z",
989 | projectPath,
990 | files: new Map(),
991 | documentation: new Map(),
992 | });
993 |
994 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
995 | {
996 | filePath: "src/api.ts",
997 | hasDrift: true,
998 | severity: "medium",
999 | drifts: [
1000 | {
1001 | type: "outdated",
1002 | affectedDocs: [docPath],
1003 | codeChanges: [
1004 | {
1005 | type: "modified",
1006 | category: "function",
1007 | name: "oldFunction",
1008 | details: "Function renamed to newFunction",
1009 | impactLevel: "major",
1010 | },
1011 | ],
1012 | description: "Function signature changed",
1013 | detectedAt: "2025-01-01T00:00:00.000Z",
1014 | severity: "medium",
1015 | },
1016 | ],
1017 | impactAnalysis: {
1018 | breakingChanges: 0,
1019 | majorChanges: 1,
1020 | minorChanges: 0,
1021 | affectedDocFiles: [docPath],
1022 | estimatedUpdateEffort: "medium",
1023 | requiresManualReview: false,
1024 | },
1025 | suggestions: [
1026 | {
1027 | docFile: docPath,
1028 | section: "oldFunction",
1029 | currentContent: "This is outdated.",
1030 | suggestedContent: `## newFunction
1031 |
1032 | Updated documentation for new function.`,
1033 | reasoning: "Function was renamed from oldFunction to newFunction",
1034 | confidence: 0.95,
1035 | autoApplicable: true,
1036 | },
1037 | ],
1038 | },
1039 | ]);
1040 |
1041 | // Run in apply mode
1042 | const result = await handleSyncCodeToDocs({
1043 | projectPath,
1044 | docsPath,
1045 | mode: "apply",
1046 | autoApplyThreshold: 0.8,
1047 | });
1048 |
1049 | const data = JSON.parse(result.content[0].text);
1050 | expect(data.success).toBe(true);
1051 | expect(data.data.appliedChanges.length).toBeGreaterThan(0);
1052 |
1053 | // Verify the file was actually modified
1054 | const updatedDoc = await fs.readFile(docPath, "utf-8");
1055 | expect(updatedDoc).toContain("newFunction");
1056 | });
1057 |
1058 | test("should not apply low-confidence suggestions", async () => {
1059 | const docPath = join(docsPath, "lowconf.md");
1060 | const originalDoc = `# Low Confidence
1061 |
1062 | ## someFunction
1063 |
1064 | Original content.`;
1065 | await fs.writeFile(docPath, originalDoc);
1066 |
1067 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
1068 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
1069 | timestamp: "2025-01-01T00:00:00.000Z",
1070 | projectPath,
1071 | files: new Map(),
1072 | documentation: new Map(),
1073 | });
1074 |
1075 | jest
1076 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
1077 | .mockResolvedValue({
1078 | timestamp: "2025-01-01T00:00:00.000Z",
1079 | projectPath,
1080 | files: new Map(),
1081 | documentation: new Map(),
1082 | });
1083 |
1084 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
1085 | {
1086 | filePath: "src/lowconf.ts",
1087 | hasDrift: true,
1088 | severity: "low",
1089 | drifts: [
1090 | {
1091 | type: "outdated",
1092 | affectedDocs: [docPath],
1093 | codeChanges: [
1094 | {
1095 | type: "modified",
1096 | category: "function",
1097 | name: "someFunction",
1098 | details: "Minor change",
1099 | impactLevel: "minor",
1100 | },
1101 | ],
1102 | description: "Minor change",
1103 | detectedAt: "2025-01-01T00:00:00.000Z",
1104 | severity: "low",
1105 | },
1106 | ],
1107 | impactAnalysis: {
1108 | breakingChanges: 0,
1109 | majorChanges: 0,
1110 | minorChanges: 1,
1111 | affectedDocFiles: [docPath],
1112 | estimatedUpdateEffort: "low",
1113 | requiresManualReview: false,
1114 | },
1115 | suggestions: [
1116 | {
1117 | docFile: docPath,
1118 | section: "someFunction",
1119 | currentContent: "Original content.",
1120 | suggestedContent: "Suggested content.",
1121 | reasoning: "Minor update needed",
1122 | confidence: 0.5, // Low confidence
1123 | autoApplicable: true,
1124 | },
1125 | ],
1126 | },
1127 | ]);
1128 |
1129 | const result = await handleSyncCodeToDocs({
1130 | projectPath,
1131 | docsPath,
1132 | mode: "apply",
1133 | autoApplyThreshold: 0.8, // Higher than suggestion confidence
1134 | });
1135 |
1136 | const data = JSON.parse(result.content[0].text);
1137 | expect(data.success).toBe(true);
1138 | expect(data.data.pendingChanges.length).toBeGreaterThan(0);
1139 | expect(data.data.appliedChanges.length).toBe(0);
1140 |
1141 | // Verify file was NOT modified
1142 | const unchangedDoc = await fs.readFile(docPath, "utf-8");
1143 | expect(unchangedDoc).toBe(originalDoc);
1144 | });
1145 |
1146 | test("should apply all suggestions in auto mode regardless of confidence", async () => {
1147 | const docPath = join(docsPath, "auto.md");
1148 | const originalDoc = `# Auto Mode
1149 |
1150 | ## function1
1151 |
1152 | Old content.`;
1153 | await fs.writeFile(docPath, originalDoc);
1154 |
1155 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
1156 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
1157 | timestamp: "2025-01-01T00:00:00.000Z",
1158 | projectPath,
1159 | files: new Map(),
1160 | documentation: new Map(),
1161 | });
1162 |
1163 | jest
1164 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
1165 | .mockResolvedValue({
1166 | timestamp: "2025-01-01T00:00:00.000Z",
1167 | projectPath,
1168 | files: new Map(),
1169 | documentation: new Map(),
1170 | });
1171 |
1172 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
1173 | {
1174 | filePath: "src/auto.ts",
1175 | hasDrift: true,
1176 | severity: "low",
1177 | drifts: [
1178 | {
1179 | type: "outdated",
1180 | affectedDocs: [docPath],
1181 | codeChanges: [
1182 | {
1183 | type: "modified",
1184 | category: "function",
1185 | name: "function1",
1186 | details: "Change",
1187 | impactLevel: "minor",
1188 | },
1189 | ],
1190 | description: "Change",
1191 | detectedAt: "2025-01-01T00:00:00.000Z",
1192 | severity: "low",
1193 | },
1194 | ],
1195 | impactAnalysis: {
1196 | breakingChanges: 0,
1197 | majorChanges: 0,
1198 | minorChanges: 1,
1199 | affectedDocFiles: [docPath],
1200 | estimatedUpdateEffort: "low",
1201 | requiresManualReview: false,
1202 | },
1203 | suggestions: [
1204 | {
1205 | docFile: docPath,
1206 | section: "function1",
1207 | currentContent: "Old content.",
1208 | suggestedContent: `## function1
1209 |
1210 | New content from auto mode.`,
1211 | reasoning: "Auto-applied update",
1212 | confidence: 0.3, // Very low confidence
1213 | autoApplicable: false, // Not auto-applicable
1214 | },
1215 | ],
1216 | },
1217 | ]);
1218 |
1219 | const result = await handleSyncCodeToDocs({
1220 | projectPath,
1221 | docsPath,
1222 | mode: "auto", // Auto mode applies everything
1223 | });
1224 |
1225 | const data = JSON.parse(result.content[0].text);
1226 | expect(data.success).toBe(true);
1227 | expect(data.data.appliedChanges.length).toBeGreaterThan(0);
1228 |
1229 | // Verify file was modified
1230 | const updatedDoc = await fs.readFile(docPath, "utf-8");
1231 | expect(updatedDoc).toContain("New content from auto mode");
1232 | });
1233 |
1234 | test("should handle apply errors and mark as pending", async () => {
1235 | const docPath = join(docsPath, "error.md");
1236 | // Don't create the file - this will cause an error
1237 |
1238 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
1239 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
1240 | timestamp: "2025-01-01T00:00:00.000Z",
1241 | projectPath,
1242 | files: new Map(),
1243 | documentation: new Map(),
1244 | });
1245 |
1246 | jest
1247 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
1248 | .mockResolvedValue({
1249 | timestamp: "2025-01-01T00:00:00.000Z",
1250 | projectPath,
1251 | files: new Map(),
1252 | documentation: new Map(),
1253 | });
1254 |
1255 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
1256 | {
1257 | filePath: "src/error.ts",
1258 | hasDrift: true,
1259 | severity: "medium",
1260 | drifts: [
1261 | {
1262 | type: "outdated",
1263 | affectedDocs: [docPath],
1264 | codeChanges: [
1265 | {
1266 | type: "modified",
1267 | category: "function",
1268 | name: "errorFunction",
1269 | details: "Change",
1270 | impactLevel: "minor",
1271 | },
1272 | ],
1273 | description: "Change",
1274 | detectedAt: "2025-01-01T00:00:00.000Z",
1275 | severity: "medium",
1276 | },
1277 | ],
1278 | impactAnalysis: {
1279 | breakingChanges: 0,
1280 | majorChanges: 1,
1281 | minorChanges: 0,
1282 | affectedDocFiles: [docPath],
1283 | estimatedUpdateEffort: "medium",
1284 | requiresManualReview: false,
1285 | },
1286 | suggestions: [
1287 | {
1288 | docFile: docPath, // File doesn't exist
1289 | section: "errorSection",
1290 | currentContent: "N/A",
1291 | suggestedContent: "New content",
1292 | reasoning: "Should fail",
1293 | confidence: 0.95,
1294 | autoApplicable: true,
1295 | },
1296 | ],
1297 | },
1298 | ]);
1299 |
1300 | const mockContext = {
1301 | info: jest.fn(),
1302 | warn: jest.fn(),
1303 | };
1304 |
1305 | const result = await handleSyncCodeToDocs(
1306 | {
1307 | projectPath,
1308 | docsPath,
1309 | mode: "apply",
1310 | autoApplyThreshold: 0.8,
1311 | },
1312 | mockContext,
1313 | );
1314 |
1315 | const data = JSON.parse(result.content[0].text);
1316 | expect(data.success).toBe(true);
1317 | // Failed applies should be added to pending changes
1318 | expect(data.data.pendingChanges.length).toBeGreaterThan(0);
1319 | expect(mockContext.warn).toHaveBeenCalled();
1320 | });
1321 |
1322 | test("should add new section when section doesn't exist", async () => {
1323 | const docPath = join(docsPath, "newsection.md");
1324 | const originalDoc = `# New Section Test
1325 |
1326 | ## existingSection
1327 |
1328 | Existing content.`;
1329 | await fs.writeFile(docPath, originalDoc);
1330 |
1331 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
1332 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
1333 | timestamp: "2025-01-01T00:00:00.000Z",
1334 | projectPath,
1335 | files: new Map(),
1336 | documentation: new Map(),
1337 | });
1338 |
1339 | jest
1340 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
1341 | .mockResolvedValue({
1342 | timestamp: "2025-01-01T00:00:00.000Z",
1343 | projectPath,
1344 | files: new Map(),
1345 | documentation: new Map(),
1346 | });
1347 |
1348 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
1349 | {
1350 | filePath: "src/new.ts",
1351 | hasDrift: true,
1352 | severity: "low",
1353 | drifts: [
1354 | {
1355 | type: "missing",
1356 | affectedDocs: [docPath],
1357 | codeChanges: [
1358 | {
1359 | type: "added",
1360 | category: "function",
1361 | name: "newFunction",
1362 | details: "New function added",
1363 | impactLevel: "minor",
1364 | },
1365 | ],
1366 | description: "New function",
1367 | detectedAt: "2025-01-01T00:00:00.000Z",
1368 | severity: "low",
1369 | },
1370 | ],
1371 | impactAnalysis: {
1372 | breakingChanges: 0,
1373 | majorChanges: 0,
1374 | minorChanges: 1,
1375 | affectedDocFiles: [docPath],
1376 | estimatedUpdateEffort: "low",
1377 | requiresManualReview: false,
1378 | },
1379 | suggestions: [
1380 | {
1381 | docFile: docPath,
1382 | section: "newSection", // This section doesn't exist
1383 | currentContent: "",
1384 | suggestedContent: `## newSection
1385 |
1386 | This is a brand new section.`,
1387 | reasoning: "New function added",
1388 | confidence: 0.9,
1389 | autoApplicable: true,
1390 | },
1391 | ],
1392 | },
1393 | ]);
1394 |
1395 | const result = await handleSyncCodeToDocs({
1396 | projectPath,
1397 | docsPath,
1398 | mode: "apply",
1399 | autoApplyThreshold: 0.8,
1400 | });
1401 |
1402 | const data = JSON.parse(result.content[0].text);
1403 | expect(data.success).toBe(true);
1404 | expect(data.data.appliedChanges.length).toBeGreaterThan(0);
1405 |
1406 | // Verify new section was appended
1407 | const updatedDoc = await fs.readFile(docPath, "utf-8");
1408 | expect(updatedDoc).toContain("newSection");
1409 | expect(updatedDoc).toContain("brand new section");
1410 | });
1411 |
1412 | test("should handle breaking changes recommendation", async () => {
1413 | const docPath = join(docsPath, "breaking.md");
1414 | await fs.writeFile(docPath, "# Breaking");
1415 |
1416 | jest.spyOn(DriftDetector.prototype, "initialize").mockResolvedValue();
1417 | jest.spyOn(DriftDetector.prototype, "createSnapshot").mockResolvedValue({
1418 | timestamp: "2025-01-01T00:00:00.000Z",
1419 | projectPath,
1420 | files: new Map(),
1421 | documentation: new Map(),
1422 | });
1423 |
1424 | jest
1425 | .spyOn(DriftDetector.prototype, "loadLatestSnapshot")
1426 | .mockResolvedValue({
1427 | timestamp: "2025-01-01T00:00:00.000Z",
1428 | projectPath,
1429 | files: new Map(),
1430 | documentation: new Map(),
1431 | });
1432 |
1433 | jest.spyOn(DriftDetector.prototype, "detectDrift").mockResolvedValue([
1434 | {
1435 | filePath: "src/breaking.ts",
1436 | hasDrift: true,
1437 | severity: "critical",
1438 | drifts: [
1439 | {
1440 | type: "breaking",
1441 | affectedDocs: [docPath],
1442 | codeChanges: [
1443 | {
1444 | type: "removed",
1445 | category: "function",
1446 | name: "oldAPI",
1447 | details: "Breaking change",
1448 | impactLevel: "breaking",
1449 | },
1450 | ],
1451 | description: "Breaking change",
1452 | detectedAt: "2025-01-01T00:00:00.000Z",
1453 | severity: "critical",
1454 | },
1455 | ],
1456 | impactAnalysis: {
1457 | breakingChanges: 2, // Multiple breaking changes
1458 | majorChanges: 0,
1459 | minorChanges: 0,
1460 | affectedDocFiles: [docPath],
1461 | estimatedUpdateEffort: "high",
1462 | requiresManualReview: true,
1463 | },
1464 | suggestions: [
1465 | {
1466 | docFile: docPath,
1467 | section: "API",
1468 | currentContent: "Old API",
1469 | suggestedContent: "New API",
1470 | reasoning: "Breaking change",
1471 | confidence: 0.9,
1472 | autoApplicable: true,
1473 | },
1474 | ],
1475 | },
1476 | ]);
1477 |
1478 | const result = await handleSyncCodeToDocs({
1479 | projectPath,
1480 | docsPath,
1481 | mode: "detect",
1482 | });
1483 |
1484 | const data = JSON.parse(result.content[0].text);
1485 | expect(data.success).toBe(true);
1486 | expect(data.data.stats.breakingChanges).toBe(2);
1487 |
1488 | // Should have critical recommendation
1489 | const criticalRec = data.recommendations.find(
1490 | (r: any) => r.type === "critical",
1491 | );
1492 | expect(criticalRec).toBeDefined();
1493 | expect(criticalRec.title).toContain("Breaking");
1494 | });
1495 |
1496 | afterEach(() => {
1497 | jest.restoreAllMocks();
1498 | });
1499 | });
1500 | });
1501 |
```
--------------------------------------------------------------------------------
/docs/api/assets/style.css:
--------------------------------------------------------------------------------
```css
1 | @layer typedoc {
2 | :root {
3 | --dim-toolbar-contents-height: 2.5rem;
4 | --dim-toolbar-border-bottom-width: 1px;
5 | --dim-header-height: calc(
6 | var(--dim-toolbar-border-bottom-width) +
7 | var(--dim-toolbar-contents-height)
8 | );
9 |
10 | /* 0rem For mobile; unit is required for calculation in `calc` */
11 | --dim-container-main-margin-y: 0rem;
12 |
13 | --dim-footer-height: 3.5rem;
14 |
15 | --modal-animation-duration: 0.2s;
16 | }
17 |
18 | :root {
19 | /* Light */
20 | --light-color-background: #f2f4f8;
21 | --light-color-background-secondary: #eff0f1;
22 | /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */
23 | --light-color-background-active: #d6d8da;
24 | --light-color-background-warning: #e6e600;
25 | --light-color-warning-text: #222;
26 | --light-color-accent: #c5c7c9;
27 | --light-color-active-menu-item: var(--light-color-background-active);
28 | --light-color-text: #222;
29 | --light-color-contrast-text: #000;
30 | --light-color-text-aside: #5e5e5e;
31 |
32 | --light-color-icon-background: var(--light-color-background);
33 | --light-color-icon-text: var(--light-color-text);
34 |
35 | --light-color-comment-tag-text: var(--light-color-text);
36 | --light-color-comment-tag: var(--light-color-background);
37 |
38 | --light-color-link: #1f70c2;
39 | --light-color-focus-outline: #3584e4;
40 |
41 | --light-color-ts-keyword: #056bd6;
42 | --light-color-ts-project: #b111c9;
43 | --light-color-ts-module: var(--light-color-ts-project);
44 | --light-color-ts-namespace: var(--light-color-ts-project);
45 | --light-color-ts-enum: #7e6f15;
46 | --light-color-ts-enum-member: var(--light-color-ts-enum);
47 | --light-color-ts-variable: #4760ec;
48 | --light-color-ts-function: #572be7;
49 | --light-color-ts-class: #1f70c2;
50 | --light-color-ts-interface: #108024;
51 | --light-color-ts-constructor: var(--light-color-ts-class);
52 | --light-color-ts-property: #9f5f30;
53 | --light-color-ts-method: #be3989;
54 | --light-color-ts-reference: #ff4d82;
55 | --light-color-ts-call-signature: var(--light-color-ts-method);
56 | --light-color-ts-index-signature: var(--light-color-ts-property);
57 | --light-color-ts-constructor-signature: var(
58 | --light-color-ts-constructor
59 | );
60 | --light-color-ts-parameter: var(--light-color-ts-variable);
61 | /* type literal not included as links will never be generated to it */
62 | --light-color-ts-type-parameter: #a55c0e;
63 | --light-color-ts-accessor: #c73c3c;
64 | --light-color-ts-get-signature: var(--light-color-ts-accessor);
65 | --light-color-ts-set-signature: var(--light-color-ts-accessor);
66 | --light-color-ts-type-alias: #d51270;
67 | /* reference not included as links will be colored with the kind that it points to */
68 | --light-color-document: #000000;
69 |
70 | --light-color-alert-note: #0969d9;
71 | --light-color-alert-tip: #1a7f37;
72 | --light-color-alert-important: #8250df;
73 | --light-color-alert-warning: #9a6700;
74 | --light-color-alert-caution: #cf222e;
75 |
76 | --light-external-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='10' height='10'><path fill-opacity='0' stroke='%23000' stroke-width='10' d='m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z'/></svg>");
77 | --light-color-scheme: light;
78 | }
79 |
80 | :root {
81 | /* Dark */
82 | --dark-color-background: #2b2e33;
83 | --dark-color-background-secondary: #1e2024;
84 | /* Not to be confused with [:active](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) */
85 | --dark-color-background-active: #5d5d6a;
86 | --dark-color-background-warning: #bebe00;
87 | --dark-color-warning-text: #222;
88 | --dark-color-accent: #9096a2;
89 | --dark-color-active-menu-item: var(--dark-color-background-active);
90 | --dark-color-text: #f5f5f5;
91 | --dark-color-contrast-text: #ffffff;
92 | --dark-color-text-aside: #dddddd;
93 |
94 | --dark-color-icon-background: var(--dark-color-background-secondary);
95 | --dark-color-icon-text: var(--dark-color-text);
96 |
97 | --dark-color-comment-tag-text: var(--dark-color-text);
98 | --dark-color-comment-tag: var(--dark-color-background);
99 |
100 | --dark-color-link: #00aff4;
101 | --dark-color-focus-outline: #4c97f2;
102 |
103 | --dark-color-ts-keyword: #3399ff;
104 | --dark-color-ts-project: #e358ff;
105 | --dark-color-ts-module: var(--dark-color-ts-project);
106 | --dark-color-ts-namespace: var(--dark-color-ts-project);
107 | --dark-color-ts-enum: #f4d93e;
108 | --dark-color-ts-enum-member: var(--dark-color-ts-enum);
109 | --dark-color-ts-variable: #798dff;
110 | --dark-color-ts-function: #a280ff;
111 | --dark-color-ts-class: #8ac4ff;
112 | --dark-color-ts-interface: #6cff87;
113 | --dark-color-ts-constructor: var(--dark-color-ts-class);
114 | --dark-color-ts-property: #ff984d;
115 | --dark-color-ts-method: #ff4db8;
116 | --dark-color-ts-reference: #ff4d82;
117 | --dark-color-ts-call-signature: var(--dark-color-ts-method);
118 | --dark-color-ts-index-signature: var(--dark-color-ts-property);
119 | --dark-color-ts-constructor-signature: var(--dark-color-ts-constructor);
120 | --dark-color-ts-parameter: var(--dark-color-ts-variable);
121 | /* type literal not included as links will never be generated to it */
122 | --dark-color-ts-type-parameter: #e07d13;
123 | --dark-color-ts-accessor: #ff6060;
124 | --dark-color-ts-get-signature: var(--dark-color-ts-accessor);
125 | --dark-color-ts-set-signature: var(--dark-color-ts-accessor);
126 | --dark-color-ts-type-alias: #ff6492;
127 | /* reference not included as links will be colored with the kind that it points to */
128 | --dark-color-document: #ffffff;
129 |
130 | --dark-color-alert-note: #0969d9;
131 | --dark-color-alert-tip: #1a7f37;
132 | --dark-color-alert-important: #8250df;
133 | --dark-color-alert-warning: #9a6700;
134 | --dark-color-alert-caution: #cf222e;
135 |
136 | --dark-external-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='10' height='10'><path fill-opacity='0' stroke='%23fff' stroke-width='10' d='m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z'/></svg>");
137 | --dark-color-scheme: dark;
138 | }
139 |
140 | @media (prefers-color-scheme: light) {
141 | :root {
142 | --color-background: var(--light-color-background);
143 | --color-background-secondary: var(
144 | --light-color-background-secondary
145 | );
146 | --color-background-active: var(--light-color-background-active);
147 | --color-background-warning: var(--light-color-background-warning);
148 | --color-warning-text: var(--light-color-warning-text);
149 | --color-accent: var(--light-color-accent);
150 | --color-active-menu-item: var(--light-color-active-menu-item);
151 | --color-text: var(--light-color-text);
152 | --color-contrast-text: var(--light-color-contrast-text);
153 | --color-text-aside: var(--light-color-text-aside);
154 |
155 | --color-icon-background: var(--light-color-icon-background);
156 | --color-icon-text: var(--light-color-icon-text);
157 |
158 | --color-comment-tag-text: var(--light-color-text);
159 | --color-comment-tag: var(--light-color-background);
160 |
161 | --color-link: var(--light-color-link);
162 | --color-focus-outline: var(--light-color-focus-outline);
163 |
164 | --color-ts-keyword: var(--light-color-ts-keyword);
165 | --color-ts-project: var(--light-color-ts-project);
166 | --color-ts-module: var(--light-color-ts-module);
167 | --color-ts-namespace: var(--light-color-ts-namespace);
168 | --color-ts-enum: var(--light-color-ts-enum);
169 | --color-ts-enum-member: var(--light-color-ts-enum-member);
170 | --color-ts-variable: var(--light-color-ts-variable);
171 | --color-ts-function: var(--light-color-ts-function);
172 | --color-ts-class: var(--light-color-ts-class);
173 | --color-ts-interface: var(--light-color-ts-interface);
174 | --color-ts-constructor: var(--light-color-ts-constructor);
175 | --color-ts-property: var(--light-color-ts-property);
176 | --color-ts-method: var(--light-color-ts-method);
177 | --color-ts-reference: var(--light-color-ts-reference);
178 | --color-ts-call-signature: var(--light-color-ts-call-signature);
179 | --color-ts-index-signature: var(--light-color-ts-index-signature);
180 | --color-ts-constructor-signature: var(
181 | --light-color-ts-constructor-signature
182 | );
183 | --color-ts-parameter: var(--light-color-ts-parameter);
184 | --color-ts-type-parameter: var(--light-color-ts-type-parameter);
185 | --color-ts-accessor: var(--light-color-ts-accessor);
186 | --color-ts-get-signature: var(--light-color-ts-get-signature);
187 | --color-ts-set-signature: var(--light-color-ts-set-signature);
188 | --color-ts-type-alias: var(--light-color-ts-type-alias);
189 | --color-document: var(--light-color-document);
190 |
191 | --color-alert-note: var(--light-color-alert-note);
192 | --color-alert-tip: var(--light-color-alert-tip);
193 | --color-alert-important: var(--light-color-alert-important);
194 | --color-alert-warning: var(--light-color-alert-warning);
195 | --color-alert-caution: var(--light-color-alert-caution);
196 |
197 | --external-icon: var(--light-external-icon);
198 | --color-scheme: var(--light-color-scheme);
199 | }
200 | }
201 |
202 | @media (prefers-color-scheme: dark) {
203 | :root {
204 | --color-background: var(--dark-color-background);
205 | --color-background-secondary: var(
206 | --dark-color-background-secondary
207 | );
208 | --color-background-active: var(--dark-color-background-active);
209 | --color-background-warning: var(--dark-color-background-warning);
210 | --color-warning-text: var(--dark-color-warning-text);
211 | --color-accent: var(--dark-color-accent);
212 | --color-active-menu-item: var(--dark-color-active-menu-item);
213 | --color-text: var(--dark-color-text);
214 | --color-contrast-text: var(--dark-color-contrast-text);
215 | --color-text-aside: var(--dark-color-text-aside);
216 |
217 | --color-icon-background: var(--dark-color-icon-background);
218 | --color-icon-text: var(--dark-color-icon-text);
219 |
220 | --color-comment-tag-text: var(--dark-color-text);
221 | --color-comment-tag: var(--dark-color-background);
222 |
223 | --color-link: var(--dark-color-link);
224 | --color-focus-outline: var(--dark-color-focus-outline);
225 |
226 | --color-ts-keyword: var(--dark-color-ts-keyword);
227 | --color-ts-project: var(--dark-color-ts-project);
228 | --color-ts-module: var(--dark-color-ts-module);
229 | --color-ts-namespace: var(--dark-color-ts-namespace);
230 | --color-ts-enum: var(--dark-color-ts-enum);
231 | --color-ts-enum-member: var(--dark-color-ts-enum-member);
232 | --color-ts-variable: var(--dark-color-ts-variable);
233 | --color-ts-function: var(--dark-color-ts-function);
234 | --color-ts-class: var(--dark-color-ts-class);
235 | --color-ts-interface: var(--dark-color-ts-interface);
236 | --color-ts-constructor: var(--dark-color-ts-constructor);
237 | --color-ts-property: var(--dark-color-ts-property);
238 | --color-ts-method: var(--dark-color-ts-method);
239 | --color-ts-reference: var(--dark-color-ts-reference);
240 | --color-ts-call-signature: var(--dark-color-ts-call-signature);
241 | --color-ts-index-signature: var(--dark-color-ts-index-signature);
242 | --color-ts-constructor-signature: var(
243 | --dark-color-ts-constructor-signature
244 | );
245 | --color-ts-parameter: var(--dark-color-ts-parameter);
246 | --color-ts-type-parameter: var(--dark-color-ts-type-parameter);
247 | --color-ts-accessor: var(--dark-color-ts-accessor);
248 | --color-ts-get-signature: var(--dark-color-ts-get-signature);
249 | --color-ts-set-signature: var(--dark-color-ts-set-signature);
250 | --color-ts-type-alias: var(--dark-color-ts-type-alias);
251 | --color-document: var(--dark-color-document);
252 |
253 | --color-alert-note: var(--dark-color-alert-note);
254 | --color-alert-tip: var(--dark-color-alert-tip);
255 | --color-alert-important: var(--dark-color-alert-important);
256 | --color-alert-warning: var(--dark-color-alert-warning);
257 | --color-alert-caution: var(--dark-color-alert-caution);
258 |
259 | --external-icon: var(--dark-external-icon);
260 | --color-scheme: var(--dark-color-scheme);
261 | }
262 | }
263 |
264 | :root[data-theme="light"] {
265 | --color-background: var(--light-color-background);
266 | --color-background-secondary: var(--light-color-background-secondary);
267 | --color-background-active: var(--light-color-background-active);
268 | --color-background-warning: var(--light-color-background-warning);
269 | --color-warning-text: var(--light-color-warning-text);
270 | --color-icon-background: var(--light-color-icon-background);
271 | --color-accent: var(--light-color-accent);
272 | --color-active-menu-item: var(--light-color-active-menu-item);
273 | --color-text: var(--light-color-text);
274 | --color-contrast-text: var(--light-color-contrast-text);
275 | --color-text-aside: var(--light-color-text-aside);
276 | --color-icon-text: var(--light-color-icon-text);
277 |
278 | --color-comment-tag-text: var(--light-color-text);
279 | --color-comment-tag: var(--light-color-background);
280 |
281 | --color-link: var(--light-color-link);
282 | --color-focus-outline: var(--light-color-focus-outline);
283 |
284 | --color-ts-keyword: var(--light-color-ts-keyword);
285 | --color-ts-project: var(--light-color-ts-project);
286 | --color-ts-module: var(--light-color-ts-module);
287 | --color-ts-namespace: var(--light-color-ts-namespace);
288 | --color-ts-enum: var(--light-color-ts-enum);
289 | --color-ts-enum-member: var(--light-color-ts-enum-member);
290 | --color-ts-variable: var(--light-color-ts-variable);
291 | --color-ts-function: var(--light-color-ts-function);
292 | --color-ts-class: var(--light-color-ts-class);
293 | --color-ts-interface: var(--light-color-ts-interface);
294 | --color-ts-constructor: var(--light-color-ts-constructor);
295 | --color-ts-property: var(--light-color-ts-property);
296 | --color-ts-method: var(--light-color-ts-method);
297 | --color-ts-reference: var(--light-color-ts-reference);
298 | --color-ts-call-signature: var(--light-color-ts-call-signature);
299 | --color-ts-index-signature: var(--light-color-ts-index-signature);
300 | --color-ts-constructor-signature: var(
301 | --light-color-ts-constructor-signature
302 | );
303 | --color-ts-parameter: var(--light-color-ts-parameter);
304 | --color-ts-type-parameter: var(--light-color-ts-type-parameter);
305 | --color-ts-accessor: var(--light-color-ts-accessor);
306 | --color-ts-get-signature: var(--light-color-ts-get-signature);
307 | --color-ts-set-signature: var(--light-color-ts-set-signature);
308 | --color-ts-type-alias: var(--light-color-ts-type-alias);
309 | --color-document: var(--light-color-document);
310 |
311 | --color-note: var(--light-color-note);
312 | --color-tip: var(--light-color-tip);
313 | --color-important: var(--light-color-important);
314 | --color-warning: var(--light-color-warning);
315 | --color-caution: var(--light-color-caution);
316 |
317 | --external-icon: var(--light-external-icon);
318 | --color-scheme: var(--light-color-scheme);
319 | }
320 |
321 | :root[data-theme="dark"] {
322 | --color-background: var(--dark-color-background);
323 | --color-background-secondary: var(--dark-color-background-secondary);
324 | --color-background-active: var(--dark-color-background-active);
325 | --color-background-warning: var(--dark-color-background-warning);
326 | --color-warning-text: var(--dark-color-warning-text);
327 | --color-icon-background: var(--dark-color-icon-background);
328 | --color-accent: var(--dark-color-accent);
329 | --color-active-menu-item: var(--dark-color-active-menu-item);
330 | --color-text: var(--dark-color-text);
331 | --color-contrast-text: var(--dark-color-contrast-text);
332 | --color-text-aside: var(--dark-color-text-aside);
333 | --color-icon-text: var(--dark-color-icon-text);
334 |
335 | --color-comment-tag-text: var(--dark-color-text);
336 | --color-comment-tag: var(--dark-color-background);
337 |
338 | --color-link: var(--dark-color-link);
339 | --color-focus-outline: var(--dark-color-focus-outline);
340 |
341 | --color-ts-keyword: var(--dark-color-ts-keyword);
342 | --color-ts-project: var(--dark-color-ts-project);
343 | --color-ts-module: var(--dark-color-ts-module);
344 | --color-ts-namespace: var(--dark-color-ts-namespace);
345 | --color-ts-enum: var(--dark-color-ts-enum);
346 | --color-ts-enum-member: var(--dark-color-ts-enum-member);
347 | --color-ts-variable: var(--dark-color-ts-variable);
348 | --color-ts-function: var(--dark-color-ts-function);
349 | --color-ts-class: var(--dark-color-ts-class);
350 | --color-ts-interface: var(--dark-color-ts-interface);
351 | --color-ts-constructor: var(--dark-color-ts-constructor);
352 | --color-ts-property: var(--dark-color-ts-property);
353 | --color-ts-method: var(--dark-color-ts-method);
354 | --color-ts-reference: var(--dark-color-ts-reference);
355 | --color-ts-call-signature: var(--dark-color-ts-call-signature);
356 | --color-ts-index-signature: var(--dark-color-ts-index-signature);
357 | --color-ts-constructor-signature: var(
358 | --dark-color-ts-constructor-signature
359 | );
360 | --color-ts-parameter: var(--dark-color-ts-parameter);
361 | --color-ts-type-parameter: var(--dark-color-ts-type-parameter);
362 | --color-ts-accessor: var(--dark-color-ts-accessor);
363 | --color-ts-get-signature: var(--dark-color-ts-get-signature);
364 | --color-ts-set-signature: var(--dark-color-ts-set-signature);
365 | --color-ts-type-alias: var(--dark-color-ts-type-alias);
366 | --color-document: var(--dark-color-document);
367 |
368 | --color-note: var(--dark-color-note);
369 | --color-tip: var(--dark-color-tip);
370 | --color-important: var(--dark-color-important);
371 | --color-warning: var(--dark-color-warning);
372 | --color-caution: var(--dark-color-caution);
373 |
374 | --external-icon: var(--dark-external-icon);
375 | --color-scheme: var(--dark-color-scheme);
376 | }
377 |
378 | html {
379 | color-scheme: var(--color-scheme);
380 | @media (prefers-reduced-motion: no-preference) {
381 | scroll-behavior: smooth;
382 | }
383 | }
384 |
385 | *:focus-visible,
386 | .tsd-accordion-summary:focus-visible svg {
387 | outline: 2px solid var(--color-focus-outline);
388 | }
389 |
390 | .always-visible,
391 | .always-visible .tsd-signatures {
392 | display: inherit !important;
393 | }
394 |
395 | h1,
396 | h2,
397 | h3,
398 | h4,
399 | h5,
400 | h6 {
401 | line-height: 1.2;
402 | }
403 |
404 | h1 {
405 | font-size: 1.875rem;
406 | margin: 0.67rem 0;
407 | }
408 |
409 | h2 {
410 | font-size: 1.5rem;
411 | margin: 0.83rem 0;
412 | }
413 |
414 | h3 {
415 | font-size: 1.25rem;
416 | margin: 1rem 0;
417 | }
418 |
419 | h4 {
420 | font-size: 1.05rem;
421 | margin: 1.33rem 0;
422 | }
423 |
424 | h5 {
425 | font-size: 1rem;
426 | margin: 1.5rem 0;
427 | }
428 |
429 | h6 {
430 | font-size: 0.875rem;
431 | margin: 2.33rem 0;
432 | }
433 |
434 | dl,
435 | menu,
436 | ol,
437 | ul {
438 | margin: 1em 0;
439 | }
440 |
441 | dd {
442 | margin: 0 0 0 34px;
443 | }
444 |
445 | .container {
446 | max-width: 1700px;
447 | padding: 0 2rem;
448 | }
449 |
450 | /* Footer */
451 | footer {
452 | border-top: 1px solid var(--color-accent);
453 | padding-top: 1rem;
454 | padding-bottom: 1rem;
455 | max-height: var(--dim-footer-height);
456 | }
457 | footer > p {
458 | margin: 0 1em;
459 | }
460 |
461 | .container-main {
462 | margin: var(--dim-container-main-margin-y) auto;
463 | /* toolbar, footer, margin */
464 | min-height: calc(
465 | 100svh - var(--dim-header-height) - var(--dim-footer-height) -
466 | 2 * var(--dim-container-main-margin-y)
467 | );
468 | }
469 |
470 | @keyframes fade-in {
471 | from {
472 | opacity: 0;
473 | }
474 | to {
475 | opacity: 1;
476 | }
477 | }
478 | @keyframes fade-out {
479 | from {
480 | opacity: 1;
481 | visibility: visible;
482 | }
483 | to {
484 | opacity: 0;
485 | }
486 | }
487 | @keyframes pop-in-from-right {
488 | from {
489 | transform: translate(100%, 0);
490 | }
491 | to {
492 | transform: translate(0, 0);
493 | }
494 | }
495 | @keyframes pop-out-to-right {
496 | from {
497 | transform: translate(0, 0);
498 | visibility: visible;
499 | }
500 | to {
501 | transform: translate(100%, 0);
502 | }
503 | }
504 | body {
505 | background: var(--color-background);
506 | font-family:
507 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
508 | Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
509 | font-size: 16px;
510 | color: var(--color-text);
511 | margin: 0;
512 | }
513 |
514 | a {
515 | color: var(--color-link);
516 | text-decoration: none;
517 | }
518 | a:hover {
519 | text-decoration: underline;
520 | }
521 | a.external[target="_blank"] {
522 | background-image: var(--external-icon);
523 | background-position: top 3px right;
524 | background-repeat: no-repeat;
525 | padding-right: 13px;
526 | }
527 | a.tsd-anchor-link {
528 | color: var(--color-text);
529 | }
530 | :target {
531 | scroll-margin-block: calc(var(--dim-header-height) + 0.5rem);
532 | }
533 |
534 | code,
535 | pre {
536 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
537 | padding: 0.2em;
538 | margin: 0;
539 | font-size: 0.875rem;
540 | border-radius: 0.8em;
541 | }
542 |
543 | pre {
544 | position: relative;
545 | white-space: pre-wrap;
546 | word-wrap: break-word;
547 | padding: 10px;
548 | border: 1px solid var(--color-accent);
549 | margin-bottom: 8px;
550 | }
551 | pre code {
552 | padding: 0;
553 | font-size: 100%;
554 | }
555 | pre > button {
556 | position: absolute;
557 | top: 10px;
558 | right: 10px;
559 | opacity: 0;
560 | transition: opacity 0.1s;
561 | box-sizing: border-box;
562 | }
563 | pre:hover > button,
564 | pre > button.visible,
565 | pre > button:focus-visible {
566 | opacity: 1;
567 | }
568 |
569 | blockquote {
570 | margin: 1em 0;
571 | padding-left: 1em;
572 | border-left: 4px solid gray;
573 | }
574 |
575 | img {
576 | max-width: 100%;
577 | }
578 |
579 | * {
580 | scrollbar-width: thin;
581 | scrollbar-color: var(--color-accent) var(--color-icon-background);
582 | }
583 |
584 | *::-webkit-scrollbar {
585 | width: 0.75rem;
586 | }
587 |
588 | *::-webkit-scrollbar-track {
589 | background: var(--color-icon-background);
590 | }
591 |
592 | *::-webkit-scrollbar-thumb {
593 | background-color: var(--color-accent);
594 | border-radius: 999rem;
595 | border: 0.25rem solid var(--color-icon-background);
596 | }
597 |
598 | dialog {
599 | border: none;
600 | outline: none;
601 | padding: 0;
602 | background-color: var(--color-background);
603 | }
604 | dialog::backdrop {
605 | display: none;
606 | }
607 | #tsd-overlay {
608 | background-color: rgba(0, 0, 0, 0.5);
609 | position: fixed;
610 | z-index: 9999;
611 | top: 0;
612 | left: 0;
613 | right: 0;
614 | bottom: 0;
615 | animation: fade-in var(--modal-animation-duration) forwards;
616 | }
617 | #tsd-overlay.closing {
618 | animation-name: fade-out;
619 | }
620 |
621 | .tsd-typography {
622 | line-height: 1.333em;
623 | }
624 | .tsd-typography ul {
625 | list-style: square;
626 | padding: 0 0 0 20px;
627 | margin: 0;
628 | }
629 | .tsd-typography .tsd-index-panel h3,
630 | .tsd-index-panel .tsd-typography h3,
631 | .tsd-typography h4,
632 | .tsd-typography h5,
633 | .tsd-typography h6 {
634 | font-size: 1em;
635 | }
636 | .tsd-typography h5,
637 | .tsd-typography h6 {
638 | font-weight: normal;
639 | }
640 | .tsd-typography p,
641 | .tsd-typography ul,
642 | .tsd-typography ol {
643 | margin: 1em 0;
644 | }
645 | .tsd-typography table {
646 | border-collapse: collapse;
647 | border: none;
648 | }
649 | .tsd-typography td,
650 | .tsd-typography th {
651 | padding: 6px 13px;
652 | border: 1px solid var(--color-accent);
653 | }
654 | .tsd-typography thead,
655 | .tsd-typography tr:nth-child(even) {
656 | background-color: var(--color-background-secondary);
657 | }
658 |
659 | .tsd-alert {
660 | padding: 8px 16px;
661 | margin-bottom: 16px;
662 | border-left: 0.25em solid var(--alert-color);
663 | }
664 | .tsd-alert blockquote > :last-child,
665 | .tsd-alert > :last-child {
666 | margin-bottom: 0;
667 | }
668 | .tsd-alert-title {
669 | color: var(--alert-color);
670 | display: inline-flex;
671 | align-items: center;
672 | }
673 | .tsd-alert-title span {
674 | margin-left: 4px;
675 | }
676 |
677 | .tsd-alert-note {
678 | --alert-color: var(--color-alert-note);
679 | }
680 | .tsd-alert-tip {
681 | --alert-color: var(--color-alert-tip);
682 | }
683 | .tsd-alert-important {
684 | --alert-color: var(--color-alert-important);
685 | }
686 | .tsd-alert-warning {
687 | --alert-color: var(--color-alert-warning);
688 | }
689 | .tsd-alert-caution {
690 | --alert-color: var(--color-alert-caution);
691 | }
692 |
693 | .tsd-breadcrumb {
694 | margin: 0;
695 | margin-top: 1rem;
696 | padding: 0;
697 | color: var(--color-text-aside);
698 | }
699 | .tsd-breadcrumb a {
700 | color: var(--color-text-aside);
701 | text-decoration: none;
702 | }
703 | .tsd-breadcrumb a:hover {
704 | text-decoration: underline;
705 | }
706 | .tsd-breadcrumb li {
707 | display: inline;
708 | }
709 | .tsd-breadcrumb li:after {
710 | content: " / ";
711 | }
712 |
713 | .tsd-comment-tags {
714 | display: flex;
715 | flex-direction: column;
716 | }
717 | dl.tsd-comment-tag-group {
718 | display: flex;
719 | align-items: center;
720 | overflow: hidden;
721 | margin: 0.5em 0;
722 | }
723 | dl.tsd-comment-tag-group dt {
724 | display: flex;
725 | margin-right: 0.5em;
726 | font-size: 0.875em;
727 | font-weight: normal;
728 | }
729 | dl.tsd-comment-tag-group dd {
730 | margin: 0;
731 | }
732 | code.tsd-tag {
733 | padding: 0.25em 0.4em;
734 | border: 0.1em solid var(--color-accent);
735 | margin-right: 0.25em;
736 | font-size: 70%;
737 | }
738 | h1 code.tsd-tag:first-of-type {
739 | margin-left: 0.25em;
740 | }
741 |
742 | dl.tsd-comment-tag-group dd:before,
743 | dl.tsd-comment-tag-group dd:after {
744 | content: " ";
745 | }
746 | dl.tsd-comment-tag-group dd pre,
747 | dl.tsd-comment-tag-group dd:after {
748 | clear: both;
749 | }
750 | dl.tsd-comment-tag-group p {
751 | margin: 0;
752 | }
753 |
754 | .tsd-panel.tsd-comment .lead {
755 | font-size: 1.1em;
756 | line-height: 1.333em;
757 | margin-bottom: 2em;
758 | }
759 | .tsd-panel.tsd-comment .lead:last-child {
760 | margin-bottom: 0;
761 | }
762 |
763 | .tsd-filter-visibility h4 {
764 | font-size: 1rem;
765 | padding-top: 0.75rem;
766 | padding-bottom: 0.5rem;
767 | margin: 0;
768 | }
769 | .tsd-filter-item:not(:last-child) {
770 | margin-bottom: 0.5rem;
771 | }
772 | .tsd-filter-input {
773 | display: flex;
774 | width: -moz-fit-content;
775 | width: fit-content;
776 | align-items: center;
777 | -webkit-user-select: none;
778 | -moz-user-select: none;
779 | -ms-user-select: none;
780 | user-select: none;
781 | cursor: pointer;
782 | }
783 | .tsd-filter-input input[type="checkbox"] {
784 | cursor: pointer;
785 | position: absolute;
786 | width: 1.5em;
787 | height: 1.5em;
788 | opacity: 0;
789 | }
790 | .tsd-filter-input input[type="checkbox"]:disabled {
791 | pointer-events: none;
792 | }
793 | .tsd-filter-input svg {
794 | cursor: pointer;
795 | width: 1.5em;
796 | height: 1.5em;
797 | margin-right: 0.5em;
798 | border-radius: 0.33em;
799 | /* Leaving this at full opacity breaks event listeners on Firefox.
800 | Don't remove unless you know what you're doing. */
801 | opacity: 0.99;
802 | }
803 | .tsd-filter-input input[type="checkbox"]:focus-visible + svg {
804 | outline: 2px solid var(--color-focus-outline);
805 | }
806 | .tsd-checkbox-background {
807 | fill: var(--color-accent);
808 | }
809 | input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark {
810 | stroke: var(--color-text);
811 | }
812 | .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background {
813 | fill: var(--color-background);
814 | stroke: var(--color-accent);
815 | stroke-width: 0.25rem;
816 | }
817 | .tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark {
818 | stroke: var(--color-accent);
819 | }
820 |
821 | .settings-label {
822 | font-weight: bold;
823 | text-transform: uppercase;
824 | display: inline-block;
825 | }
826 |
827 | .tsd-filter-visibility .settings-label {
828 | margin: 0.75rem 0 0.5rem 0;
829 | }
830 |
831 | .tsd-theme-toggle .settings-label {
832 | margin: 0.75rem 0.75rem 0 0;
833 | }
834 |
835 | .tsd-hierarchy h4 label:hover span {
836 | text-decoration: underline;
837 | }
838 |
839 | .tsd-hierarchy {
840 | list-style: square;
841 | margin: 0;
842 | }
843 | .tsd-hierarchy-target {
844 | font-weight: bold;
845 | }
846 | .tsd-hierarchy-toggle {
847 | color: var(--color-link);
848 | cursor: pointer;
849 | }
850 |
851 | .tsd-full-hierarchy:not(:last-child) {
852 | margin-bottom: 1em;
853 | padding-bottom: 1em;
854 | border-bottom: 1px solid var(--color-accent);
855 | }
856 | .tsd-full-hierarchy,
857 | .tsd-full-hierarchy ul {
858 | list-style: none;
859 | margin: 0;
860 | padding: 0;
861 | }
862 | .tsd-full-hierarchy ul {
863 | padding-left: 1.5rem;
864 | }
865 | .tsd-full-hierarchy a {
866 | padding: 0.25rem 0 !important;
867 | font-size: 1rem;
868 | display: inline-flex;
869 | align-items: center;
870 | color: var(--color-text);
871 | }
872 | .tsd-full-hierarchy svg[data-dropdown] {
873 | cursor: pointer;
874 | }
875 | .tsd-full-hierarchy svg[data-dropdown="false"] {
876 | transform: rotate(-90deg);
877 | }
878 | .tsd-full-hierarchy svg[data-dropdown="false"] ~ ul {
879 | display: none;
880 | }
881 |
882 | .tsd-panel-group.tsd-index-group {
883 | margin-bottom: 0;
884 | }
885 | .tsd-index-panel .tsd-index-list {
886 | list-style: none;
887 | line-height: 1.333em;
888 | margin: 0;
889 | padding: 0.25rem 0 0 0;
890 | overflow: hidden;
891 | display: grid;
892 | grid-template-columns: repeat(3, 1fr);
893 | column-gap: 1rem;
894 | grid-template-rows: auto;
895 | }
896 | @media (max-width: 1024px) {
897 | .tsd-index-panel .tsd-index-list {
898 | grid-template-columns: repeat(2, 1fr);
899 | }
900 | }
901 | @media (max-width: 768px) {
902 | .tsd-index-panel .tsd-index-list {
903 | grid-template-columns: repeat(1, 1fr);
904 | }
905 | }
906 | .tsd-index-panel .tsd-index-list li {
907 | -webkit-page-break-inside: avoid;
908 | -moz-page-break-inside: avoid;
909 | -ms-page-break-inside: avoid;
910 | -o-page-break-inside: avoid;
911 | page-break-inside: avoid;
912 | }
913 |
914 | .tsd-flag {
915 | display: inline-block;
916 | padding: 0.25em 0.4em;
917 | border-radius: 4px;
918 | color: var(--color-comment-tag-text);
919 | background-color: var(--color-comment-tag);
920 | text-indent: 0;
921 | font-size: 75%;
922 | line-height: 1;
923 | font-weight: normal;
924 | }
925 |
926 | .tsd-anchor {
927 | position: relative;
928 | top: -100px;
929 | }
930 |
931 | .tsd-member {
932 | position: relative;
933 | }
934 | .tsd-member .tsd-anchor + h3 {
935 | display: flex;
936 | align-items: center;
937 | margin-top: 0;
938 | margin-bottom: 0;
939 | border-bottom: none;
940 | }
941 |
942 | .tsd-navigation.settings {
943 | margin: 0;
944 | margin-bottom: 1rem;
945 | }
946 | .tsd-navigation > a,
947 | .tsd-navigation .tsd-accordion-summary {
948 | width: calc(100% - 0.25rem);
949 | display: flex;
950 | align-items: center;
951 | }
952 | .tsd-navigation a,
953 | .tsd-navigation summary > span,
954 | .tsd-page-navigation a {
955 | display: flex;
956 | width: calc(100% - 0.25rem);
957 | align-items: center;
958 | padding: 0.25rem;
959 | color: var(--color-text);
960 | text-decoration: none;
961 | box-sizing: border-box;
962 | }
963 | .tsd-navigation a.current,
964 | .tsd-page-navigation a.current {
965 | background: var(--color-active-menu-item);
966 | color: var(--color-contrast-text);
967 | }
968 | .tsd-navigation a:hover,
969 | .tsd-page-navigation a:hover {
970 | text-decoration: underline;
971 | }
972 | .tsd-navigation ul,
973 | .tsd-page-navigation ul {
974 | margin-top: 0;
975 | margin-bottom: 0;
976 | padding: 0;
977 | list-style: none;
978 | }
979 | .tsd-navigation li,
980 | .tsd-page-navigation li {
981 | padding: 0;
982 | max-width: 100%;
983 | }
984 | .tsd-navigation .tsd-nav-link {
985 | display: none;
986 | }
987 | .tsd-nested-navigation {
988 | margin-left: 3rem;
989 | }
990 | .tsd-nested-navigation > li > details {
991 | margin-left: -1.5rem;
992 | }
993 | .tsd-small-nested-navigation {
994 | margin-left: 1.5rem;
995 | }
996 | .tsd-small-nested-navigation > li > details {
997 | margin-left: -1.5rem;
998 | }
999 |
1000 | .tsd-page-navigation-section > summary {
1001 | padding: 0.25rem;
1002 | }
1003 | .tsd-page-navigation-section > summary > svg {
1004 | margin-right: 0.25rem;
1005 | }
1006 | .tsd-page-navigation-section > div {
1007 | margin-left: 30px;
1008 | }
1009 | .tsd-page-navigation ul {
1010 | padding-left: 1.75rem;
1011 | }
1012 |
1013 | #tsd-sidebar-links a {
1014 | margin-top: 0;
1015 | margin-bottom: 0.5rem;
1016 | line-height: 1.25rem;
1017 | }
1018 | #tsd-sidebar-links a:last-of-type {
1019 | margin-bottom: 0;
1020 | }
1021 |
1022 | a.tsd-index-link {
1023 | padding: 0.25rem 0 !important;
1024 | font-size: 1rem;
1025 | line-height: 1.25rem;
1026 | display: inline-flex;
1027 | align-items: center;
1028 | color: var(--color-text);
1029 | }
1030 | .tsd-accordion-summary {
1031 | list-style-type: none; /* hide marker on non-safari */
1032 | outline: none; /* broken on safari, so just hide it */
1033 | display: flex;
1034 | align-items: center;
1035 | gap: 0.25rem;
1036 | box-sizing: border-box;
1037 | }
1038 | .tsd-accordion-summary::-webkit-details-marker {
1039 | display: none; /* hide marker on safari */
1040 | }
1041 | .tsd-accordion-summary,
1042 | .tsd-accordion-summary a {
1043 | -moz-user-select: none;
1044 | -webkit-user-select: none;
1045 | -ms-user-select: none;
1046 | user-select: none;
1047 |
1048 | cursor: pointer;
1049 | }
1050 | .tsd-accordion-summary a {
1051 | width: calc(100% - 1.5rem);
1052 | }
1053 | .tsd-accordion-summary > * {
1054 | margin-top: 0;
1055 | margin-bottom: 0;
1056 | padding-top: 0;
1057 | padding-bottom: 0;
1058 | }
1059 | /*
1060 | * We need to be careful to target the arrow indicating whether the accordion
1061 | * is open, but not any other SVGs included in the details element.
1062 | */
1063 | .tsd-accordion:not([open]) > .tsd-accordion-summary > svg:first-child {
1064 | transform: rotate(-90deg);
1065 | }
1066 | .tsd-index-content > :not(:first-child) {
1067 | margin-top: 0.75rem;
1068 | }
1069 | .tsd-index-summary {
1070 | margin-top: 1.5rem;
1071 | margin-bottom: 0.75rem;
1072 | display: flex;
1073 | align-content: center;
1074 | }
1075 |
1076 | .tsd-no-select {
1077 | -webkit-user-select: none;
1078 | -moz-user-select: none;
1079 | -ms-user-select: none;
1080 | user-select: none;
1081 | }
1082 | .tsd-kind-icon {
1083 | margin-right: 0.5rem;
1084 | width: 1.25rem;
1085 | height: 1.25rem;
1086 | min-width: 1.25rem;
1087 | min-height: 1.25rem;
1088 | }
1089 | .tsd-signature > .tsd-kind-icon {
1090 | margin-right: 0.8rem;
1091 | }
1092 |
1093 | .tsd-panel {
1094 | margin-bottom: 2.5rem;
1095 | }
1096 | .tsd-panel.tsd-member {
1097 | margin-bottom: 4rem;
1098 | }
1099 | .tsd-panel:empty {
1100 | display: none;
1101 | }
1102 | .tsd-panel > h1,
1103 | .tsd-panel > h2,
1104 | .tsd-panel > h3 {
1105 | margin: 1.5rem -1.5rem 0.75rem -1.5rem;
1106 | padding: 0 1.5rem 0.75rem 1.5rem;
1107 | }
1108 | .tsd-panel > h1.tsd-before-signature,
1109 | .tsd-panel > h2.tsd-before-signature,
1110 | .tsd-panel > h3.tsd-before-signature {
1111 | margin-bottom: 0;
1112 | border-bottom: none;
1113 | }
1114 |
1115 | .tsd-panel-group {
1116 | margin: 2rem 0;
1117 | }
1118 | .tsd-panel-group.tsd-index-group {
1119 | margin: 2rem 0;
1120 | }
1121 | .tsd-panel-group.tsd-index-group details {
1122 | margin: 2rem 0;
1123 | }
1124 | .tsd-panel-group > .tsd-accordion-summary {
1125 | margin-bottom: 1rem;
1126 | }
1127 |
1128 | #tsd-search[open] {
1129 | animation: fade-in var(--modal-animation-duration) ease-out forwards;
1130 | }
1131 | #tsd-search[open].closing {
1132 | animation-name: fade-out;
1133 | }
1134 |
1135 | /* Avoid setting `display` on closed dialog */
1136 | #tsd-search[open] {
1137 | display: flex;
1138 | flex-direction: column;
1139 | padding: 1rem;
1140 | width: 32rem;
1141 | max-width: 90vw;
1142 | max-height: calc(100vh - env(keyboard-inset-height, 0px) - 25vh);
1143 | /* Anchor dialog to top */
1144 | margin-top: 10vh;
1145 | border-radius: 6px;
1146 | will-change: max-height;
1147 | }
1148 | #tsd-search-input {
1149 | box-sizing: border-box;
1150 | width: 100%;
1151 | padding: 0 0.625rem; /* 10px */
1152 | outline: 0;
1153 | border: 2px solid var(--color-accent);
1154 | background-color: transparent;
1155 | color: var(--color-text);
1156 | border-radius: 4px;
1157 | height: 2.5rem;
1158 | flex: 0 0 auto;
1159 | font-size: 0.875rem;
1160 | transition: border-color 0.2s, background-color 0.2s;
1161 | }
1162 | #tsd-search-input:focus-visible {
1163 | background-color: var(--color-background-active);
1164 | border-color: transparent;
1165 | color: var(--color-contrast-text);
1166 | }
1167 | #tsd-search-input::placeholder {
1168 | color: inherit;
1169 | opacity: 0.8;
1170 | }
1171 | #tsd-search-results {
1172 | margin: 0;
1173 | padding: 0;
1174 | list-style: none;
1175 | flex: 1 1 auto;
1176 | display: flex;
1177 | flex-direction: column;
1178 | overflow-y: auto;
1179 | }
1180 | #tsd-search-results:not(:empty) {
1181 | margin-top: 0.5rem;
1182 | }
1183 | #tsd-search-results > li {
1184 | background-color: var(--color-background);
1185 | line-height: 1.5;
1186 | box-sizing: border-box;
1187 | border-radius: 4px;
1188 | }
1189 | #tsd-search-results > li:nth-child(even) {
1190 | background-color: var(--color-background-secondary);
1191 | }
1192 | #tsd-search-results > li:is(:hover, [aria-selected="true"]) {
1193 | background-color: var(--color-background-active);
1194 | color: var(--color-contrast-text);
1195 | }
1196 | /* It's important that this takes full size of parent `li`, to capture a click on `li` */
1197 | #tsd-search-results > li > a {
1198 | display: flex;
1199 | align-items: center;
1200 | padding: 0.5rem 0.25rem;
1201 | box-sizing: border-box;
1202 | width: 100%;
1203 | }
1204 | #tsd-search-results > li > a > .text {
1205 | flex: 1 1 auto;
1206 | min-width: 0;
1207 | overflow-wrap: anywhere;
1208 | }
1209 | #tsd-search-results > li > a .parent {
1210 | color: var(--color-text-aside);
1211 | }
1212 | #tsd-search-results > li > a mark {
1213 | color: inherit;
1214 | background-color: inherit;
1215 | font-weight: bold;
1216 | }
1217 | #tsd-search-status {
1218 | flex: 1;
1219 | display: grid;
1220 | place-content: center;
1221 | text-align: center;
1222 | overflow-wrap: anywhere;
1223 | }
1224 | #tsd-search-status:not(:empty) {
1225 | min-height: 6rem;
1226 | }
1227 |
1228 | .tsd-signature {
1229 | margin: 0 0 1rem 0;
1230 | padding: 1rem 0.5rem;
1231 | border: 1px solid var(--color-accent);
1232 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
1233 | font-size: 14px;
1234 | overflow-x: auto;
1235 | }
1236 |
1237 | .tsd-signature-keyword {
1238 | color: var(--color-ts-keyword);
1239 | font-weight: normal;
1240 | }
1241 |
1242 | .tsd-signature-symbol {
1243 | color: var(--color-text-aside);
1244 | font-weight: normal;
1245 | }
1246 |
1247 | .tsd-signature-type {
1248 | font-style: italic;
1249 | font-weight: normal;
1250 | }
1251 |
1252 | .tsd-signatures {
1253 | padding: 0;
1254 | margin: 0 0 1em 0;
1255 | list-style-type: none;
1256 | }
1257 | .tsd-signatures .tsd-signature {
1258 | margin: 0;
1259 | border-color: var(--color-accent);
1260 | border-width: 1px 0;
1261 | transition: background-color 0.1s;
1262 | }
1263 | .tsd-signatures .tsd-index-signature:not(:last-child) {
1264 | margin-bottom: 1em;
1265 | }
1266 | .tsd-signatures .tsd-index-signature .tsd-signature {
1267 | border-width: 1px;
1268 | }
1269 | .tsd-description .tsd-signatures .tsd-signature {
1270 | border-width: 1px;
1271 | }
1272 |
1273 | ul.tsd-parameter-list,
1274 | ul.tsd-type-parameter-list {
1275 | list-style: square;
1276 | margin: 0;
1277 | padding-left: 20px;
1278 | }
1279 | ul.tsd-parameter-list > li.tsd-parameter-signature,
1280 | ul.tsd-type-parameter-list > li.tsd-parameter-signature {
1281 | list-style: none;
1282 | margin-left: -20px;
1283 | }
1284 | ul.tsd-parameter-list h5,
1285 | ul.tsd-type-parameter-list h5 {
1286 | font-size: 16px;
1287 | margin: 1em 0 0.5em 0;
1288 | }
1289 | .tsd-sources {
1290 | margin-top: 1rem;
1291 | font-size: 0.875em;
1292 | }
1293 | .tsd-sources a {
1294 | color: var(--color-text-aside);
1295 | text-decoration: underline;
1296 | }
1297 | .tsd-sources ul {
1298 | list-style: none;
1299 | padding: 0;
1300 | }
1301 |
1302 | .tsd-page-toolbar {
1303 | position: sticky;
1304 | z-index: 1;
1305 | top: 0;
1306 | left: 0;
1307 | width: 100%;
1308 | color: var(--color-text);
1309 | background: var(--color-background-secondary);
1310 | border-bottom: var(--dim-toolbar-border-bottom-width)
1311 | var(--color-accent) solid;
1312 | transition: transform 0.3s ease-in-out;
1313 | }
1314 | .tsd-page-toolbar a {
1315 | color: var(--color-text);
1316 | }
1317 | .tsd-toolbar-contents {
1318 | display: flex;
1319 | align-items: center;
1320 | height: var(--dim-toolbar-contents-height);
1321 | margin: 0 auto;
1322 | }
1323 | .tsd-toolbar-contents > .title {
1324 | font-weight: bold;
1325 | margin-right: auto;
1326 | }
1327 | #tsd-toolbar-links {
1328 | display: flex;
1329 | align-items: center;
1330 | gap: 1.5rem;
1331 | margin-right: 1rem;
1332 | }
1333 |
1334 | .tsd-widget {
1335 | box-sizing: border-box;
1336 | display: inline-block;
1337 | opacity: 0.8;
1338 | height: 2.5rem;
1339 | width: 2.5rem;
1340 | transition: opacity 0.1s, background-color 0.1s;
1341 | text-align: center;
1342 | cursor: pointer;
1343 | border: none;
1344 | background-color: transparent;
1345 | }
1346 | .tsd-widget:hover {
1347 | opacity: 0.9;
1348 | }
1349 | .tsd-widget:active {
1350 | opacity: 1;
1351 | background-color: var(--color-accent);
1352 | }
1353 | #tsd-toolbar-menu-trigger {
1354 | display: none;
1355 | }
1356 |
1357 | .tsd-member-summary-name {
1358 | display: inline-flex;
1359 | align-items: center;
1360 | padding: 0.25rem;
1361 | text-decoration: none;
1362 | }
1363 |
1364 | .tsd-anchor-icon {
1365 | display: inline-flex;
1366 | align-items: center;
1367 | margin-left: 0.5rem;
1368 | color: var(--color-text);
1369 | vertical-align: middle;
1370 | }
1371 |
1372 | .tsd-anchor-icon svg {
1373 | width: 1em;
1374 | height: 1em;
1375 | visibility: hidden;
1376 | }
1377 |
1378 | .tsd-member-summary-name:hover > .tsd-anchor-icon svg,
1379 | .tsd-anchor-link:hover > .tsd-anchor-icon svg,
1380 | .tsd-anchor-icon:focus-visible svg {
1381 | visibility: visible;
1382 | }
1383 |
1384 | .deprecated {
1385 | text-decoration: line-through !important;
1386 | }
1387 |
1388 | .warning {
1389 | padding: 1rem;
1390 | color: var(--color-warning-text);
1391 | background: var(--color-background-warning);
1392 | }
1393 |
1394 | .tsd-kind-project {
1395 | color: var(--color-ts-project);
1396 | }
1397 | .tsd-kind-module {
1398 | color: var(--color-ts-module);
1399 | }
1400 | .tsd-kind-namespace {
1401 | color: var(--color-ts-namespace);
1402 | }
1403 | .tsd-kind-enum {
1404 | color: var(--color-ts-enum);
1405 | }
1406 | .tsd-kind-enum-member {
1407 | color: var(--color-ts-enum-member);
1408 | }
1409 | .tsd-kind-variable {
1410 | color: var(--color-ts-variable);
1411 | }
1412 | .tsd-kind-function {
1413 | color: var(--color-ts-function);
1414 | }
1415 | .tsd-kind-class {
1416 | color: var(--color-ts-class);
1417 | }
1418 | .tsd-kind-interface {
1419 | color: var(--color-ts-interface);
1420 | }
1421 | .tsd-kind-constructor {
1422 | color: var(--color-ts-constructor);
1423 | }
1424 | .tsd-kind-property {
1425 | color: var(--color-ts-property);
1426 | }
1427 | .tsd-kind-method {
1428 | color: var(--color-ts-method);
1429 | }
1430 | .tsd-kind-reference {
1431 | color: var(--color-ts-reference);
1432 | }
1433 | .tsd-kind-call-signature {
1434 | color: var(--color-ts-call-signature);
1435 | }
1436 | .tsd-kind-index-signature {
1437 | color: var(--color-ts-index-signature);
1438 | }
1439 | .tsd-kind-constructor-signature {
1440 | color: var(--color-ts-constructor-signature);
1441 | }
1442 | .tsd-kind-parameter {
1443 | color: var(--color-ts-parameter);
1444 | }
1445 | .tsd-kind-type-parameter {
1446 | color: var(--color-ts-type-parameter);
1447 | }
1448 | .tsd-kind-accessor {
1449 | color: var(--color-ts-accessor);
1450 | }
1451 | .tsd-kind-get-signature {
1452 | color: var(--color-ts-get-signature);
1453 | }
1454 | .tsd-kind-set-signature {
1455 | color: var(--color-ts-set-signature);
1456 | }
1457 | .tsd-kind-type-alias {
1458 | color: var(--color-ts-type-alias);
1459 | }
1460 |
1461 | /* if we have a kind icon, don't color the text by kind */
1462 | .tsd-kind-icon ~ span {
1463 | color: var(--color-text);
1464 | }
1465 |
1466 | /* mobile */
1467 | @media (max-width: 769px) {
1468 | #tsd-toolbar-menu-trigger {
1469 | display: inline-block;
1470 | /* temporary fix to vertically align, for compatibility */
1471 | line-height: 2.5;
1472 | }
1473 | #tsd-toolbar-links {
1474 | display: none;
1475 | }
1476 |
1477 | .container-main {
1478 | display: flex;
1479 | }
1480 | .col-content {
1481 | float: none;
1482 | max-width: 100%;
1483 | width: 100%;
1484 | }
1485 | .col-sidebar {
1486 | position: fixed !important;
1487 | overflow-y: auto;
1488 | -webkit-overflow-scrolling: touch;
1489 | z-index: 1024;
1490 | top: 0 !important;
1491 | bottom: 0 !important;
1492 | left: auto !important;
1493 | right: 0 !important;
1494 | padding: 1.5rem 1.5rem 0 0;
1495 | width: 75vw;
1496 | visibility: hidden;
1497 | background-color: var(--color-background);
1498 | transform: translate(100%, 0);
1499 | }
1500 | .col-sidebar > *:last-child {
1501 | padding-bottom: 20px;
1502 | }
1503 | .overlay {
1504 | content: "";
1505 | display: block;
1506 | position: fixed;
1507 | z-index: 1023;
1508 | top: 0;
1509 | left: 0;
1510 | right: 0;
1511 | bottom: 0;
1512 | background-color: rgba(0, 0, 0, 0.75);
1513 | visibility: hidden;
1514 | }
1515 |
1516 | .to-has-menu .overlay {
1517 | animation: fade-in 0.4s;
1518 | }
1519 |
1520 | .to-has-menu .col-sidebar {
1521 | animation: pop-in-from-right 0.4s;
1522 | }
1523 |
1524 | .from-has-menu .overlay {
1525 | animation: fade-out 0.4s;
1526 | }
1527 |
1528 | .from-has-menu .col-sidebar {
1529 | animation: pop-out-to-right 0.4s;
1530 | }
1531 |
1532 | .has-menu body {
1533 | overflow: hidden;
1534 | }
1535 | .has-menu .overlay {
1536 | visibility: visible;
1537 | }
1538 | .has-menu .col-sidebar {
1539 | visibility: visible;
1540 | transform: translate(0, 0);
1541 | display: flex;
1542 | flex-direction: column;
1543 | gap: 1.5rem;
1544 | max-height: 100vh;
1545 | padding: 1rem 2rem;
1546 | }
1547 | .has-menu .tsd-navigation {
1548 | max-height: 100%;
1549 | }
1550 | .tsd-navigation .tsd-nav-link {
1551 | display: flex;
1552 | }
1553 | }
1554 |
1555 | /* one sidebar */
1556 | @media (min-width: 770px) {
1557 | .container-main {
1558 | display: grid;
1559 | grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
1560 | grid-template-areas: "sidebar content";
1561 | --dim-container-main-margin-y: 2rem;
1562 | }
1563 |
1564 | .tsd-breadcrumb {
1565 | margin-top: 0;
1566 | }
1567 |
1568 | .col-sidebar {
1569 | grid-area: sidebar;
1570 | }
1571 | .col-content {
1572 | grid-area: content;
1573 | padding: 0 1rem;
1574 | }
1575 | }
1576 | @media (min-width: 770px) and (max-width: 1399px) {
1577 | .col-sidebar {
1578 | max-height: calc(
1579 | 100vh - var(--dim-header-height) - var(--dim-footer-height) -
1580 | 2 * var(--dim-container-main-margin-y)
1581 | );
1582 | overflow: auto;
1583 | position: sticky;
1584 | top: calc(
1585 | var(--dim-header-height) + var(--dim-container-main-margin-y)
1586 | );
1587 | }
1588 | .site-menu {
1589 | margin-top: 1rem;
1590 | }
1591 | }
1592 |
1593 | /* two sidebars */
1594 | @media (min-width: 1200px) {
1595 | .container-main {
1596 | grid-template-columns:
1597 | minmax(0, 1fr) minmax(0, 2.5fr) minmax(
1598 | 0,
1599 | 20rem
1600 | );
1601 | grid-template-areas: "sidebar content toc";
1602 | }
1603 |
1604 | .col-sidebar {
1605 | display: contents;
1606 | }
1607 |
1608 | .page-menu {
1609 | grid-area: toc;
1610 | padding-left: 1rem;
1611 | }
1612 | .site-menu {
1613 | grid-area: sidebar;
1614 | }
1615 |
1616 | .site-menu {
1617 | margin-top: 0rem;
1618 | }
1619 |
1620 | .page-menu,
1621 | .site-menu {
1622 | max-height: calc(
1623 | 100vh - var(--dim-header-height) - var(--dim-footer-height) -
1624 | 2 * var(--dim-container-main-margin-y)
1625 | );
1626 | overflow: auto;
1627 | position: sticky;
1628 | top: calc(
1629 | var(--dim-header-height) + var(--dim-container-main-margin-y)
1630 | );
1631 | }
1632 | }
1633 | }
1634 |
```