@jayjiang/byoao 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/system-transform.js +19 -1
- package/dist/hooks/system-transform.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/graph-health.js +25 -0
- package/dist/tools/graph-health.js.map +1 -0
- package/dist/tools/note-read.js +19 -0
- package/dist/tools/note-read.js.map +1 -0
- package/dist/tools/search-vault.js +22 -0
- package/dist/tools/search-vault.js.map +1 -0
- package/dist/vault/__tests__/graph-health.test.js +102 -0
- package/dist/vault/__tests__/graph-health.test.js.map +1 -0
- package/dist/vault/__tests__/note-read.test.js +71 -0
- package/dist/vault/__tests__/note-read.test.js.map +1 -0
- package/dist/vault/__tests__/search-vault.test.js +93 -0
- package/dist/vault/__tests__/search-vault.test.js.map +1 -0
- package/dist/vault/__tests__/vault-detect.test.js +54 -0
- package/dist/vault/__tests__/vault-detect.test.js.map +1 -0
- package/dist/vault/graph-health.js +83 -0
- package/dist/vault/graph-health.js.map +1 -0
- package/dist/vault/note-read.js +70 -0
- package/dist/vault/note-read.js.map +1 -0
- package/dist/vault/retrieval-types.js +5 -0
- package/dist/vault/retrieval-types.js.map +1 -0
- package/dist/vault/search-vault.js +87 -0
- package/dist/vault/search-vault.js.map +1 -0
- package/dist/vault/vault-detect.js +29 -0
- package/dist/vault/vault-detect.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { detectVaultContext } from "../vault/vault-detect.js";
|
|
3
4
|
/**
|
|
4
5
|
* Read AGENT.md from the current working directory.
|
|
5
6
|
* Returns the content if found, null otherwise.
|
|
@@ -22,13 +23,30 @@ export function readAgentMdFromCwd() {
|
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
|
-
* Hook: inject AGENT.md into system prompt.
|
|
26
|
+
* Hook: inject AGENT.md and vault retrieval context into system prompt.
|
|
26
27
|
* Mutating pattern — modifies output.system in place.
|
|
27
28
|
*/
|
|
28
29
|
export async function systemTransformHook(_input, output) {
|
|
30
|
+
// 1. Inject AGENT.md content
|
|
29
31
|
const agentMd = readAgentMdFromCwd();
|
|
30
32
|
if (agentMd) {
|
|
31
33
|
output.system.push(`\n---\n## BYOAO Vault Context (from AGENT.md)\n\n${agentMd}`);
|
|
32
34
|
}
|
|
35
|
+
// 2. Inject vault retrieval tool guidance
|
|
36
|
+
const vaultPath = detectVaultContext(process.cwd());
|
|
37
|
+
if (vaultPath) {
|
|
38
|
+
output.system.push([
|
|
39
|
+
"\n---",
|
|
40
|
+
"## BYOAO Vault Retrieval Context",
|
|
41
|
+
"",
|
|
42
|
+
`You are in a BYOAO Obsidian vault at: ${vaultPath}`,
|
|
43
|
+
"For vault knowledge queries (notes, tags, links, properties, graph health), prefer the BYOAO vault tools:",
|
|
44
|
+
"- byoao_search_vault — text search across vault notes",
|
|
45
|
+
"- byoao_note_read — read a specific note by name",
|
|
46
|
+
"- byoao_graph_health — find orphans, unresolved links, dead ends",
|
|
47
|
+
"",
|
|
48
|
+
"Use grep/rg only for source code searches or when BYOAO tools return runtime_unavailable.",
|
|
49
|
+
].join("\n"));
|
|
50
|
+
}
|
|
33
51
|
}
|
|
34
52
|
//# sourceMappingURL=system-transform.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-transform.js","sourceRoot":"","sources":["../../src/hooks/system-transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"system-transform.js","sourceRoot":"","sources":["../../src/hooks/system-transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC;KAC3C,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,OAAO,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA8C,EAC9C,MAA4B;IAE5B,6BAA6B;IAC7B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB,oDAAoD,OAAO,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,0CAA0C;IAC1C,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB;YACE,OAAO;YACP,kCAAkC;YAClC,EAAE;YACF,yCAAyC,SAAS,EAAE;YACpD,2GAA2G;YAC3G,uDAAuD;YACvD,kDAAkD;YAClD,kEAAkE;YAClE,EAAE;YACF,2FAA2F;SAC5F,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ import { byoao_add_glossary_term } from "./tools/add-glossary-term.js";
|
|
|
8
8
|
import { byoao_vault_status } from "./tools/vault-status.js";
|
|
9
9
|
import { byoao_vault_doctor } from "./tools/vault-doctor.js";
|
|
10
10
|
import { byoao_switch_provider } from "./tools/switch-provider.js";
|
|
11
|
+
import { byoao_search_vault } from "./tools/search-vault.js";
|
|
12
|
+
import { byoao_note_read } from "./tools/note-read.js";
|
|
13
|
+
import { byoao_graph_health } from "./tools/graph-health.js";
|
|
11
14
|
import { systemTransformHook } from "./hooks/system-transform.js";
|
|
12
15
|
import { getIdleSuggestion } from "./hooks/idle-suggestions.js";
|
|
13
16
|
const BYOAOPlugin = async (ctx) => {
|
|
@@ -22,6 +25,9 @@ const BYOAOPlugin = async (ctx) => {
|
|
|
22
25
|
byoao_vault_status,
|
|
23
26
|
byoao_vault_doctor,
|
|
24
27
|
byoao_switch_provider,
|
|
28
|
+
byoao_search_vault,
|
|
29
|
+
byoao_note_read,
|
|
30
|
+
byoao_graph_health,
|
|
25
31
|
},
|
|
26
32
|
"experimental.chat.system.transform": systemTransformHook,
|
|
27
33
|
event: async ({ event }) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kEAAkE;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,GAAW,KAAK,EAAE,GAAG,EAAE,EAAE;IACxC,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE;YACJ,gBAAgB;YAChB,gBAAgB;YAChB,iBAAiB;YACjB,uBAAuB;YACvB,kBAAkB;YAClB,kBAAkB;YAClB,qBAAqB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,sDAAsD;AACtD,kEAAkE;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,GAAW,KAAK,EAAE,GAAG,EAAE,EAAE;IACxC,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE;YACJ,gBAAgB;YAChB,gBAAgB;YAChB,iBAAiB;YACjB,uBAAuB;YACvB,kBAAkB;YAClB,kBAAkB;YAClB,qBAAqB;YACrB,kBAAkB;YAClB,eAAe;YACf,kBAAkB;SACnB;QACD,oCAAoC,EAAE,mBAAmB;QACzD,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAClC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;gBACvC,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;wBACnB,IAAI,EAAE;4BACJ,KAAK,EAAE,OAAO;4BACd,OAAO,EAAE,UAAU;4BACnB,OAAO,EAAE,MAAM;4BACf,QAAQ,EAAE,IAAI;yBACf;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { getGraphHealth } from "../vault/graph-health.js";
|
|
3
|
+
export const byoao_graph_health = tool({
|
|
4
|
+
description: "Diagnose Obsidian vault graph health — find orphan notes (no links in or out), unresolved links (broken wikilinks), and dead-end notes. Uses Obsidian CLI for vault-aware analysis.",
|
|
5
|
+
args: {
|
|
6
|
+
vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
|
|
7
|
+
check: tool.schema
|
|
8
|
+
.enum(["all", "orphans", "unresolved", "deadends"])
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Which check to run: 'all' (default), 'orphans', 'unresolved', or 'deadends'"),
|
|
11
|
+
limit: tool.schema
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Maximum number of results to return (default: 20)"),
|
|
15
|
+
},
|
|
16
|
+
async execute(args) {
|
|
17
|
+
const result = await getGraphHealth({
|
|
18
|
+
vaultPath: args.vaultPath,
|
|
19
|
+
check: args.check,
|
|
20
|
+
limit: args.limit,
|
|
21
|
+
});
|
|
22
|
+
return JSON.stringify(result, null, 2);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=graph-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-health.js","sourceRoot":"","sources":["../../src/tools/graph-health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACrC,WAAW,EACT,qLAAqL;IACvL,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;aAClD,QAAQ,EAAE;aACV,QAAQ,CACP,6EAA6E,CAC9E;QACH,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;YAClC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAkE;YAC9E,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { readNote } from "../vault/note-read.js";
|
|
3
|
+
export const byoao_note_read = tool({
|
|
4
|
+
description: "Read a specific note from an Obsidian vault by name. Uses Obsidian CLI to resolve and read the note. Preferred over cat/Read for Obsidian vault notes.",
|
|
5
|
+
args: {
|
|
6
|
+
vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
|
|
7
|
+
file: tool.schema
|
|
8
|
+
.string()
|
|
9
|
+
.describe("Note name to read (without .md extension, e.g. 'Refund Automation')"),
|
|
10
|
+
},
|
|
11
|
+
async execute(args) {
|
|
12
|
+
const result = await readNote({
|
|
13
|
+
vaultPath: args.vaultPath,
|
|
14
|
+
file: args.file,
|
|
15
|
+
});
|
|
16
|
+
return JSON.stringify(result, null, 2);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=note-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-read.js","sourceRoot":"","sources":["../../src/tools/note-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EACT,wJAAwJ;IAC1J,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,IAAI,EAAE,IAAI,CAAC,MAAM;aACd,MAAM,EAAE;aACR,QAAQ,CAAC,qEAAqE,CAAC;KACnF;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;YAC5B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { searchVault } from "../vault/search-vault.js";
|
|
3
|
+
export const byoao_search_vault = tool({
|
|
4
|
+
description: "Search an Obsidian vault for notes matching a text query. Uses Obsidian CLI search:context for vault-aware results. Preferred over grep/rg for Obsidian vault knowledge queries about notes, tags, and content.",
|
|
5
|
+
args: {
|
|
6
|
+
vaultPath: tool.schema.string().describe("Absolute path to the Obsidian vault"),
|
|
7
|
+
query: tool.schema.string().describe("Text query to search for in vault notes"),
|
|
8
|
+
limit: tool.schema
|
|
9
|
+
.number()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Maximum number of results to return (default: 20)"),
|
|
12
|
+
},
|
|
13
|
+
async execute(args) {
|
|
14
|
+
const result = await searchVault({
|
|
15
|
+
vaultPath: args.vaultPath,
|
|
16
|
+
query: args.query,
|
|
17
|
+
limit: args.limit,
|
|
18
|
+
});
|
|
19
|
+
return JSON.stringify(result, null, 2);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=search-vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-vault.js","sourceRoot":"","sources":["../../src/tools/search-vault.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACrC,WAAW,EACT,iNAAiN;IACnN,IAAI,EAAE;QACJ,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;QAC/E,KAAK,EAAE,IAAI,CAAC,MAAM;aACf,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,mDAAmD,CAAC;KACjE;IACD,KAAK,CAAC,OAAO,CAAC,IAAI;QAChB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
vi.mock("../obsidian-cli.js", () => ({
|
|
3
|
+
isObsidianCliAvailable: vi.fn(),
|
|
4
|
+
execObsidianCmd: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
import { getGraphHealth } from "../graph-health.js";
|
|
7
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
|
|
8
|
+
const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
|
|
9
|
+
const mockExecCmd = vi.mocked(execObsidianCmd);
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.resetAllMocks();
|
|
12
|
+
});
|
|
13
|
+
describe("getGraphHealth", () => {
|
|
14
|
+
it("returns runtime_unavailable when CLI is not available", async () => {
|
|
15
|
+
mockIsAvailable.mockReturnValue(false);
|
|
16
|
+
const result = await getGraphHealth({ vaultPath: "/vault" });
|
|
17
|
+
expect(result.status).toBe("runtime_unavailable");
|
|
18
|
+
expect(result.mode).toBe("graph-health");
|
|
19
|
+
expect(result.diagnostics).toContain("Obsidian CLI not available");
|
|
20
|
+
});
|
|
21
|
+
it("runs all three checks by default", async () => {
|
|
22
|
+
mockIsAvailable.mockReturnValue(true);
|
|
23
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
24
|
+
await getGraphHealth({ vaultPath: "/vault" });
|
|
25
|
+
expect(mockExecCmd).toHaveBeenCalledTimes(3);
|
|
26
|
+
expect(mockExecCmd).toHaveBeenCalledWith(["orphans", "--vault", "/vault"]);
|
|
27
|
+
expect(mockExecCmd).toHaveBeenCalledWith(["unresolved", "--vault", "/vault"]);
|
|
28
|
+
expect(mockExecCmd).toHaveBeenCalledWith(["deadends", "--vault", "/vault"]);
|
|
29
|
+
});
|
|
30
|
+
it("runs only the specified check", async () => {
|
|
31
|
+
mockIsAvailable.mockReturnValue(true);
|
|
32
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
33
|
+
await getGraphHealth({ vaultPath: "/vault", check: "orphans" });
|
|
34
|
+
expect(mockExecCmd).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(mockExecCmd).toHaveBeenCalledWith(["orphans", "--vault", "/vault"]);
|
|
36
|
+
});
|
|
37
|
+
it("returns no_results when vault is healthy", async () => {
|
|
38
|
+
mockIsAvailable.mockReturnValue(true);
|
|
39
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
40
|
+
const result = await getGraphHealth({ vaultPath: "/vault" });
|
|
41
|
+
expect(result.status).toBe("no_results");
|
|
42
|
+
expect(result.results).toHaveLength(0);
|
|
43
|
+
expect(result.summary).toContain("No issues");
|
|
44
|
+
});
|
|
45
|
+
it("parses orphan results into structured items", async () => {
|
|
46
|
+
mockIsAvailable.mockReturnValue(true);
|
|
47
|
+
mockExecCmd.mockImplementation((args) => {
|
|
48
|
+
if (args[0] === "orphans") {
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
output: "Projects/Abandoned.md\nInbox/Random.md",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { success: true, output: "" };
|
|
55
|
+
});
|
|
56
|
+
const result = await getGraphHealth({ vaultPath: "/vault" });
|
|
57
|
+
expect(result.status).toBe("ok");
|
|
58
|
+
const orphans = result.results.filter((r) => r.metadata?.check === "orphans");
|
|
59
|
+
expect(orphans).toHaveLength(2);
|
|
60
|
+
expect(orphans[0].title).toBe("Abandoned");
|
|
61
|
+
expect(orphans[0].path).toBe("Projects/Abandoned.md");
|
|
62
|
+
});
|
|
63
|
+
it("parses unresolved links into structured items", async () => {
|
|
64
|
+
mockIsAvailable.mockReturnValue(true);
|
|
65
|
+
mockExecCmd.mockImplementation((args) => {
|
|
66
|
+
if (args[0] === "unresolved") {
|
|
67
|
+
return { success: true, output: "Missing Note\nAnother Missing" };
|
|
68
|
+
}
|
|
69
|
+
return { success: true, output: "" };
|
|
70
|
+
});
|
|
71
|
+
const result = await getGraphHealth({ vaultPath: "/vault" });
|
|
72
|
+
const unresolved = result.results.filter((r) => r.metadata?.check === "unresolved");
|
|
73
|
+
expect(unresolved).toHaveLength(2);
|
|
74
|
+
expect(unresolved[0].title).toBe("Missing Note");
|
|
75
|
+
});
|
|
76
|
+
it("continues when one check fails but others succeed", async () => {
|
|
77
|
+
mockIsAvailable.mockReturnValue(true);
|
|
78
|
+
mockExecCmd.mockImplementation((args) => {
|
|
79
|
+
if (args[0] === "orphans") {
|
|
80
|
+
return { success: false, output: "", error: "timeout" };
|
|
81
|
+
}
|
|
82
|
+
return { success: true, output: "Inbox/note.md" };
|
|
83
|
+
});
|
|
84
|
+
const result = await getGraphHealth({ vaultPath: "/vault" });
|
|
85
|
+
// Should still return results from successful checks
|
|
86
|
+
expect(result.results.length).toBeGreaterThan(0);
|
|
87
|
+
expect(result.diagnostics).toContain("orphans check failed: timeout");
|
|
88
|
+
});
|
|
89
|
+
it("respects the limit parameter", async () => {
|
|
90
|
+
mockIsAvailable.mockReturnValue(true);
|
|
91
|
+
const lines = Array.from({ length: 30 }, (_, i) => `Notes/note${i}.md`);
|
|
92
|
+
mockExecCmd.mockReturnValue({ success: true, output: lines.join("\n") });
|
|
93
|
+
const result = await getGraphHealth({
|
|
94
|
+
vaultPath: "/vault",
|
|
95
|
+
check: "orphans",
|
|
96
|
+
limit: 5,
|
|
97
|
+
});
|
|
98
|
+
expect(result.results).toHaveLength(5);
|
|
99
|
+
expect(result.truncated).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=graph-health.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-health.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/graph-health.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
vi.mock("../obsidian-cli.js", () => ({
|
|
3
|
+
isObsidianCliAvailable: vi.fn(),
|
|
4
|
+
execObsidianCmd: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
import { readNote } from "../note-read.js";
|
|
7
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
|
|
8
|
+
const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
|
|
9
|
+
const mockExecCmd = vi.mocked(execObsidianCmd);
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.resetAllMocks();
|
|
12
|
+
});
|
|
13
|
+
describe("readNote", () => {
|
|
14
|
+
it("returns runtime_unavailable when CLI is not available", async () => {
|
|
15
|
+
mockIsAvailable.mockReturnValue(false);
|
|
16
|
+
const result = await readNote({ vaultPath: "/vault", file: "MyNote" });
|
|
17
|
+
expect(result.status).toBe("runtime_unavailable");
|
|
18
|
+
expect(result.mode).toBe("read");
|
|
19
|
+
expect(result.diagnostics).toContain("Obsidian CLI not available");
|
|
20
|
+
});
|
|
21
|
+
it("returns runtime_unavailable when CLI command fails", async () => {
|
|
22
|
+
mockIsAvailable.mockReturnValue(true);
|
|
23
|
+
mockExecCmd.mockReturnValue({
|
|
24
|
+
success: false,
|
|
25
|
+
output: "",
|
|
26
|
+
error: "Note not found",
|
|
27
|
+
});
|
|
28
|
+
const result = await readNote({ vaultPath: "/vault", file: "Missing" });
|
|
29
|
+
expect(result.status).toBe("runtime_unavailable");
|
|
30
|
+
expect(result.diagnostics[0]).toContain("Note not found");
|
|
31
|
+
});
|
|
32
|
+
it("returns note content as a single result item", async () => {
|
|
33
|
+
mockIsAvailable.mockReturnValue(true);
|
|
34
|
+
mockExecCmd.mockReturnValue({
|
|
35
|
+
success: true,
|
|
36
|
+
output: "---\ntype: project\ntags: [active]\n---\n\n# Refund Automation\n\nProject details here.",
|
|
37
|
+
});
|
|
38
|
+
const result = await readNote({ vaultPath: "/vault", file: "Refund Automation" });
|
|
39
|
+
expect(result.status).toBe("ok");
|
|
40
|
+
expect(result.results).toHaveLength(1);
|
|
41
|
+
expect(result.results[0].title).toBe("Refund Automation");
|
|
42
|
+
expect(result.results[0].file).toBe("Refund Automation");
|
|
43
|
+
expect(result.results[0].snippet).toContain("Project details here");
|
|
44
|
+
});
|
|
45
|
+
it("returns no_results when CLI returns empty output", async () => {
|
|
46
|
+
mockIsAvailable.mockReturnValue(true);
|
|
47
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
48
|
+
const result = await readNote({ vaultPath: "/vault", file: "Empty" });
|
|
49
|
+
expect(result.status).toBe("no_results");
|
|
50
|
+
expect(result.results).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
it("passes vault path and file to CLI correctly", async () => {
|
|
53
|
+
mockIsAvailable.mockReturnValue(true);
|
|
54
|
+
mockExecCmd.mockReturnValue({ success: true, output: "content" });
|
|
55
|
+
await readNote({ vaultPath: "/my/vault", file: "My Note" });
|
|
56
|
+
expect(mockExecCmd).toHaveBeenCalledWith([
|
|
57
|
+
"read",
|
|
58
|
+
"--vault",
|
|
59
|
+
"/my/vault",
|
|
60
|
+
"My Note",
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
it("truncates snippet for very long notes", async () => {
|
|
64
|
+
mockIsAvailable.mockReturnValue(true);
|
|
65
|
+
const longContent = "a".repeat(500);
|
|
66
|
+
mockExecCmd.mockReturnValue({ success: true, output: longContent });
|
|
67
|
+
const result = await readNote({ vaultPath: "/vault", file: "Long" });
|
|
68
|
+
expect(result.results[0].snippet.length).toBeLessThanOrEqual(240);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=note-read.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-read.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/note-read.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
// Mock obsidian-cli before importing the module under test
|
|
3
|
+
vi.mock("../obsidian-cli.js", () => ({
|
|
4
|
+
isObsidianCliAvailable: vi.fn(),
|
|
5
|
+
execObsidianCmd: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
import { searchVault } from "../search-vault.js";
|
|
8
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "../obsidian-cli.js";
|
|
9
|
+
const mockIsAvailable = vi.mocked(isObsidianCliAvailable);
|
|
10
|
+
const mockExecCmd = vi.mocked(execObsidianCmd);
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.resetAllMocks();
|
|
13
|
+
});
|
|
14
|
+
describe("searchVault", () => {
|
|
15
|
+
it("returns runtime_unavailable when CLI is not available", async () => {
|
|
16
|
+
mockIsAvailable.mockReturnValue(false);
|
|
17
|
+
const result = await searchVault({ vaultPath: "/vault", query: "test" });
|
|
18
|
+
expect(result.status).toBe("runtime_unavailable");
|
|
19
|
+
expect(result.mode).toBe("search:context");
|
|
20
|
+
expect(result.fallback).toBe("none");
|
|
21
|
+
expect(result.diagnostics).toContain("Obsidian CLI not available");
|
|
22
|
+
});
|
|
23
|
+
it("returns runtime_unavailable when CLI command fails", async () => {
|
|
24
|
+
mockIsAvailable.mockReturnValue(true);
|
|
25
|
+
mockExecCmd.mockReturnValue({
|
|
26
|
+
success: false,
|
|
27
|
+
output: "",
|
|
28
|
+
error: "Vault not found",
|
|
29
|
+
});
|
|
30
|
+
const result = await searchVault({ vaultPath: "/vault", query: "test" });
|
|
31
|
+
expect(result.status).toBe("runtime_unavailable");
|
|
32
|
+
expect(result.diagnostics[0]).toContain("Vault not found");
|
|
33
|
+
});
|
|
34
|
+
it("returns no_results when CLI returns empty output", async () => {
|
|
35
|
+
mockIsAvailable.mockReturnValue(true);
|
|
36
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
37
|
+
const result = await searchVault({ vaultPath: "/vault", query: "test" });
|
|
38
|
+
expect(result.status).toBe("no_results");
|
|
39
|
+
expect(result.results).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
it("parses CLI output into structured results", async () => {
|
|
42
|
+
mockIsAvailable.mockReturnValue(true);
|
|
43
|
+
mockExecCmd.mockReturnValue({
|
|
44
|
+
success: true,
|
|
45
|
+
output: [
|
|
46
|
+
"Projects/Refund Automation.md:Owner handoff for refund automation workflow",
|
|
47
|
+
"Daily/2026-03-20.md:Discussed refund automation timeline with team",
|
|
48
|
+
].join("\n"),
|
|
49
|
+
});
|
|
50
|
+
const result = await searchVault({ vaultPath: "/vault", query: "refund" });
|
|
51
|
+
expect(result.status).toBe("ok");
|
|
52
|
+
expect(result.results).toHaveLength(2);
|
|
53
|
+
expect(result.results[0].title).toBe("Refund Automation");
|
|
54
|
+
expect(result.results[0].path).toBe("Projects/Refund Automation.md");
|
|
55
|
+
expect(result.results[0].file).toBe("Refund Automation");
|
|
56
|
+
expect(result.results[0].snippet).toContain("refund automation");
|
|
57
|
+
});
|
|
58
|
+
it("respects the limit parameter", async () => {
|
|
59
|
+
mockIsAvailable.mockReturnValue(true);
|
|
60
|
+
const lines = Array.from({ length: 30 }, (_, i) => `Notes/note${i}.md:content line ${i}`);
|
|
61
|
+
mockExecCmd.mockReturnValue({ success: true, output: lines.join("\n") });
|
|
62
|
+
const result = await searchVault({
|
|
63
|
+
vaultPath: "/vault",
|
|
64
|
+
query: "content",
|
|
65
|
+
limit: 5,
|
|
66
|
+
});
|
|
67
|
+
expect(result.results).toHaveLength(5);
|
|
68
|
+
expect(result.truncated).toBe(true);
|
|
69
|
+
expect(result.totalMatches).toBe(30);
|
|
70
|
+
});
|
|
71
|
+
it("truncates snippets to MAX_SNIPPET_LENGTH", async () => {
|
|
72
|
+
mockIsAvailable.mockReturnValue(true);
|
|
73
|
+
const longLine = "a".repeat(500);
|
|
74
|
+
mockExecCmd.mockReturnValue({
|
|
75
|
+
success: true,
|
|
76
|
+
output: `Notes/long.md:${longLine}`,
|
|
77
|
+
});
|
|
78
|
+
const result = await searchVault({ vaultPath: "/vault", query: "a" });
|
|
79
|
+
expect(result.results[0].snippet.length).toBeLessThanOrEqual(240);
|
|
80
|
+
});
|
|
81
|
+
it("passes vault path and query to CLI correctly", async () => {
|
|
82
|
+
mockIsAvailable.mockReturnValue(true);
|
|
83
|
+
mockExecCmd.mockReturnValue({ success: true, output: "" });
|
|
84
|
+
await searchVault({ vaultPath: "/my/vault", query: "hello world" });
|
|
85
|
+
expect(mockExecCmd).toHaveBeenCalledWith([
|
|
86
|
+
"search:context",
|
|
87
|
+
"--vault",
|
|
88
|
+
"/my/vault",
|
|
89
|
+
"hello world",
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=search-vault.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-vault.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/search-vault.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { detectVaultContext } from "../vault-detect.js";
|
|
6
|
+
let tmpDir;
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "byoao-detect-"));
|
|
9
|
+
});
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await fs.remove(tmpDir);
|
|
12
|
+
});
|
|
13
|
+
describe("detectVaultContext", () => {
|
|
14
|
+
it("returns vault path when .obsidian/ and AGENT.md exist", async () => {
|
|
15
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
16
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
17
|
+
const result = detectVaultContext(tmpDir);
|
|
18
|
+
expect(result).toBe(tmpDir);
|
|
19
|
+
});
|
|
20
|
+
it("returns vault path when .obsidian/ and Knowledge/Glossary.md exist", async () => {
|
|
21
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
22
|
+
await fs.ensureDir(path.join(tmpDir, "Knowledge"));
|
|
23
|
+
await fs.writeFile(path.join(tmpDir, "Knowledge/Glossary.md"), "# Glossary");
|
|
24
|
+
const result = detectVaultContext(tmpDir);
|
|
25
|
+
expect(result).toBe(tmpDir);
|
|
26
|
+
});
|
|
27
|
+
it("returns null when .obsidian/ is missing", async () => {
|
|
28
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
29
|
+
const result = detectVaultContext(tmpDir);
|
|
30
|
+
expect(result).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it("returns null when .obsidian/ exists but no AGENT.md or Glossary", async () => {
|
|
33
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
34
|
+
const result = detectVaultContext(tmpDir);
|
|
35
|
+
expect(result).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
it("checks parent directory when target is a subdirectory", async () => {
|
|
38
|
+
await fs.ensureDir(path.join(tmpDir, ".obsidian"));
|
|
39
|
+
await fs.writeFile(path.join(tmpDir, "AGENT.md"), "# Agent");
|
|
40
|
+
const subDir = path.join(tmpDir, "Projects");
|
|
41
|
+
await fs.ensureDir(subDir);
|
|
42
|
+
const result = detectVaultContext(subDir);
|
|
43
|
+
expect(result).toBe(tmpDir);
|
|
44
|
+
});
|
|
45
|
+
it("returns null for a completely empty directory", async () => {
|
|
46
|
+
const result = detectVaultContext(tmpDir);
|
|
47
|
+
expect(result).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
it("returns null for non-existent path", async () => {
|
|
50
|
+
const result = detectVaultContext("/nonexistent/path/12345");
|
|
51
|
+
expect(result).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=vault-detect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-detect.test.js","sourceRoot":"","sources":["../../../src/vault/__tests__/vault-detect.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
|
|
3
|
+
import { DEFAULT_RESULT_LIMIT } from "./retrieval-types.js";
|
|
4
|
+
const ALL_CHECKS = ["orphans", "unresolved", "deadends"];
|
|
5
|
+
/** Parse CLI output lines into result items for a given check type. */
|
|
6
|
+
function parseCheckOutput(check, output) {
|
|
7
|
+
const lines = output.split("\n").filter((l) => l.trim().length > 0);
|
|
8
|
+
return lines.map((line) => {
|
|
9
|
+
const trimmed = line.trim();
|
|
10
|
+
// For orphans/deadends: output is file paths
|
|
11
|
+
// For unresolved: output is link target names
|
|
12
|
+
const isPath = trimmed.includes("/") || trimmed.endsWith(".md");
|
|
13
|
+
const title = isPath ? path.basename(trimmed, ".md") : trimmed;
|
|
14
|
+
return {
|
|
15
|
+
title,
|
|
16
|
+
path: isPath ? trimmed : "",
|
|
17
|
+
file: title,
|
|
18
|
+
metadata: { check },
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run graph health diagnostics using Obsidian CLI.
|
|
24
|
+
* Returns runtime_unavailable if CLI is not available — no internal fallback.
|
|
25
|
+
*/
|
|
26
|
+
export async function getGraphHealth(input) {
|
|
27
|
+
const { vaultPath, check = "all", limit = DEFAULT_RESULT_LIMIT } = input;
|
|
28
|
+
const mode = "graph-health";
|
|
29
|
+
const base = {
|
|
30
|
+
mode,
|
|
31
|
+
vault: vaultPath,
|
|
32
|
+
fallback: "none",
|
|
33
|
+
};
|
|
34
|
+
// 1. Check CLI availability
|
|
35
|
+
if (!isObsidianCliAvailable()) {
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
status: "runtime_unavailable",
|
|
39
|
+
summary: "Obsidian CLI not available",
|
|
40
|
+
results: [],
|
|
41
|
+
truncated: false,
|
|
42
|
+
diagnostics: ["Obsidian CLI not available"],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 2. Determine which checks to run
|
|
46
|
+
const checks = check === "all" ? ALL_CHECKS : [check];
|
|
47
|
+
// 3. Run each check and collect results
|
|
48
|
+
const allItems = [];
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
for (const checkType of checks) {
|
|
51
|
+
const cliResult = execObsidianCmd([checkType, "--vault", vaultPath]);
|
|
52
|
+
if (!cliResult.success) {
|
|
53
|
+
diagnostics.push(`${checkType} check failed: ${cliResult.error ?? "unknown error"}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (cliResult.output.trim().length > 0) {
|
|
57
|
+
allItems.push(...parseCheckOutput(checkType, cliResult.output));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 4. Build response
|
|
61
|
+
if (allItems.length === 0 && diagnostics.length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
...base,
|
|
64
|
+
status: "no_results",
|
|
65
|
+
summary: "No issues found — vault graph is healthy",
|
|
66
|
+
results: [],
|
|
67
|
+
truncated: false,
|
|
68
|
+
diagnostics,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const truncated = allItems.length > limit;
|
|
72
|
+
const results = allItems.slice(0, limit);
|
|
73
|
+
return {
|
|
74
|
+
...base,
|
|
75
|
+
status: allItems.length > 0 ? "ok" : "no_results",
|
|
76
|
+
summary: `Found ${allItems.length} graph issues across ${checks.join(", ")} checks`,
|
|
77
|
+
results,
|
|
78
|
+
truncated,
|
|
79
|
+
totalMatches: allItems.length,
|
|
80
|
+
diagnostics,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=graph-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-health.js","sourceRoot":"","sources":["../../src/vault/graph-health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
|
|
2
|
+
import { MAX_SNIPPET_LENGTH } from "./retrieval-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read a note from the vault using Obsidian CLI.
|
|
5
|
+
* Returns runtime_unavailable if CLI is not available — no internal fallback.
|
|
6
|
+
*/
|
|
7
|
+
export async function readNote(input) {
|
|
8
|
+
const { vaultPath, file } = input;
|
|
9
|
+
const mode = "read";
|
|
10
|
+
const base = {
|
|
11
|
+
mode,
|
|
12
|
+
vault: vaultPath,
|
|
13
|
+
fallback: "none",
|
|
14
|
+
};
|
|
15
|
+
// 1. Check CLI availability
|
|
16
|
+
if (!isObsidianCliAvailable()) {
|
|
17
|
+
return {
|
|
18
|
+
...base,
|
|
19
|
+
status: "runtime_unavailable",
|
|
20
|
+
summary: "Obsidian CLI not available",
|
|
21
|
+
results: [],
|
|
22
|
+
truncated: false,
|
|
23
|
+
diagnostics: ["Obsidian CLI not available"],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// 2. Execute read command
|
|
27
|
+
const cliResult = execObsidianCmd(["read", "--vault", vaultPath, file]);
|
|
28
|
+
if (!cliResult.success) {
|
|
29
|
+
return {
|
|
30
|
+
...base,
|
|
31
|
+
status: "runtime_unavailable",
|
|
32
|
+
summary: `Failed to read note "${file}"`,
|
|
33
|
+
results: [],
|
|
34
|
+
truncated: false,
|
|
35
|
+
diagnostics: [cliResult.error ?? "Unknown CLI error"],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// 3. Handle empty output
|
|
39
|
+
const content = cliResult.output.trim();
|
|
40
|
+
if (content.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
...base,
|
|
43
|
+
status: "no_results",
|
|
44
|
+
summary: `Note "${file}" is empty or not found`,
|
|
45
|
+
results: [],
|
|
46
|
+
truncated: false,
|
|
47
|
+
diagnostics: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// 4. Build result — full content as snippet (truncated for the summary item)
|
|
51
|
+
const snippet = content.length > MAX_SNIPPET_LENGTH
|
|
52
|
+
? content.substring(0, MAX_SNIPPET_LENGTH)
|
|
53
|
+
: content;
|
|
54
|
+
return {
|
|
55
|
+
...base,
|
|
56
|
+
status: "ok",
|
|
57
|
+
summary: `Read note "${file}" (${content.length} chars)`,
|
|
58
|
+
results: [
|
|
59
|
+
{
|
|
60
|
+
title: file,
|
|
61
|
+
path: "",
|
|
62
|
+
file,
|
|
63
|
+
snippet,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
truncated: false,
|
|
67
|
+
diagnostics: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=note-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-read.js","sourceRoot":"","sources":["../../src/vault/note-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO1D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAoB;IACjD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC;IACpB,MAAM,IAAI,GAAyD;QACjE,IAAI;QACJ,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,MAAM;KACjB,CAAC;IAEF,4BAA4B;IAC5B,IAAI,CAAC,sBAAsB,EAAE,EAAE,CAAC;QAC9B,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EAAE,4BAA4B;YACrC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,CAAC,4BAA4B,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED,0BAA0B;IAC1B,MAAM,SAAS,GAAG,eAAe,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IAExE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EAAE,wBAAwB,IAAI,GAAG;YACxC,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,mBAAmB,CAAC;SACtD,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,SAAS,IAAI,yBAAyB;YAC/C,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,EAAE;SAChB,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,MAAM,OAAO,GACX,OAAO,CAAC,MAAM,GAAG,kBAAkB;QACjC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,kBAAkB,CAAC;QAC1C,CAAC,CAAC,OAAO,CAAC;IAEd,OAAO;QACL,GAAG,IAAI;QACP,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,cAAc,IAAI,MAAM,OAAO,CAAC,MAAM,SAAS;QACxD,OAAO,EAAE;YACP;gBACE,KAAK,EAAE,IAAI;gBACX,IAAI,EAAE,EAAE;gBACR,IAAI;gBACJ,OAAO;aACR;SACF;QACD,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retrieval-types.js","sourceRoot":"","sources":["../../src/vault/retrieval-types.ts"],"names":[],"mappings":"AA4CA,mDAAmD;AACnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isObsidianCliAvailable, execObsidianCmd } from "./obsidian-cli.js";
|
|
3
|
+
import { DEFAULT_RESULT_LIMIT, MAX_SNIPPET_LENGTH } from "./retrieval-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Search vault notes using Obsidian CLI search:context.
|
|
6
|
+
* Returns runtime_unavailable if CLI is not available — no internal fallback.
|
|
7
|
+
*/
|
|
8
|
+
export async function searchVault(input) {
|
|
9
|
+
const { vaultPath, query, limit = DEFAULT_RESULT_LIMIT } = input;
|
|
10
|
+
const mode = "search:context";
|
|
11
|
+
const base = {
|
|
12
|
+
mode,
|
|
13
|
+
vault: vaultPath,
|
|
14
|
+
fallback: "none",
|
|
15
|
+
};
|
|
16
|
+
// 1. Check CLI availability
|
|
17
|
+
if (!isObsidianCliAvailable()) {
|
|
18
|
+
return {
|
|
19
|
+
...base,
|
|
20
|
+
status: "runtime_unavailable",
|
|
21
|
+
summary: "Obsidian CLI not available",
|
|
22
|
+
results: [],
|
|
23
|
+
truncated: false,
|
|
24
|
+
diagnostics: ["Obsidian CLI not available"],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// 2. Execute search:context
|
|
28
|
+
const cliResult = execObsidianCmd([
|
|
29
|
+
"search:context",
|
|
30
|
+
"--vault",
|
|
31
|
+
vaultPath,
|
|
32
|
+
query,
|
|
33
|
+
]);
|
|
34
|
+
if (!cliResult.success) {
|
|
35
|
+
return {
|
|
36
|
+
...base,
|
|
37
|
+
status: "runtime_unavailable",
|
|
38
|
+
summary: "Obsidian CLI command failed",
|
|
39
|
+
results: [],
|
|
40
|
+
truncated: false,
|
|
41
|
+
diagnostics: [cliResult.error ?? "Unknown CLI error"],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// 3. Parse output lines (format: path/to/note.md:context line)
|
|
45
|
+
const lines = cliResult.output.split("\n").filter((l) => l.trim().length > 0);
|
|
46
|
+
if (lines.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
...base,
|
|
49
|
+
status: "no_results",
|
|
50
|
+
summary: `No matches for "${query}"`,
|
|
51
|
+
results: [],
|
|
52
|
+
truncated: false,
|
|
53
|
+
diagnostics: [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 4. Build result items
|
|
57
|
+
const allItems = [];
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
const colonIdx = line.indexOf(":");
|
|
60
|
+
if (colonIdx === -1)
|
|
61
|
+
continue;
|
|
62
|
+
const filePath = line.substring(0, colonIdx);
|
|
63
|
+
const snippet = line.substring(colonIdx + 1).trim();
|
|
64
|
+
const title = path.basename(filePath, ".md");
|
|
65
|
+
allItems.push({
|
|
66
|
+
title,
|
|
67
|
+
path: filePath,
|
|
68
|
+
file: title,
|
|
69
|
+
snippet: snippet.length > MAX_SNIPPET_LENGTH
|
|
70
|
+
? snippet.substring(0, MAX_SNIPPET_LENGTH)
|
|
71
|
+
: snippet,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// 5. Apply limit
|
|
75
|
+
const truncated = allItems.length > limit;
|
|
76
|
+
const results = allItems.slice(0, limit);
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
status: "ok",
|
|
80
|
+
summary: `${allItems.length} matching notes for "${query}"`,
|
|
81
|
+
results,
|
|
82
|
+
truncated,
|
|
83
|
+
totalMatches: allItems.length,
|
|
84
|
+
diagnostics: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=search-vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-vault.js","sourceRoot":"","sources":["../../src/vault/search-vault.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Detect whether a directory is (or is inside) a BYOAO vault.
|
|
5
|
+
* Returns the vault root path if detected, null otherwise.
|
|
6
|
+
*
|
|
7
|
+
* A directory is a BYOAO vault if:
|
|
8
|
+
* - .obsidian/ exists AND AGENT.md exists, OR
|
|
9
|
+
* - .obsidian/ exists AND Knowledge/Glossary.md exists
|
|
10
|
+
*
|
|
11
|
+
* Checks the given directory and its immediate parent.
|
|
12
|
+
*/
|
|
13
|
+
export function detectVaultContext(dir) {
|
|
14
|
+
const candidates = [dir, path.dirname(dir)];
|
|
15
|
+
for (const candidate of candidates) {
|
|
16
|
+
if (!fs.existsSync(candidate))
|
|
17
|
+
continue;
|
|
18
|
+
const hasObsidian = fs.existsSync(path.join(candidate, ".obsidian"));
|
|
19
|
+
if (!hasObsidian)
|
|
20
|
+
continue;
|
|
21
|
+
const hasAgentMd = fs.existsSync(path.join(candidate, "AGENT.md"));
|
|
22
|
+
const hasGlossary = fs.existsSync(path.join(candidate, "Knowledge", "Glossary.md"));
|
|
23
|
+
if (hasAgentMd || hasGlossary) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=vault-detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-detect.js","sourceRoot":"","sources":["../../src/vault/vault-detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAS;QAExC,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW;YAAE,SAAS;QAE3B,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;QACnE,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAC/B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CACjD,CAAC;QAEF,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|