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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tests/core/*
!tests/core/stage-learner.test.ts
!tests/core/dampening.test.ts
!tests/core/classify.test.ts
!tests/core/vault-path.test.ts
!tests/mcp/
tests/mcp/*
!tests/mcp/server.test.ts
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ ori bridge status [--json] # Inspect
ori bridge <target> --uninstall # Remove Ori config for a target
```

Path-taking commands treat relative file paths as vault-relative. Absolute
paths continue to work unchanged.

---

## Vault Structure
Expand Down
2 changes: 1 addition & 1 deletion src/cli/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ export async function runServeMcp(startDir: string, vaultOverride?: string) {
path: z.string().describe("Path to note file"),
},
async ({ path }) => {
const result = await runValidate({ notePath: path });
const result = await runValidate({ notePath: path, startDir: vaultDir });
return textResult(result);
}
);
Expand Down
9 changes: 6 additions & 3 deletions src/cli/validate.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import path from "node:path";
import { loadConfig, resolveTemplatePath } from "../core/config.js";
import { findVaultRoot, getVaultPaths } from "../core/vault.js";
import { findVaultRoot, getVaultPaths, resolveVaultPath } from "../core/vault.js";
import { validateNoteAgainstSchema } from "../core/schema.js";
import { parseFrontmatter } from "../core/frontmatter.js";
import { promises as fs } from "node:fs";

export type ValidateOptions = {
notePath: string;
startDir?: string;
};

export async function runValidate(
options: ValidateOptions
): Promise<{ success: boolean; errors: string[]; warnings: string[] }>
{
const absoluteNotePath = path.resolve(options.notePath);
const vaultRoot = await findVaultRoot(path.dirname(absoluteNotePath));
const vaultRoot = path.isAbsolute(options.notePath)
? await findVaultRoot(path.dirname(path.resolve(options.notePath)))
: await findVaultRoot(options.startDir ?? process.cwd());
const absoluteNotePath = resolveVaultPath(vaultRoot, options.notePath);
const vaultPaths = getVaultPaths(vaultRoot);
const config = await loadConfig(vaultPaths.config);

Expand Down
6 changes: 6 additions & 0 deletions src/core/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ export function getVaultPaths(root: string): VaultPaths {
};
}

export function resolveVaultPath(vaultRoot: string, inputPath: string): string {
return path.isAbsolute(inputPath)
? path.resolve(inputPath)
: path.resolve(vaultRoot, inputPath);
}

export async function listNoteTitles(notesDir: string): Promise<string[]> {
try {
const entries = await fs.readdir(notesDir, { withFileTypes: true });
Expand Down
68 changes: 68 additions & 0 deletions tests/core/vault-path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it } from "vitest";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { runInit } from "../../src/cli/init.js";
import { runValidate } from "../../src/cli/validate.js";
import { resolveVaultPath } from "../../src/core/vault.js";

async function makeTempDir(prefix: string): Promise<string> {
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
}

describe("vault-relative paths", () => {
it("resolves relative paths against the vault root", () => {
const vaultRoot = path.resolve("tmp-vault");
expect(resolveVaultPath(vaultRoot, "notes/example.md")).toBe(
path.resolve(vaultRoot, "notes/example.md"),
);
});

it("preserves absolute paths", () => {
const absolutePath = path.resolve("elsewhere", "example.md");
expect(resolveVaultPath(path.resolve("tmp-vault"), absolutePath)).toBe(absolutePath);
});

it("validates vault-relative note paths when the caller is outside the vault", async () => {
const vaultDir = await makeTempDir("ori-validate-vault-");
const projectDir = await makeTempDir("ori-validate-project-");
const originalCwd = process.cwd();

try {
await runInit({ targetDir: vaultDir });
process.chdir(projectDir);
const notePath = path.join(vaultDir, "notes", "vault-relative-validation.md");
await fs.writeFile(
notePath,
[
"---",
"description: Validates vault-relative path handling",
"type: insight",
"project:",
" - cli",
"status: active",
"created: 2026-05-05",
"---",
"",
"# Vault relative validation works",
"",
"Relative paths should resolve inside the vault even when the caller is elsewhere.",
"",
].join("\n"),
"utf8",
);

const result = await runValidate({
startDir: vaultDir,
notePath: "notes/vault-relative-validation.md",
});

expect(result.success).toBe(true);
expect(result.errors).toEqual([]);
} finally {
process.chdir(originalCwd);
await fs.rm(vaultDir, { recursive: true, force: true });
await fs.rm(projectDir, { recursive: true, force: true });
}
});
});
Loading