Skip to content
Merged
521 changes: 74 additions & 447 deletions README.md

Large diffs are not rendered by default.

594 changes: 594 additions & 0 deletions USER_GUIDE.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/cli/src/__tests__/create-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { promises as fs } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execSync } from "node:child_process";

describe("create command", () => {
let testDir: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ async function createLocalFolderDocset(options: any): Promise<DocsetConfig> {
if (!stat.isDirectory()) {
throw new Error(`Path is not a directory: ${options.path}`);
}
} catch (error) {
} catch {
throw new Error(`Path does not exist: ${options.path}`);
}

Expand Down
43 changes: 12 additions & 31 deletions packages/cli/src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,14 @@ function displaySummary(statuses: DocsetStatus[]) {
}

if (!initialized) {
console.log(`${chalk.bold(docset.id)} (${docset.name})`);
console.log(
`${chalk.yellow("⚠️")} ${chalk.bold(docset.id)} - ${chalk.yellow("Not initialized")}`,
);
console.log(
chalk.gray(` ${docset.sources?.length || 0} source(s) configured`),
chalk.gray(
` Not initialized | ${docset.sources?.length || 0} source(s) configured`,
),
);
console.log();
console.log(chalk.blue(` 💡 Run: agentic-knowledge init ${docset.id}`));
continue;
}

Expand All @@ -194,38 +196,17 @@ function displaySummary(statuses: DocsetStatus[]) {
continue;
}

// Calculate status
const lastActivity = metadata.last_refreshed || metadata.initialized_at;
const lastActivityTime = new Date(lastActivity);
const timeSince = Date.now() - lastActivityTime.getTime();
const hoursSince = timeSince / (1000 * 60 * 60);
const daysSince = timeSince / (1000 * 60 * 60 * 24);

let timeDisplay;
let statusIcon;

if (hoursSince < 1) {
timeDisplay = `${Math.round(hoursSince * 60)} minutes ago`;
statusIcon = chalk.green("✅");
} else if (hoursSince < 24) {
timeDisplay = `${Math.round(hoursSince)} hours ago`;
statusIcon = chalk.green("✅");
} else if (daysSince < 7) {
timeDisplay = `${Math.round(daysSince)} days ago`;
statusIcon = chalk.yellow("⚠️");
} else {
timeDisplay = `${Math.round(daysSince)} days ago`;
statusIcon = chalk.red("🔄");
}
// Format initialization date
const initDate = new Date(metadata.initialized_at);
const dateDisplay = initDate.toISOString().split("T")[0]; // YYYY-MM-DD format

console.log(
`${statusIcon} ${chalk.bold(docset.id)} - ${chalk.gray(metadata.total_files)} files`,
);
console.log(`${chalk.bold(docset.id)} (${docset.name})`);
console.log(
chalk.gray(
` Last updated: ${timeDisplay} | ${sources.length}/${metadata.sources_count} sources loaded`,
` Initialized | ${metadata.total_files} files | ${sources.length}/${metadata.sources_count} source(s) loaded`,
),
);
console.log(chalk.gray(` Initialized: ${dateDisplay}`));
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,16 @@

/**
* Error types that can occur in the core system
* Note: Linter may warn about "unused" enum values, but these are used throughout
* the codebase as ErrorType.CONFIG_NOT_FOUND, etc. The warnings are false positives.
*/
export enum ErrorType {
CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND",

Check warning on line 126 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'CONFIG_NOT_FOUND' is defined but never used. Allowed unused vars must match /^_/u
CONFIG_INVALID = "CONFIG_INVALID",

Check warning on line 127 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'CONFIG_INVALID' is defined but never used. Allowed unused vars must match /^_/u
DOCSET_NOT_FOUND = "DOCSET_NOT_FOUND",

Check warning on line 128 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'DOCSET_NOT_FOUND' is defined but never used. Allowed unused vars must match /^_/u
PATH_INVALID = "PATH_INVALID",

Check warning on line 129 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'PATH_INVALID' is defined but never used. Allowed unused vars must match /^_/u
TEMPLATE_ERROR = "TEMPLATE_ERROR",

Check warning on line 130 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'TEMPLATE_ERROR' is defined but never used. Allowed unused vars must match /^_/u
YAML_PARSE_ERROR = "YAML_PARSE_ERROR",

Check warning on line 131 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'YAML_PARSE_ERROR' is defined but never used. Allowed unused vars must match /^_/u
}

/**
Expand All @@ -134,9 +136,9 @@
*/
export class KnowledgeError extends Error {
constructor(
public type: ErrorType,

Check warning on line 139 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'type' is defined but never used. Allowed unused args must match /^_/u
message: string,
public context?: Record<string, unknown>,

Check warning on line 141 in packages/core/src/types.ts

View workflow job for this annotation

GitHub Actions / test

'context' is defined but never used. Allowed unused args must match /^_/u
) {
super(message);
this.name = "KnowledgeError";
Expand Down
52 changes: 51 additions & 1 deletion packages/mcp-server/src/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ docsets:
const server = createAgenticKnowledgeServer();

// Create a mock request handler to capture tool descriptions
let toolsResponse: any = null;
let _toolsResponse: any = null;

// We can't easily test the actual ListToolsRequestSchema handler directly,
// but we can verify the server creates without errors and our configuration loads
Expand Down Expand Up @@ -149,5 +149,55 @@ docsets:
// Server creation should succeed even if config loading will fail later
// This tests that server initialization is robust
});

it("should fail when searching uninitialized docset", async () => {
// Create a docset with git_repo source that is NOT initialized
const uninitializedConfig = `
version: "1.0"
docsets:
- id: "uninitialized-docs"
name: "Uninitialized Documentation"
description: "A docset that hasn't been initialized yet"
sources:
- type: git_repo
url: "https://github.com/example/repo.git"
local_path: ".knowledge/docsets/uninitialized-docs"
`;
await fs.writeFile(tempConfigPath, uninitializedConfig);

const server = createAgenticKnowledgeServer();

// Create the docset directory (simulating what the create command does)
// but don't create the .agentic-metadata.json file (which init command creates)
const docsetDir = join(
tempDir,
".knowledge",
"docsets",
"uninitialized-docs",
);
await fs.mkdir(docsetDir, { recursive: true });

// Try to search the uninitialized docset
const callToolHandler = (server as any)._requestHandlers.get(
"tools/call",
);
expect(callToolHandler).toBeDefined();

const result = await callToolHandler({
method: "tools/call",
params: {
name: "search_docs",
arguments: {
docset_id: "uninitialized-docs",
keywords: "test",
},
},
});

// Should return an error
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("not initialized");
expect(result.content[0].text).toContain("agentic-knowledge init");
});
});
});
13 changes: 13 additions & 0 deletions packages/mcp-server/src/__tests__/web-sources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ template: "Search for '{{keywords}}' in {{local_path}}. Also consider: {{general
"# Test Documentation\n\nThis simulates downloaded web content.",
);

// Create metadata file (simulating what init command creates)
const metadata = {
docset_id: "web-source-docs",
docset_name: "Web Source Documentation",
initialized_at: new Date().toISOString(),
total_files: 1,
sources_count: 1,
};
await fs.writeFile(
join(webSourceDir, ".agentic-metadata.json"),
JSON.stringify(metadata, null, 2),
);

// Mock process.cwd to return our temp directory
vi.spyOn(process, "cwd").mockReturnValue(tempDir);

Expand Down
Loading