Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/cli/src/commands/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ export async function search(term: string, opts: SearchOpts) {

spinner.stop();

// Filter results by scope when loaded from cache (cache contains all scopes)
if (scope !== "all") {
const scopeDir = scope === "wiki" ? "/wiki/" : "/raw/";
results = results.filter((r) => r.path.includes(scopeDir));
}

if (opts.json) {
console.log(JSON.stringify(results, null, 2));
return;
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands/watch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ describe("watch: HTTP server /ingest", () => {
});

test("builds correct markdown without url", () => {
const body = { content: "Body text", title: "Title Only" };
const body: { content: string; title: string; url?: string } = {
content: "Body text",
title: "Title Only",
};
const fullContent = body.title
? `# ${body.title}\n\n${body.url ? `Source: ${body.url}\n\n` : ""}${body.content}`
: body.content;
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/compile/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,35 @@ async function compileVaultInner(

await saveManifest(root, manifest);

// Update search index with newly compiled wiki articles
try {
const { SearchIndex } = await import("../search/engine.js");
const searchIndex = new SearchIndex();
await searchIndex.load(root);
for (const op of allOperations) {
if ((op.op === "create" || op.op === "update") && op.content) {
const { frontmatter, body } = parseFrontmatter(op.content);
const title =
(frontmatter.title as string) ?? op.path.split("/").pop()?.replace(/\.md$/, "") ?? "";
const tags = Array.isArray(frontmatter.tags) ? (frontmatter.tags as string[]) : [];
const date =
(frontmatter.created as string) ??
(frontmatter.updated as string) ??
new Date().toISOString().slice(0, 10);
searchIndex.addDocument({
path: join(root, op.path),
title,
content: body,
tags,
date,
});
}
}
await searchIndex.save(root);
} catch {
// Search index update is best-effort — don't fail the compile
}

// Log compile activity
const parts = [`${sourcesToCompile.length} sources compiled`];
if (totalCreated > 0) parts.push(`${totalCreated} articles created`);
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/daemon/folder-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export function matchGlob(filename: string, pattern: string): boolean {

// Handle *.{ext1,ext2} pattern
const braceMatch = pattern.match(/^\*\.\{(.+)\}$/);
if (braceMatch) {
if (braceMatch?.[1]) {
const extensions = braceMatch[1].split(",").map((e) => `.${e.trim()}`);
return extensions.includes(extname(filename).toLowerCase());
}

// Handle *.ext pattern
const extMatch = pattern.match(/^\*(\..+)$/);
if (extMatch) {
if (extMatch?.[1]) {
return extname(filename).toLowerCase() === extMatch[1].toLowerCase();
}

Expand Down Expand Up @@ -105,7 +105,7 @@ export async function scanFolder(folder: WatchFolder): Promise<string[]> {
try {
const files = await readdir(absPath, { recursive: folder.recursive });
for (const file of files) {
const name = typeof file === "string" ? file : file.toString();
const name = String(file);
const base = name.includes("/") ? name.split("/").pop()! : name;
if (!base.startsWith(".") && matchGlob(base, folder.glob)) {
matches.push(join(absPath, name));
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/recovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export async function repairVault(root: string): Promise<RecoveryIssue[]> {
.sort()
.reverse();

if (backups.length > 0) {
if (backups[0]) {
const latest = join(backupsDir, backups[0]);
try {
const backup = await readFile(latest, "utf-8");
Expand Down
17 changes: 8 additions & 9 deletions packages/core/src/search/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export class SearchIndex {
path: d.path,
title: d.title,
snippet: d.content.slice(0, 200),
tokenCount: d.tokens.length,
tokenCount: d.tokenCount,
termFreqs: [...d.termFreqs.entries()],
tags: d.tags,
date: d.date,
Expand Down Expand Up @@ -436,9 +436,10 @@ export class SearchIndex {

try {
const raw = await readFile(path, "utf-8");
const data = JSON.parse(raw) as SerializedIndex & { version: number };
const data = JSON.parse(raw) as SerializedIndex;
const version = data.version as number;

if (data.version !== 1 && data.version !== 2) return false;
if (version !== 1 && version !== 2) return false;

this.documents = data.documents.map((d) => ({
path: d.path,
Expand Down Expand Up @@ -507,12 +508,10 @@ export class SearchIndex {
const docFreq = new Map<string, number>();

for (const doc of this.documents) {
const seen = new Set<string>();
for (const token of doc.tokens) {
if (!seen.has(token)) {
docFreq.set(token, (docFreq.get(token) ?? 0) + 1);
seen.add(token);
}
// Use termFreqs.keys() instead of tokens — loaded docs have tokens: []
// but termFreqs is always populated (from serialization or addDocument)
for (const term of doc.termFreqs.keys()) {
docFreq.set(term, (docFreq.get(term) ?? 0) + 1);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/skills/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,13 @@ async function installFromGitHub(
skillsDir: string,
): Promise<InstallResult> {
// repo format: "user/repo" or "user/repo#branch"
const [repoPath, branch] = repo.split("#");
const [repoPath, branch] = repo.split("#") as [string, string | undefined];
const parts = repoPath.split("/");
if (parts.length !== 2) {
if (parts.length !== 2 || !parts[1]) {
throw new Error(`Invalid GitHub repo format: "${repo}". Expected "user/repo"`);
}

const repoName = parts[1];
const repoName: string = parts[1];
const destDir = join(skillsDir, repoName);

if (existsSync(destDir)) {
Expand Down
Loading