pi-vault-mind 0.7.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +428 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/src/commands.d.ts +9 -0
  6. package/dist/src/commands.js +813 -0
  7. package/dist/src/events.d.ts +13 -0
  8. package/dist/src/events.js +236 -0
  9. package/dist/src/graph.d.ts +3 -0
  10. package/dist/src/graph.js +234 -0
  11. package/dist/src/index.d.ts +2 -0
  12. package/dist/src/index.js +61 -0
  13. package/dist/src/lance.d.ts +40 -0
  14. package/dist/src/lance.js +409 -0
  15. package/dist/src/server.d.ts +25 -0
  16. package/dist/src/server.js +180 -0
  17. package/dist/src/settings-ui.d.ts +9 -0
  18. package/dist/src/settings-ui.js +313 -0
  19. package/dist/src/state.d.ts +2 -0
  20. package/dist/src/state.js +16 -0
  21. package/dist/src/tools.d.ts +2 -0
  22. package/dist/src/tools.js +772 -0
  23. package/dist/src/types.d.ts +103 -0
  24. package/dist/src/types.js +51 -0
  25. package/dist/src/utils.d.ts +17 -0
  26. package/dist/src/utils.js +102 -0
  27. package/dist/src/vault-writer.d.ts +17 -0
  28. package/dist/src/vault-writer.js +141 -0
  29. package/dist/src/watcher.d.ts +91 -0
  30. package/dist/src/watcher.js +411 -0
  31. package/dist/src/widget.d.ts +3 -0
  32. package/dist/src/widget.js +12 -0
  33. package/dist/test/index.test.d.ts +1 -0
  34. package/dist/test/index.test.js +368 -0
  35. package/package.json +83 -0
  36. package/skills/vault-mind/SKILL.md +260 -0
  37. package/skills/vault-mind/references/tool-reference.md +53 -0
  38. package/skills/vault-mind-broadcaster/SKILL.md +112 -0
  39. package/skills/vault-mind-heavy-lifter/SKILL.md +34 -0
  40. package/skills/vault-mind-manager/SKILL.md +35 -0
  41. package/skills/vault-mind-miner/SKILL.md +40 -0
  42. package/skills/vault-mind-setup/SKILL.md +385 -0
  43. package/skills/vault-mind-setup/references/obsidian-cli-and-plugins.md +269 -0
  44. package/skills/vault-mind-setup/references/obsidian-vault-structure.md +106 -0
  45. package/skills/vault-mind-setup/references/pi-extension-wiring.md +236 -0
  46. package/skills/vault-mind-setup/references/troubleshooting-tree.md +147 -0
@@ -0,0 +1,103 @@
1
+ export interface CollectionDef {
2
+ path: string;
3
+ schema: string[];
4
+ dedupField?: string;
5
+ }
6
+ export interface InjectorDef {
7
+ name: string;
8
+ regex: string;
9
+ captureGroup?: number;
10
+ collection: string;
11
+ filterField?: string;
12
+ artifactPath?: string;
13
+ template?: string;
14
+ }
15
+ export interface EmbeddingConfig {
16
+ provider: "ollama" | "transformers";
17
+ ollamaModel?: string;
18
+ ollamaHost?: string;
19
+ }
20
+ export interface GraphConfig {
21
+ enabled?: boolean;
22
+ canvasSync?: boolean;
23
+ }
24
+ export interface VaultConfig {
25
+ path: string;
26
+ autoSync?: boolean;
27
+ autoSyncTags?: string[];
28
+ autoSyncMinLength?: number;
29
+ }
30
+ export interface WikiConfig {
31
+ dataDir: string;
32
+ embedding: EmbeddingConfig;
33
+ graph?: GraphConfig;
34
+ vaults?: Record<string, VaultConfig>;
35
+ ftsEnabled?: boolean;
36
+ httpPort?: number;
37
+ }
38
+ export interface PiContextDef {
39
+ enabled?: boolean;
40
+ tagPatterns?: string[];
41
+ enhanceInjectors?: boolean;
42
+ autoEnableAcm?: boolean;
43
+ indexContextEvents?: boolean;
44
+ }
45
+ export interface UniversalConfig {
46
+ version: number;
47
+ collections: Record<string, CollectionDef>;
48
+ injectors: InjectorDef[];
49
+ wiki: WikiConfig;
50
+ extensionCompatibility?: {
51
+ "pi-context"?: PiContextDef;
52
+ };
53
+ }
54
+ export declare const DEFAULT_CONFIG: UniversalConfig;
55
+ export interface WikiSearchInput {
56
+ query: string;
57
+ limit?: number;
58
+ }
59
+ export interface WikiGraphQueryInput {
60
+ entity: string;
61
+ depth?: number;
62
+ }
63
+ export interface PromoteWikiInput {
64
+ sourceCollection: string;
65
+ targetCollection: string;
66
+ entryIds: string[];
67
+ reason: string;
68
+ }
69
+ export interface QueryWikiInput {
70
+ collection: string;
71
+ query?: string;
72
+ filters?: Record<string, string>;
73
+ }
74
+ export interface AppendWikiInput {
75
+ collection: string;
76
+ mode: "strict" | "gated" | "autopilot";
77
+ entry: Record<string, string>;
78
+ sync?: "auto" | "manual" | "none";
79
+ vault?: string;
80
+ }
81
+ export interface WikiSyncInput {
82
+ vault?: string;
83
+ query?: string;
84
+ tag?: string;
85
+ collection?: string;
86
+ format?: "markdown" | "canvas";
87
+ limit?: number;
88
+ }
89
+ export interface ConfigureWikiInput {
90
+ action: "read" | "update";
91
+ config?: Record<string, unknown>;
92
+ }
93
+ export interface DescribeWikiInput {
94
+ collection: string;
95
+ }
96
+ export interface WikiExportInput {
97
+ collection: string;
98
+ format: "json" | "csv" | "markdown";
99
+ }
100
+ export interface WikiIngestInput {
101
+ source: string;
102
+ sourceType?: "url" | "file" | "text";
103
+ }
@@ -0,0 +1,51 @@
1
+ export const DEFAULT_CONFIG = {
2
+ version: 2,
3
+ collections: {
4
+ main: {
5
+ path: "collections/main.jsonl",
6
+ schema: ["id", "domain", "source", "fact", "tag", "artifact"],
7
+ dedupField: "fact",
8
+ },
9
+ pending: {
10
+ path: "collections/pending.jsonl",
11
+ schema: "main",
12
+ },
13
+ context_events: {
14
+ path: "collections/context_events.jsonl",
15
+ schema: ["id", "type", "session_entry_id", "content", "timestamp", "tags"],
16
+ dedupField: "id",
17
+ },
18
+ },
19
+ injectors: [
20
+ {
21
+ name: "draft-context",
22
+ regex: "draft\\s+(\\S+)",
23
+ collection: "main",
24
+ filterField: "tag",
25
+ artifactPath: "collections/artifact.md",
26
+ },
27
+ ],
28
+ wiki: {
29
+ dataDir: ".lancedb",
30
+ embedding: {
31
+ provider: "transformers",
32
+ ollamaModel: "embeddinggemma",
33
+ ollamaHost: "http://127.0.0.1:11434",
34
+ },
35
+ graph: {
36
+ enabled: true,
37
+ canvasSync: false,
38
+ },
39
+ ftsEnabled: true,
40
+ httpPort: 11435,
41
+ },
42
+ extensionCompatibility: {
43
+ "pi-context": {
44
+ enabled: false,
45
+ tagPatterns: [],
46
+ enhanceInjectors: false,
47
+ autoEnableAcm: true,
48
+ indexContextEvents: true,
49
+ },
50
+ },
51
+ };
@@ -0,0 +1,17 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { PiContextDef, UniversalConfig } from "./types.js";
3
+ export declare const CONFIG_FILES: string[];
4
+ export declare const GLOBAL_CONFIG_DIR: string;
5
+ export declare const GLOBAL_CONFIG_PATH: string;
6
+ export declare const EXT_ROOT: string;
7
+ export declare const resolvePath: (cwd: string, p: string) => string;
8
+ export declare const ensureDir: (filePath: string) => void;
9
+ export declare const findConfig: (cwd: string) => {
10
+ global?: string;
11
+ project?: string;
12
+ };
13
+ export declare const loadConfig: (cwd: string) => UniversalConfig;
14
+ export declare const hasPiContextTools: (pi: ExtensionAPI) => boolean;
15
+ export declare const isPiContextEnabled: (cfg: UniversalConfig) => boolean;
16
+ export declare const getPiContextConfig: (cfg: UniversalConfig) => PiContextDef;
17
+ export declare const collectionNames: (cfg: UniversalConfig) => string[];
@@ -0,0 +1,102 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { DEFAULT_CONFIG } from "./types.js";
4
+ // Config filenames, in priority order (first match wins).
5
+ export const CONFIG_FILES = ["pi-vault-mind.config.json", ".pi/vault-mind.config.json"];
6
+ export const GLOBAL_CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || "", ".pi", "agent");
7
+ export const GLOBAL_CONFIG_PATH = path.join(GLOBAL_CONFIG_DIR, "vault-mind.config.json");
8
+ export const EXT_ROOT = path.join(import.meta.dirname, "..");
9
+ export const resolvePath = (cwd, p) => path.isAbsolute(p) ? p : path.join(cwd, p);
10
+ export const ensureDir = (filePath) => {
11
+ const dir = path.dirname(filePath);
12
+ if (!fs.existsSync(dir))
13
+ fs.mkdirSync(dir, { recursive: true });
14
+ };
15
+ export const findConfig = (cwd) => {
16
+ const result = {};
17
+ if (fs.existsSync(GLOBAL_CONFIG_PATH)) {
18
+ result.global = GLOBAL_CONFIG_PATH;
19
+ }
20
+ for (const rel of CONFIG_FILES) {
21
+ const fp = path.join(cwd, rel);
22
+ if (fs.existsSync(fp)) {
23
+ result.project = fp;
24
+ break;
25
+ }
26
+ }
27
+ return result;
28
+ };
29
+ const readConfigFile = (p) => {
30
+ if (!p)
31
+ return {};
32
+ try {
33
+ const raw = JSON.parse(fs.readFileSync(p, "utf-8"));
34
+ return raw;
35
+ }
36
+ catch (e) {
37
+ console.warn(`[pi-vault-mind] Config parse error at ${p}: ${e}`);
38
+ return {};
39
+ }
40
+ };
41
+ const mergeConfigLayer = (base, layer, cwd) => {
42
+ const rawLayer = layer;
43
+ const merged = {
44
+ version: layer.version ?? base.version,
45
+ collections: { ...base.collections },
46
+ injectors: [...(base.injectors || []), ...(layer.injectors || [])],
47
+ wiki: {
48
+ ...base.wiki,
49
+ ...(layer.wiki || {}),
50
+ embedding: { ...base.wiki.embedding, ...(layer.wiki?.embedding || {}) },
51
+ graph: { ...base.wiki.graph, ...(layer.wiki?.graph || {}) },
52
+ vaults: { ...base.wiki.vaults, ...(layer.wiki?.vaults || {}) },
53
+ },
54
+ extensionCompatibility: {
55
+ ...base.extensionCompatibility,
56
+ ...layer.extensionCompatibility,
57
+ },
58
+ };
59
+ // Resolve collection entries (handles schema aliasing like pending → main).
60
+ const migrationSrc = rawLayer.collections || layer.collections || {};
61
+ const resolvedCollections = {};
62
+ for (const [name, def] of Object.entries(migrationSrc)) {
63
+ const resolvedBase = typeof def.schema === "string" ? resolvedCollections[def.schema] : undefined;
64
+ resolvedCollections[name] = {
65
+ path: resolvePath(cwd, def.path || (resolvedBase?.path ?? "")),
66
+ schema: typeof def.schema === "string" ? (resolvedBase?.schema ?? []) : def.schema,
67
+ dedupField: def.dedupField ?? resolvedBase?.dedupField,
68
+ };
69
+ }
70
+ merged.collections = resolvedCollections;
71
+ // Resolve injector entries: default missing 'collection' field to 'main'.
72
+ merged.injectors = merged.injectors.map((ij) => ({
73
+ ...ij,
74
+ collection: ij.collection || "main",
75
+ artifactPath: ij.artifactPath ? resolvePath(cwd, ij.artifactPath) : undefined,
76
+ }));
77
+ return merged;
78
+ };
79
+ export const loadConfig = (cwd) => {
80
+ const paths = findConfig(cwd);
81
+ const globalCfg = readConfigFile(paths.global);
82
+ const projectCfg = readConfigFile(paths.project);
83
+ let merged = mergeConfigLayer(DEFAULT_CONFIG, globalCfg, cwd);
84
+ merged = mergeConfigLayer(merged, projectCfg, cwd);
85
+ return merged;
86
+ };
87
+ export const hasPiContextTools = (pi) => {
88
+ const tools = pi.getAllTools();
89
+ const names = new Set(tools.map((t) => t.name));
90
+ return names.has("context_tag") && names.has("context_log") && names.has("context_checkout");
91
+ };
92
+ export const isPiContextEnabled = (cfg) => cfg.extensionCompatibility?.["pi-context"]?.enabled === true;
93
+ export const getPiContextConfig = (cfg) => ({
94
+ enabled: false,
95
+ tagPatterns: [],
96
+ enhanceInjectors: false,
97
+ autoEnableAcm: true,
98
+ indexContextEvents: true,
99
+ ...cfg.extensionCompatibility?.["pi-context"],
100
+ });
101
+ // Backward compat alias
102
+ export const collectionNames = (cfg) => Object.keys(cfg.collections);
@@ -0,0 +1,17 @@
1
+ import type { WikiConfig } from "./types.js";
2
+ /** Writes wiki entries to an Obsidian vault as markdown and canvas files. */
3
+ export declare class VaultWriter {
4
+ private vaultPath;
5
+ private cfg;
6
+ constructor(vaultPath: string, cfg: WikiConfig);
7
+ private agentDir;
8
+ private inboxDir;
9
+ private wikiDir;
10
+ private ensureDir;
11
+ /** Write a single entry as a vault markdown note. */
12
+ writeEntry(collection: string, entry: Record<string, string>): Promise<string>;
13
+ /** Query and sync multiple entries to vault inbox. */
14
+ syncEntries(collection: string, query: string, limit: number): Promise<number>;
15
+ /** Generate a .canvas file from entity graph edges. */
16
+ syncGraphToCanvas(): Promise<number>;
17
+ }
@@ -0,0 +1,141 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { connect } from "./lance.js";
4
+ import { loadConfig } from "./utils.js";
5
+ /** Writes wiki entries to an Obsidian vault as markdown and canvas files. */
6
+ export class VaultWriter {
7
+ vaultPath;
8
+ cfg;
9
+ constructor(vaultPath, cfg) {
10
+ this.vaultPath = vaultPath;
11
+ this.cfg = cfg;
12
+ }
13
+ agentDir() {
14
+ return path.join(this.vaultPath, "Agent");
15
+ }
16
+ inboxDir() {
17
+ return path.join(this.agentDir(), "Inbox");
18
+ }
19
+ wikiDir() {
20
+ return path.join(this.agentDir(), "Wiki");
21
+ }
22
+ ensureDir(dir) {
23
+ if (!fs.existsSync(dir))
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ }
26
+ /** Write a single entry as a vault markdown note. */
27
+ async writeEntry(collection, entry) {
28
+ const inbox = this.inboxDir();
29
+ this.ensureDir(inbox);
30
+ const id = entry.id || entry.fact?.slice(0, 40).replace(/[^a-zA-Z0-9]/g, "-") || "entry";
31
+ const filename = `${id}.md`;
32
+ const filepath = path.join(inbox, filename);
33
+ const frontmatter = [
34
+ "---",
35
+ `id: "${entry.id || ""}"`,
36
+ `collection: ${collection}`,
37
+ `domain: ${entry.domain || ""}`,
38
+ `tag: ${entry.tag || ""}`,
39
+ `source: ${entry.source || ""}`,
40
+ `created: ${entry.created_at || new Date().toISOString()}`,
41
+ "---",
42
+ ].join("\n");
43
+ const body = `${[
44
+ frontmatter,
45
+ "",
46
+ `# ${entry.fact?.slice(0, 80) || "Entry"}`,
47
+ "",
48
+ entry.fact || "",
49
+ "",
50
+ entry.artifact ? `**See also:** ${entry.artifact}` : "",
51
+ ]
52
+ .filter(Boolean)
53
+ .join("\n")}\n`;
54
+ fs.writeFileSync(filepath, body, "utf-8");
55
+ return filepath;
56
+ }
57
+ /** Query and sync multiple entries to vault inbox. */
58
+ async syncEntries(collection, query, limit) {
59
+ const inbox = this.inboxDir();
60
+ this.ensureDir(inbox);
61
+ this.ensureDir(this.wikiDir());
62
+ const cfg = loadConfig(this.vaultPath);
63
+ const def = cfg.collections?.[collection];
64
+ if (!def || !fs.existsSync(def.path))
65
+ return 0;
66
+ const lines = fs.readFileSync(def.path, "utf-8").split("\n").filter(Boolean);
67
+ const entries = [];
68
+ for (const line of lines) {
69
+ try {
70
+ entries.push(JSON.parse(line));
71
+ }
72
+ catch {
73
+ /* skip */
74
+ }
75
+ }
76
+ let filtered = entries;
77
+ if (query) {
78
+ const lower = query.toLowerCase();
79
+ if (lower.startsWith("tag:")) {
80
+ const tag = lower.slice(4);
81
+ filtered = entries.filter((e) => e.tag === tag);
82
+ }
83
+ else {
84
+ filtered = entries.filter((e) => Object.values(e).some((v) => String(v).toLowerCase().includes(lower)));
85
+ }
86
+ }
87
+ filtered = filtered.slice(0, limit);
88
+ for (const entry of filtered) {
89
+ await this.writeEntry(collection, entry);
90
+ }
91
+ return filtered.length;
92
+ }
93
+ /** Generate a .canvas file from entity graph edges. */
94
+ async syncGraphToCanvas() {
95
+ const wikiDir = this.wikiDir();
96
+ this.ensureDir(wikiDir);
97
+ const canvasPath = path.join(wikiDir, "Wiki-Graph.canvas");
98
+ try {
99
+ const db = await connect(this.cfg.dataDir);
100
+ const existing = await db.tableNames();
101
+ const nodes = [];
102
+ const edges = [];
103
+ if (existing.includes("entities")) {
104
+ const table = await db.openTable("entities");
105
+ const entities = await table.query().limit(100).toArray();
106
+ for (let i = 0; i < entities.length; i++) {
107
+ const e = entities[i];
108
+ nodes.push({
109
+ id: `e-${e.id || i}`,
110
+ type: "text",
111
+ x: (i % 5) * 300 + 50,
112
+ y: Math.floor(i / 5) * 200 + 50,
113
+ width: 250,
114
+ height: 120,
115
+ file: e.name ? `${String(e.name).replace(/[^a-zA-Z0-9]/g, "-")}.md` : undefined,
116
+ });
117
+ }
118
+ }
119
+ if (existing.includes("relations")) {
120
+ const table = await db.openTable("relations");
121
+ const relations = await table.query().limit(200).toArray();
122
+ for (let i = 0; i < relations.length; i++) {
123
+ const r = relations[i];
124
+ edges.push({
125
+ id: `edge-${i}`,
126
+ fromNode: `e-${r.from_entity_id}`,
127
+ toNode: `e-${r.to_entity_id}`,
128
+ fromSide: "right",
129
+ toSide: "left",
130
+ label: r.relation_type || undefined,
131
+ });
132
+ }
133
+ }
134
+ fs.writeFileSync(canvasPath, JSON.stringify({ nodes, edges }, null, 2), "utf-8");
135
+ return edges.length;
136
+ }
137
+ catch {
138
+ return 0;
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Vault Watcher — passive file-system observer for @agent markers.
3
+ *
4
+ * Inspired by pi-piqo but redesigned for the pi-vault-mind multi-agent
5
+ * architecture. Instead of sending prompts directly to the LLM, the
6
+ * Watcher groups markers by target agent role and dispatches them as
7
+ * well-structured subagent tasks into the active pi session. The
8
+ * Manager agent then uses the standard subagent() tool to fork
9
+ * isolated worker sessions.
10
+ *
11
+ * Key differences from pi-piqo:
12
+ * - Multi-role grouping (one bundled task per agent per file)
13
+ * - Named IDs force isolation (e.g., @agent-Miner:customId)
14
+ * - Concurrency limits prevent explosion of subagent processes
15
+ * - Debounced watching (1000ms per file) groups rapid saves
16
+ */
17
+ import * as fs from "node:fs";
18
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
+ interface AgentMarker {
20
+ /** Raw marker line (e.g., @agent-Miner:task1) */
21
+ rawLine: string;
22
+ /** Line number in the file (1-indexed) */
23
+ lineNumber: number;
24
+ /** The agent role extracted from the marker */
25
+ role: string;
26
+ /** Optional unique ID for custom isolation (e.g., :task1) */
27
+ taskId?: string;
28
+ /** The instruction text after the marker */
29
+ instruction: string;
30
+ /** Context: surrounding lines from the file */
31
+ context: string;
32
+ }
33
+ interface AgentGroup {
34
+ role: string;
35
+ taskId: string;
36
+ markers: AgentMarker[];
37
+ filePath: string;
38
+ /** Combined instruction from all markers in this group */
39
+ combinedInstruction: string;
40
+ /** Unique dispatch ID for tracking and writeback */
41
+ dispatchId: string;
42
+ }
43
+ export interface WatcherState {
44
+ /** Active file watchers keyed by vault name */
45
+ watchers: Map<string, fs.FSWatcher>;
46
+ /** Per-file debounce timers to group rapid saves */
47
+ debounceTimers: Map<string, ReturnType<typeof setTimeout>>;
48
+ /** Files currently being processed (prevents duplicate dispatches) */
49
+ processing: Set<string>;
50
+ /** Whether the watcher is active */
51
+ running: boolean;
52
+ /** Max concurrent subagent dispatches */
53
+ maxConcurrent: number;
54
+ /** Currently active dispatch count */
55
+ activeDispatches: number;
56
+ /** Queue of pending dispatch tasks */
57
+ pendingQueue: AgentGroup[];
58
+ /** Active dispatch records (for status reporting) */
59
+ activeDispatches_: Map<string, DispatchRecord>;
60
+ }
61
+ interface DispatchRecord {
62
+ dispatchId: string;
63
+ filePath: string;
64
+ role: string;
65
+ agentName: string;
66
+ dispatchedAt: string;
67
+ markerCount: number;
68
+ }
69
+ /**
70
+ * Scan a file for @agent markers and return grouped tasks.
71
+ * Exported for testing.
72
+ *
73
+ * Grouping strategy:
74
+ * 1. Same role markers in a single file are bundled into one task.
75
+ * 2. Different role markers create separate groups.
76
+ * 3. Named IDs (e.g., @agent-Miner:customId) force isolation regardless of role.
77
+ */
78
+ export declare function scanFile(filePath: string): AgentGroup[];
79
+ /**
80
+ * Generate a unique dispatch ID for tracking and writeback targeting.
81
+ * Exported for testing.
82
+ */
83
+ export declare function generateDispatchId(role: string): string;
84
+ export declare function startWatcher(pi: ExtensionAPI, vaults: Record<string, {
85
+ path: string;
86
+ }>, state: WatcherState): void;
87
+ export declare function stopWatcher(state: WatcherState): void;
88
+ export declare function processQueue(pi: ExtensionAPI, state: WatcherState): void;
89
+ export declare function getWatcherStatus(state: WatcherState): string;
90
+ export declare function createWatcherState(): WatcherState;
91
+ export {};