From 19fd46027536de1b92e2f7ea8285cce2e1620c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Wed, 4 Mar 2026 13:39:43 +0100 Subject: [PATCH] fix(core): correct local folder docset initialization path ## Intent Fix critical bug where local folder docsets could not be initialized correctly. The `calculateLocalPath()` function was returning the source path (e.g., `./docs`) for `local_folder` type docsets instead of the standard docset directory (`.knowledge/docsets/{id}`). This caused the init command to use the source directory as the target, potentially deleting source files with `--force` and creating circular symlinks. ## Key changes - `calculateLocalPath()` now consistently returns `.knowledge/docsets/{id}` for all source types including `local_folder`, matching behavior of `git_repo` and `archive` types - Local folder initialization now correctly creates symlinks in the docset directory pointing to source folders, preserving source files - Updated tests to expect the corrected behavior - Fixed invalid source type `zip` to `archive` in example config - Added `local-docs` docset as working example of local folder configuration ## Dependencies and side effects - Existing local folder docsets will need to be re-initialized after this fix - The symlink structure changes from source-path-based to standard docset directory, ensuring consistency across all source types - MCP server path resolution for local folders remains compatible as it already uses the standard docset directory path --- .knowledge/config.yaml | 9 ++- .../src/__tests__/path-calculator.test.ts | 62 +++++++++++-------- packages/core/src/paths/calculator.ts | 24 +++---- 3 files changed, 52 insertions(+), 43 deletions(-) diff --git a/.knowledge/config.yaml b/.knowledge/config.yaml index a6b36e2..41806b7 100644 --- a/.knowledge/config.yaml +++ b/.knowledge/config.yaml @@ -28,6 +28,13 @@ docsets: Zip archive: https://bahnhub.tech.rz.db.de/artifactory/pipeship-generic-stage-dev-local/docs-as-code/latest/pipeship-docs.tar.gz sources: - - type: zip + - type: archive url: >- https://bahnhub.tech.rz.db.de/artifactory/pipeship-generic-stage-dev-local/docs-as-code/latest/pipeship-docs.tar.gz + - id: local-docs + name: Local Documentation + description: "Local documentation: ./docs" + sources: + - type: local_folder + paths: + - ./docs diff --git a/packages/core/src/__tests__/path-calculator.test.ts b/packages/core/src/__tests__/path-calculator.test.ts index e6ab32f..cc1664c 100644 --- a/packages/core/src/__tests__/path-calculator.test.ts +++ b/packages/core/src/__tests__/path-calculator.test.ts @@ -4,7 +4,7 @@ import { describe, test, expect, beforeEach, afterEach } from "vitest"; import { promises as fs } from "node:fs"; -import { join, resolve, isAbsolute } from "node:path"; +import { join, resolve } from "node:path"; import { tmpdir } from "node:os"; import { calculateLocalPath, @@ -34,6 +34,8 @@ describe("Path Calculation", () => { describe("calculateLocalPath", () => { test("should handle absolute paths", () => { + // Even with absolute source paths, calculateLocalPath should return + // the standard docset directory for consistency const docset: DocsetConfig = { id: "test", name: "Test", @@ -46,11 +48,14 @@ describe("Path Calculation", () => { }; const result = calculateLocalPath(docset, configPath); - expect(result).toBe("/absolute/path/to/docs"); - expect(isAbsolute(result)).toBe(true); + // Should return .knowledge/docsets/{id}, not the source path + const expected = join(tempDir, ".knowledge", "docsets", "test"); + expect(result).toBe(expected); }); - test("should resolve relative paths from project root", () => { + test("should resolve relative paths to standard docset directory", () => { + // For local_folder, calculateLocalPath should return the standard + // docset directory .knowledge/docsets/{id} where symlinks will be created const docset: DocsetConfig = { id: "test", name: "Test", @@ -63,10 +68,12 @@ describe("Path Calculation", () => { }; const result = calculateLocalPath(docset, configPath); - expect(result).toBe("docs"); + const expected = join(tempDir, ".knowledge", "docsets", "test"); + expect(result).toBe(expected); }); - test("should resolve parent directory paths", () => { + test("should resolve parent directory paths to standard docset directory", () => { + // Even with parent directory paths, should return standard docset directory const docset: DocsetConfig = { id: "test", name: "Test", @@ -79,10 +86,12 @@ describe("Path Calculation", () => { }; const result = calculateLocalPath(docset, configPath); - expect(result).toBe("../shared-docs"); + const expected = join(tempDir, ".knowledge", "docsets", "test"); + expect(result).toBe(expected); }); - test("should normalize complex relative paths", () => { + test("should normalize complex relative paths to standard docset directory", () => { + // Even with complex paths, should return standard docset directory const docset: DocsetConfig = { id: "test", name: "Test", @@ -95,7 +104,8 @@ describe("Path Calculation", () => { }; const result = calculateLocalPath(docset, configPath); - expect(result).toBe("guides"); + const expected = join(tempDir, ".knowledge", "docsets", "test"); + expect(result).toBe(expected); }); test("should handle invalid config path gracefully", () => { @@ -115,6 +125,7 @@ describe("Path Calculation", () => { }); test("should handle multiple paths in local_folder source", () => { + // Uses the docset id for the directory, not the source paths const docset: DocsetConfig = { id: "multi-docs", name: "Multiple Documentation", @@ -127,7 +138,8 @@ describe("Path Calculation", () => { }; const result = calculateLocalPath(docset, configPath); - expect(result).toBe("docs"); + const expected = join(tempDir, ".knowledge", "docsets", "multi-docs"); + expect(result).toBe(expected); }); test("should handle git repo without optional fields", () => { @@ -272,16 +284,15 @@ describe("Path Calculation", () => { ], }; - // Calculate path + // Calculate path - should return standard docset directory const calculatedPath = calculateLocalPath(docset, configPath); - // Should return relative path - expect(calculatedPath).toBe("docs"); + // Should return .knowledge/docsets/{id} path + const expected = join(tempDir, ".knowledge", "docsets", "test"); + expect(calculatedPath).toBe(expected); - // Validate actual path exists (resolve relative to project root) - const absolutePath = resolve(tempDir, calculatedPath); - const pathExists = await validatePath(absolutePath); - expect(pathExists).toBe(true); + // The docset directory won't exist until init is run, + // but the path calculation should be consistent }); test("should handle complex nested directory structure", async () => { @@ -314,14 +325,15 @@ describe("Path Calculation", () => { const result = calculateLocalPath(docset, projectConfig); - // Should return relative path - expect(result).toBe("src/components"); - - // Validate actual path exists - const projectRoot = join(tempDir, "project"); - const absolutePath = resolve(projectRoot, result); - const validated = await validatePath(absolutePath); - expect(validated).toBe(true); + // Should return standard docset directory + const expected = join( + tempDir, + "project", + ".knowledge", + "docsets", + "components", + ); + expect(result).toBe(expected); }); }); }); diff --git a/packages/core/src/paths/calculator.ts b/packages/core/src/paths/calculator.ts index 5516121..667168b 100644 --- a/packages/core/src/paths/calculator.ts +++ b/packages/core/src/paths/calculator.ts @@ -2,14 +2,7 @@ * Path calculation utilities */ -import { - resolve, - dirname, - isAbsolute, - join, - normalize, - relative, -} from "node:path"; +import { dirname, join, normalize, relative } from "node:path"; import { promises as fs } from "node:fs"; import * as fsSync from "node:fs"; import * as pathModule from "node:path"; @@ -42,12 +35,15 @@ export function calculateLocalPath( } if (primarySource.type === "local_folder") { - // For local folders, return relative path from project root + // For local folders, use standardized path: .knowledge/docsets/{id} + // This ensures consistency with git_repo and archive types, + // and prevents accidentally modifying source directories during init if (!primarySource.paths || primarySource.paths.length === 0) { throw new Error( `Local folder source for docset '${docset.id}' has no paths configured`, ); } + // Validate that at least one path is configured const firstPath = primarySource.paths[0]; if (!firstPath) { throw new Error( @@ -55,14 +51,8 @@ export function calculateLocalPath( ); } - if (isAbsolute(firstPath)) { - // If absolute path, return as-is - return normalize(firstPath); - } - - // If relative path, resolve from project root and return relative - const resolvedPath = resolve(projectRoot, firstPath); - return relative(projectRoot, resolvedPath) || "."; + // Return the standard docset directory, symlinks will be created there + return join(configDir, "docsets", docset.id); } if (primarySource.type === "git_repo") {