pi-vault-mind 0.16.30 → 0.16.32

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 (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +59 -45
  3. package/dist/src/commands.js +7 -7
  4. package/dist/src/context-capture.d.ts +11 -6
  5. package/dist/src/context-capture.js +12 -7
  6. package/dist/src/events.d.ts +2 -2
  7. package/dist/src/events.js +109 -132
  8. package/dist/src/extension-packages.d.ts +4 -4
  9. package/dist/src/extension-packages.js +4 -4
  10. package/dist/src/index.js +9 -8
  11. package/dist/src/personalize.js +2 -2
  12. package/dist/src/settings-ui.js +1 -1
  13. package/dist/src/types.d.ts +1 -1
  14. package/dist/src/types.js +1 -1
  15. package/dist/src/utils.js +3 -3
  16. package/dist/test/check-deps-script.test.js +102 -0
  17. package/dist/test/context-capture.test.js +34 -14
  18. package/dist/test/events.test.js +210 -132
  19. package/dist/test/pvm-cli.test.js +30 -0
  20. package/dist/test/pvm-setup.test.js +251 -0
  21. package/dist/test/utils.test.js +26 -4
  22. package/dist/test/vault-pi-script.test.js +87 -3
  23. package/package.json +19 -7
  24. package/pi-vault-mind.config.example.json +1 -1
  25. package/runtime/pvm-extensions.json +11 -0
  26. package/runtime/skills/LICENSE +21 -0
  27. package/runtime/skills/json-canvas/SKILL.md +244 -0
  28. package/runtime/skills/json-canvas/references/EXAMPLES.md +329 -0
  29. package/runtime/skills/nlm-skill/LICENSE +21 -0
  30. package/runtime/skills/nlm-skill/SKILL.md +936 -0
  31. package/runtime/skills/nlm-skill/references/command_reference.md +1072 -0
  32. package/runtime/skills/nlm-skill/references/remote-mcp.md +83 -0
  33. package/runtime/skills/nlm-skill/references/studio-prompt-examples.md +420 -0
  34. package/runtime/skills/nlm-skill/references/studio-prompting-guide.md +399 -0
  35. package/runtime/skills/nlm-skill/references/troubleshooting.md +427 -0
  36. package/runtime/skills/nlm-skill/references/workflows.md +572 -0
  37. package/runtime/skills/obsidian-bases/SKILL.md +499 -0
  38. package/runtime/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md +173 -0
  39. package/runtime/skills/obsidian-cli/SKILL.md +106 -0
  40. package/runtime/skills/obsidian-markdown/SKILL.md +196 -0
  41. package/runtime/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  42. package/runtime/skills/obsidian-markdown/references/EMBEDS.md +70 -0
  43. package/runtime/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  44. package/scripts/check-deps.sh +182 -118
  45. package/scripts/generate-extension-packages-json.mjs +5 -5
  46. package/scripts/pvm-setup.mjs +289 -0
  47. package/scripts/pvm.mjs +96 -3
  48. package/scripts/setup-vault-pi.sh +2 -66
  49. package/scripts/vault-pi.sh +56 -3
  50. package/skills/github-release-notes-file/SKILL.md +0 -42
  51. package/skills/obsidian-guided-test-session/SKILL.md +0 -110
  52. package/skills/obsidian-interactive-test/SKILL.md +0 -88
  53. package/skills/obsidian-plugin-publish/SKILL.md +0 -58
  54. package/skills/pi-vault-mind-release/SKILL.md +0 -141
  55. package/skills/pre-test-release/SKILL.md +0 -116
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.32 — 2026-08-28
4
+
5
+ ### Changed
6
+
7
+ - **Concise setup progress.** `pvm setup` now reports one bounded progress line per extension and curated skill while preserving captured installer diagnostics on failure.
8
+
9
+ ### Fixed
10
+
11
+ - **Vault-isolated main sessions.** `pvm run` disables implicit skill and prompt-template discovery, then explicitly loads only curated and package skills rooted in the vault-local agent directory. Specialist skill availability can no longer select a specialist identity for the interactive main session.
12
+ - **Escaping package paths.** Setup removes local package paths that resolve outside the vault-local agent directory while preserving npm packages and in-runtime custom packages.
13
+
14
+ ## 0.16.31 — 2026-08-28
15
+
16
+ ### Added
17
+
18
+ - **One-command vault setup.** `pvm setup [vault-path]` installs the complete vault-local extension, task/subagent, NotebookLM, and curated skill profile before merge-safely scaffolding Vault Mind, model-router, and main-agent configuration.
19
+ - **Resource-only repair.** `pvm setup --resources-only [vault-path]` refreshes extensions, tools, and skills without changing vault configuration.
20
+ - **Dependency audit and repair.** `pvm check-deps [--fix] [--strict] [--obsidian] [vault-path]` verifies the host runtime, vault-local extensions, curated skills, and optional content or Obsidian tooling.
21
+
22
+ ### Changed
23
+
24
+ - **Pi 0.84.3 runtime.** Development and compatibility checks now target `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` 0.84.3.
25
+ - **Node.js 22.19 runtime floor.** Package metadata, dependency audits, and setup documentation now match the minimum Node version required by Pi 0.84.3.
26
+ - **Current task and subagent extensions.** New vault setup installs `@juicesharp/rpiv-todo` 2.7.1 and `pi-subagents` 0.58.0, while removing the obsolete `@tintinweb/pi-tasks` package.
27
+ - **Pi Context 2.1.2 integration.** Session-start and turn-end capture now use the current checkpoint and context-event APIs.
28
+ - **Curated package surface.** Published Pi discovery exposes only Vault Mind usage, setup, and specialist content skills; internal release, plugin-development, and test workflows are no longer shipped as runtime skills.
29
+ - **Reproducible package tooling.** The repository now pins pnpm 10.34.5 so Corepack does not select an incompatible pnpm major during clean release builds.
30
+
31
+ ### Fixed
32
+
33
+ - **Quiet main-agent startup.** Automatic context management initializes without injecting a synthetic `/acm` user message into a new session.
34
+ - **Stable Pi launcher selection.** Setup avoids transient `npx` binaries and consistently provisions the vault-local agent directory with the installed Pi runtime.
35
+
36
+ ### Verification
37
+
38
+ - Node 24.19.0 with pnpm 10.34.5: dependency install, core build, and package-scoped Biome check completed successfully.
39
+ - Full core test suite: 937/937 passing.
40
+ - Packed `pi-vault-mind-0.16.31.tgz` successfully; the packaged `pvm --help` executable ran and exposed the `run`, `setup`, and `check-deps` commands.
41
+
3
42
  ## 0.16.30 — 2026-08-27
4
43
 
5
44
  ### Added
package/README.md CHANGED
@@ -123,37 +123,32 @@ obsidian plugin:install id=obsidian42-brat enable
123
123
  # In Obsidian: Settings → BRAT → Beta Plugin List → Add
124
124
  ```
125
125
 
126
- ### Recommended: kepano/obsidian-skills (pi skills)
126
+ ### Curated vault-local skills
127
127
 
128
- Install via the official [`skills` CLI](https://github.com/vercel-labs/skills)
129
- (pi is a first-class target agent — auto-detected):
128
+ `pvm setup` installs the supported Obsidian and NotebookLM skills into the
129
+ vault-local Pi agent directory:
130
130
 
131
131
  ```bash
132
- # Non-interactive: install all 5 globally for pi
133
- DISABLE_TELEMETRY=1 yes y | npx -y skills add https://github.com/kepano/obsidian-skills \
134
- --skill obsidian-markdown --skill obsidian-bases \
135
- --skill json-canvas --skill obsidian-cli --skill defuddle \
136
- -g -a pi --copy -y
132
+ pvm setup /path/to/your/vault
137
133
  ```
138
134
 
139
- This puts them at `~/.pi/agent/skills/<name>/SKILL.md` where pi
140
- auto-discovers them. The CLI handles agent detection, symlink/copy,
141
- and security risk confirmations.
135
+ The skills live under
136
+ `<vault>/.vault-mind/.pi/agent/skills/<name>/SKILL.md`. `pvm run` disables
137
+ Pi's implicit global/project skill discovery and explicitly loads only these
138
+ curated skills plus skills provided by packages installed in the same
139
+ vault-local agent directory. Do not install these skills globally for a Vault
140
+ Mind session; global copies defeat vault isolation and can create skill-name
141
+ conflicts.
142
142
 
143
- **Verify they're discoverable:**
143
+ - `obsidian-markdown` — Obsidian-flavored Markdown syntax
144
+ - `obsidian-bases` — valid `.base` database views
145
+ - `json-canvas` — Canvas JSON used by graph sync
146
+ - `obsidian-cli` — Obsidian CLI operation
147
+ - `nlm-skill` — NotebookLM CLI and MCP workflows
144
148
 
145
- ```bash
146
- for s in obsidian-markdown obsidian-bases json-canvas obsidian-cli defuddle; do
147
- [ -f ~/.pi/agent/skills/$s/SKILL.md ] && echo "✅ $s" || echo "❌ $s"
148
- done
149
- ```
150
- - `obsidian-markdown` — agent learns Obsidian-flavored markdown syntax
151
- - `obsidian-bases` — agent learns to generate valid `.base` files (database views)
152
- - `json-canvas` — agent learns Canvas JSON format (used for our graph sync)
153
- - `obsidian-cli` — agent learns to use the Obsidian CLI
154
- - `defuddle` — cleaner markdown extraction from web pages
155
-
156
- These skills let the **Miner, Broadcaster, and Heavy-Lifter agents** produce valid Obsidian content.
149
+ These skills let the **Miner, Broadcaster, and Heavy-Lifter agents** produce
150
+ valid Obsidian content without changing the identity of the interactive main
151
+ agent.
157
152
 
158
153
  ### Built-in Obsidian features used
159
154
 
@@ -176,7 +171,7 @@ The Broadcaster agent can generate podcasts, study guides, and slide decks from
176
171
 
177
172
  ### Prerequisites
178
173
 
179
- - **Node.js** 20+ (matches `engines` in `package.json`)
174
+ - **Node.js** 22.19.0+ (matches `engines` in `package.json`)
180
175
  - **Embedding Provider** — choose one:
181
176
  - `@xenova/transformers` — built-in, no external deps (uses all-MiniLM-L6-v2, offline-capable)
182
177
  - `ollama` — requires Ollama running locally with `embeddinggemma` (higher quality)
@@ -190,47 +185,66 @@ The Broadcaster agent can generate podcasts, study guides, and slide decks from
190
185
  > bearer token are private to whoever owns it. (A standalone PyPI package for the
191
186
  > server may come later.)
192
187
 
193
- ### 1. Install with pi
194
-
195
- ```bash
196
- pi install npm:pi-vault-mind # latest
197
- pi install npm:pi-vault-mind@0.16.30 # pinned release
198
- pi -e npm:pi-vault-mind # try without installing
199
- ```
188
+ ### 1. Install and set up the vault-local runtime
200
189
 
201
- `pi install` loads the extension but does not expose its npm executable on your
202
- shell `PATH`. Install the first-class launcher once when you want the bare
203
- `pvm` command:
190
+ Install the first-class `pvm` CLI once, then run setup against a vault:
204
191
 
205
192
  ```bash
206
193
  npm install --global pi-vault-mind
194
+ pvm setup /path/to/your/vault
207
195
  pvm run /path/to/your/vault
208
196
  ```
209
197
 
210
- Or run it without a global installation:
198
+ `pvm setup` is safe to repeat. It installs or updates the required Vault Mind,
199
+ context, model-routing, task, and subagent extensions inside
200
+ `<vault>/.vault-mind/.pi/agent/`; installs the curated `obsidian-cli`,
201
+ `obsidian-markdown`, `obsidian-bases`, and `json-canvas` skills; and
202
+ merge-safely scaffolds Vault Mind, model-router, and main-agent configuration.
203
+ Existing user configuration, collections, model choices, and `system.md` are
204
+ preserved.
205
+
206
+ Refresh only extensions, tools, and skills without touching vault
207
+ configuration:
211
208
 
212
209
  ```bash
213
- npx --yes pi-vault-mind run /path/to/your/vault
210
+ pvm setup --resources-only /path/to/your/vault
214
211
  ```
215
212
 
216
- Or from git:
213
+ Audit the isolated runtime without changing it, or repair it in place:
217
214
 
218
215
  ```bash
219
- pi install git:git@github.com:kylebrodeur/pi-vault-mind
216
+ pvm check-deps /path/to/your/vault
217
+ pvm check-deps --fix /path/to/your/vault
220
218
  ```
221
219
 
222
- ### 2. Configure (interactive wizard)
220
+ Add `--strict` to fail when optional tools are missing and `--obsidian` to
221
+ include recommended Obsidian plugin checks.
222
+
223
+ Run either mode without a global installation:
223
224
 
225
+ ```bash
226
+ npx --yes pi-vault-mind setup /path/to/your/vault
227
+ npx --yes pi-vault-mind run /path/to/your/vault
224
228
  ```
225
- /vm setup
229
+
230
+ Installing only the Pi extension remains supported:
231
+
232
+ ```bash
233
+ pi install npm:pi-vault-mind
234
+ pi -e npm:pi-vault-mind
226
235
  ```
227
236
 
228
- This configures the current vault's setup surface: vault path, embedding
229
- provider details, folder layout, and scaffolding.
237
+ ### 2. Configure interactively when needed
230
238
 
231
- Or via CLI for scripting/repair:
239
+ Inside Pi, use the wizard to choose or change embedding providers, folder
240
+ layout, and other user-facing settings:
232
241
 
233
- ```bash
242
+ ```text
243
+ /vm setup
244
+ ```
245
+ For a non-interactive repair inside Pi:
246
+
247
+ ```text
234
248
  /vm setup --vault /home/you/Obsidian/MyVault --remoteUrl https://your-embedding-service-url.example.com --model embeddinggemma
235
249
  ```
236
250
 
@@ -259,7 +259,7 @@ const handleInit = async (_args, ctx, pi) => {
259
259
  const existing = JSON.parse(fs.readFileSync(cfgDest, "utf-8"));
260
260
  existing.extensionCompatibility = existing.extensionCompatibility || {};
261
261
  existing.extensionCompatibility["pi-context"] = {
262
- tagPatterns: [],
262
+ checkpointPatterns: [],
263
263
  enhanceInjectors: false,
264
264
  autoEnableAcm: true,
265
265
  indexContextEvents: true,
@@ -314,7 +314,7 @@ const handleValidate = async (_args, ctx, pi) => {
314
314
  const contextCfg = getPiContextConfig(cfg);
315
315
  const willCapture = hasContext && contextCfg.enabled;
316
316
  lines.push("", "🧩 Extension Integrations:", " • pi-context:");
317
- lines.push(` - Tools installed: ${hasContext ? "✅ Yes (context_tag, context_log, context_checkout)" : "❌ No"}`);
317
+ lines.push(` - Tools installed: ${hasContext ? "✅ Yes (context_checkpoint, context_timeline, context_compact)" : "❌ No"}`);
318
318
  lines.push(` - Config enabled: ${contextCfg.enabled ? "✅ Yes" : "❌ No"}`);
319
319
  lines.push(` - Will capture events: ${willCapture ? "✅ Yes — on next turn" : "❌ No"}`);
320
320
  if (hasContext && !contextCfg.enabled)
@@ -1122,10 +1122,10 @@ const handleContext = async (args, ctx, pi) => {
1122
1122
  ` - Config enabled: ${piContextCfg.enabled ? "✅" : "❌"}`,
1123
1123
  ` - Will capture on next turn: ${willCapture ? "✅ Yes" : "❌ No"}`,
1124
1124
  "",
1125
- `Tag Patterns: ${piContextCfg.tagPatterns?.length || 0}`,
1126
- ...(piContextCfg.tagPatterns?.length
1127
- ? piContextCfg.tagPatterns.map((p) => ` • "${p}"`)
1128
- : [" (none — no tag-based lookups)"]),
1125
+ `Checkpoint Patterns: ${piContextCfg.checkpointPatterns?.length || 0}`,
1126
+ ...(piContextCfg.checkpointPatterns?.length
1127
+ ? piContextCfg.checkpointPatterns.map((p) => ` • "${p}"`)
1128
+ : [" (none — no checkpoint-based lookups)"]),
1129
1129
  "",
1130
1130
  `Enhance Injectors: ${piContextCfg.enhanceInjectors ? "Yes" : "No"}`,
1131
1131
  `autoEnableAcm: ${piContextCfg.autoEnableAcm ? "Yes" : "No"}`,
@@ -1163,7 +1163,7 @@ const _toggleContext = (cfgPath, enabled) => {
1163
1163
  const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
1164
1164
  existing.extensionCompatibility = existing.extensionCompatibility || {};
1165
1165
  existing.extensionCompatibility["pi-context"] = {
1166
- tagPatterns: [],
1166
+ checkpointPatterns: [],
1167
1167
  enhanceInjectors: false,
1168
1168
  autoEnableAcm: true,
1169
1169
  indexContextEvents: true,
@@ -1,18 +1,23 @@
1
1
  /**
2
- * Context Capture — Layer 4 auto-trigger pi-context /acm.
2
+ * Context Capture — Layer 4 pi-context /acm reminder.
3
+ *
4
+ * On extension load, reminds the human to run /acm to enable agentic context
5
+ * management. Does NOT inject /acm as a user or model message; the reminder
6
+ * is an unobtrusive UI notification only.
3
7
  *
4
- * On extension load, auto-sends /acm to enable agentic context management.
5
8
  * Injects context_checkpoint instructions into agent task prompts.
6
9
  *
7
10
  * Note: context_compact requires CommandCtx (only in main session, not
8
11
  * subagent forks). Subagent forks use context_checkpoint and context_timeline.
9
12
  */
10
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
11
14
  /**
12
- * Auto-send /acm on extension load to enable agentic context management
13
- * for the Manager session.
15
+ * Notify the human to run /acm to enable agentic context management.
16
+ *
17
+ * Uses ctx.ui.notify — a transient TUI notification that never enters the
18
+ * session history and is never delivered to the model.
14
19
  */
15
- export declare function enableACM(pi: ExtensionAPI): void;
20
+ export declare function notifyAcmReminder(ctx: ExtensionContext): void;
16
21
  /**
17
22
  * Build context_checkpoint instructions to inject into a dispatch task prompt.
18
23
  *
@@ -1,19 +1,24 @@
1
1
  /**
2
- * Context Capture — Layer 4 auto-trigger pi-context /acm.
2
+ * Context Capture — Layer 4 pi-context /acm reminder.
3
+ *
4
+ * On extension load, reminds the human to run /acm to enable agentic context
5
+ * management. Does NOT inject /acm as a user or model message; the reminder
6
+ * is an unobtrusive UI notification only.
3
7
  *
4
- * On extension load, auto-sends /acm to enable agentic context management.
5
8
  * Injects context_checkpoint instructions into agent task prompts.
6
9
  *
7
10
  * Note: context_compact requires CommandCtx (only in main session, not
8
11
  * subagent forks). Subagent forks use context_checkpoint and context_timeline.
9
12
  */
10
- // ── ACM auto-trigger ──────────────────────────────────────────────────────────
13
+ // ── ACM reminder notification ─────────────────────────────────────────────────
11
14
  /**
12
- * Auto-send /acm on extension load to enable agentic context management
13
- * for the Manager session.
15
+ * Notify the human to run /acm to enable agentic context management.
16
+ *
17
+ * Uses ctx.ui.notify — a transient TUI notification that never enters the
18
+ * session history and is never delivered to the model.
14
19
  */
15
- export function enableACM(pi) {
16
- pi.sendUserMessage("/acm", { deliverAs: "followUp" });
20
+ export function notifyAcmReminder(ctx) {
21
+ ctx.ui.notify("Run /acm to enable agentic context management for this session.", "info");
17
22
  }
18
23
  // ── Checkpoint instructions for agent task prompts ────────────────────────────
19
24
  /**
@@ -1,13 +1,13 @@
1
1
  import type { BeforeAgentStartEvent, ExtensionAPI, ExtensionContext, TurnEndEvent } from "@earendil-works/pi-coding-agent";
2
2
  /**
3
- * Handles before_agent_start to inject collection entries and pi-context tag matches
3
+ * Handles before_agent_start to inject collection entries and pi-context checkpoint matches
4
4
  * into the system prompt.
5
5
  */
6
6
  export declare const handleBeforeAgentStart: (pi: ExtensionAPI, event: BeforeAgentStartEvent, ctx: ExtensionContext) => Promise<{
7
7
  systemPrompt?: string;
8
8
  } | undefined>;
9
9
  /**
10
- * Handles turn_end to capture pi-context events and append them to the
10
+ * Handles turn_end to capture pi-context checkpoints and summaries in the
11
11
  * context_events collection.
12
12
  */
13
13
  export declare const handleTurnEnd: (pi: ExtensionAPI, _event: TurnEndEvent, ctx: ExtensionContext) => Promise<void>;
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import { getActiveCollection } from "./state.js";
3
3
  import { ensureDir, getPiContextConfig, hasPiContextTools, loadConfig } from "./utils.js";
4
4
  /**
5
- * Handles before_agent_start to inject collection entries and pi-context tag matches
5
+ * Handles before_agent_start to inject collection entries and pi-context checkpoint matches
6
6
  * into the system prompt.
7
7
  */
8
8
  export const handleBeforeAgentStart = async (pi, event, ctx) => {
@@ -46,81 +46,71 @@ export const handleBeforeAgentStart = async (pi, event, ctx) => {
46
46
  .replace(/\{\{entries\}\}/g, entriesText || "(none)")
47
47
  .replace(/\{\{artifact\}\}/g, artifactText || "(none)");
48
48
  }
49
- if (additions) {
50
- return { systemPrompt: event.systemPrompt + additions };
51
- }
52
- // 2. pi-context integration: auto-ACM and tag-based triggers
49
+ // 2. pi-context integration: checkpoint-based triggers via sessionManager labels.
50
+ // context_checkpoint calls pi.setLabel(targetId, name); we read those labels directly
51
+ // from ctx.sessionManager — no cross-extension tool execution required.
53
52
  const piContextCfg = getPiContextConfig(cfg);
54
- if (!piContextCfg.enabled || !hasPiContextTools(pi)) {
55
- // pi-context is disabled or unavailable — skip integration silently.
56
- }
57
- else {
58
- // ACM auto-enable is handled exactly once at session_start (see index.ts)
59
- // via pi.sendUserMessage("/acm"), gated on isVaultMindReady. A hidden
60
- // sendMessage({ customType: "pi-context", content: "/acm" }) here does NOT
61
- // enable compaction pi-context only enables ACM from its own `/acm`
62
- // command handler (which captures the command context), so this dead path
63
- // was removed.
64
- if (piContextCfg.tagPatterns && piContextCfg.tagPatterns.length > 0) {
65
- try {
66
- const allTools = pi.getAllTools();
67
- const contextLogTool = allTools.find((t) => t.name === "context_log");
68
- if (contextLogTool) {
69
- const logs = await contextLogTool.execute?.(null, { limit: 100, verbose: true }, null, null, ctx);
70
- if (logs?.content?.[0]?.text) {
71
- const logText = logs.content[0].text;
72
- let tagInjections = 0;
73
- for (const pattern of piContextCfg.tagPatterns) {
74
- const tagRegex = new RegExp(pattern, "i");
75
- const tagMatches = logText.match(/tag:\s*([\w-]+)/gi);
76
- if (tagMatches) {
77
- for (const match of tagMatches) {
78
- const tagValue = match.replace(/tag:\s*/i, "");
79
- if (tagRegex.test(tagValue)) {
80
- for (const [collectionName, collectionDef] of Object.entries(cfg.collections)) {
81
- if (collectionDef.schema.includes("tag") &&
82
- fs.existsSync(collectionDef.path)) {
83
- const lines = fs
84
- .readFileSync(collectionDef.path, "utf-8")
85
- .split("\n")
86
- .filter(Boolean);
87
- const hits = lines
88
- .map((l) => {
89
- try {
90
- return JSON.parse(l);
91
- }
92
- catch {
93
- return null;
94
- }
95
- })
96
- .filter((e) => e && e.tag === tagValue);
97
- if (hits.length > 0) {
98
- additions += `\n\n=== pi-context tag [${tagValue}] matched ${collectionName} ===\n${JSON.stringify(hits, null, 2)}\n`;
99
- tagInjections += hits.length;
100
- }
101
- }
102
- }
103
- }
104
- }
105
- }
53
+ if (piContextCfg.enabled &&
54
+ hasPiContextTools(pi) &&
55
+ piContextCfg.checkpointPatterns &&
56
+ piContextCfg.checkpointPatterns.length > 0) {
57
+ try {
58
+ const sm = ctx.sessionManager;
59
+ // Inspect only the current branch (root-to-leaf path) for labels.
60
+ // getBranch() excludes entries on abandoned forks; getEntries() would span
61
+ // all forks and wrongly trigger injection for off-branch checkpoints.
62
+ const branchEntries = sm.getBranch();
63
+ // Collect all distinct labels present on the active branch
64
+ const checkpointNames = new Set();
65
+ for (const entry of branchEntries) {
66
+ const label = sm.getLabel(entry.id);
67
+ if (label)
68
+ checkpointNames.add(label);
69
+ }
70
+ let checkpointInjections = 0;
71
+ for (const pattern of piContextCfg.checkpointPatterns) {
72
+ const checkpointRegex = new RegExp(pattern, "i");
73
+ for (const checkpointName of checkpointNames) {
74
+ if (!checkpointRegex.test(checkpointName))
75
+ continue;
76
+ for (const [collectionName, collectionDef] of Object.entries(cfg.collections)) {
77
+ if (!collectionDef.schema.includes("tag") || !fs.existsSync(collectionDef.path)) {
78
+ continue;
106
79
  }
107
- if (tagInjections > 0) {
108
- ctx.ui?.notify?.(`pi-context: injected ${tagInjections} entries from matching tags`, "info");
80
+ const hits = fs
81
+ .readFileSync(collectionDef.path, "utf-8")
82
+ .split("\n")
83
+ .filter(Boolean)
84
+ .map((line) => {
85
+ try {
86
+ return JSON.parse(line);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ })
92
+ .filter((e) => e?.tag === checkpointName);
93
+ if (hits.length > 0) {
94
+ additions += `\n\n=== pi-context checkpoint [${checkpointName}] matched ${collectionName} ===\n${JSON.stringify(hits, null, 2)}\n`;
95
+ checkpointInjections += hits.length;
109
96
  }
110
97
  }
111
98
  }
112
99
  }
113
- catch (e) {
114
- console.warn("[pi-vault-mind] pi-context tag integration error:", e);
100
+ if (checkpointInjections > 0) {
101
+ ctx.ui?.notify?.(`pi-context: injected ${checkpointInjections} entries from matching checkpoints`, "info");
115
102
  }
116
103
  }
104
+ catch (error) {
105
+ console.warn("[pi-vault-mind] pi-context checkpoint integration error:", error);
106
+ }
117
107
  }
118
108
  if (additions) {
119
109
  return { systemPrompt: event.systemPrompt + additions };
120
110
  }
121
111
  };
122
112
  /**
123
- * Handles turn_end to capture pi-context events and append them to the
113
+ * Handles turn_end to capture pi-context checkpoints and summaries in the
124
114
  * context_events collection.
125
115
  */
126
116
  export const handleTurnEnd = async (pi, _event, ctx) => {
@@ -131,96 +121,83 @@ export const handleTurnEnd = async (pi, _event, ctx) => {
131
121
  return;
132
122
  }
133
123
  try {
134
- if (!hasPiContextTools(pi)) {
135
- return;
136
- }
137
- const allTools = pi.getAllTools();
138
- const contextLogTool = allTools.find((t) => t.name === "context_log");
139
- const contextCheckoutTool = allTools.find((t) => t.name === "context_checkout");
140
- if (!contextLogTool || !contextCheckoutTool) {
141
- return;
142
- }
143
124
  const collectionDef = cfg.collections.context_events;
144
125
  if (!collectionDef) {
145
126
  console.warn("[pi-vault-mind] 'context_events' collection not defined in config. Cannot index pi-context events.");
146
127
  return;
147
128
  }
148
- // Get current session history
149
- const logs = await contextLogTool.execute?.(null, { limit: 100, verbose: true }, null, null, ctx);
150
- if (!logs?.content?.[0]?.text) {
151
- return;
152
- }
153
- const logText = logs.content[0].text;
129
+ const sm = ctx.sessionManager;
130
+ // Inspect the most recent 100 entries (100-entry bound matches prior behavior)
131
+ const recentEntries = sm.getEntries().slice(-100);
154
132
  const capturedEvents = [];
155
- const lines = logText.split("\n");
156
- for (const line of lines) {
157
- const timestamp = new Date().toISOString();
158
- let event = null;
159
- const tagMatch = line.match(/^\*?\s*([0-9a-f]+)\s+\(tag:\s*([\w-]+).*\)\s*\[(AI|USER|BASH|TOOL|SUMMARY)\]\s*(.*)/i);
160
- if (tagMatch) {
161
- event = {
162
- id: `tag-${tagMatch[1]}-${tagMatch[2]}`,
163
- type: "tag_created",
164
- session_entry_id: tagMatch[1],
165
- content: `Tag '${tagMatch[2]}' created at ${tagMatch[1]}`,
133
+ const timestamp = new Date().toISOString();
134
+ for (const entry of recentEntries) {
135
+ // Checkpoint: pi-context stores checkpoints as Pi labels on session entries.
136
+ // context_checkpoint calls pi.setLabel(entryId, name); we read via sm.getLabel.
137
+ const label = sm.getLabel(entry.id);
138
+ if (label) {
139
+ capturedEvents.push({
140
+ id: `checkpoint-${entry.id}-${label}`,
141
+ type: "checkpoint_created",
142
+ session_entry_id: entry.id,
143
+ content: `Checkpoint '${label}' created at ${entry.id}`,
166
144
  timestamp,
167
- tags: [tagMatch[2], "pi-context"],
145
+ tags: [label, "pi-context", "checkpoint"],
168
146
  details: {
169
- tag_name: tagMatch[2],
170
- entry_id: tagMatch[1],
171
- entry_type: tagMatch[3],
172
- entry_summary: tagMatch[4].trim(),
147
+ checkpoint_name: label,
148
+ entry_id: entry.id,
149
+ entry_type: entry.type.toUpperCase(),
173
150
  },
174
- };
151
+ });
175
152
  }
176
- const summaryMatch = line.match(/^\*?\s*([0-9a-f]+)\s+\((ROOT|HEAD)?.*summary from (.*)\)\s*\[SUMMARY\]\s*(.*)/i);
177
- if (summaryMatch) {
178
- event = {
179
- id: `checkout-summary-${summaryMatch[1]}`,
180
- type: "checkout_summary",
181
- session_entry_id: summaryMatch[1],
182
- content: `Checkout summary from ${summaryMatch[3]}: ${summaryMatch[4].trim()}`,
153
+ // Summary: compaction and branch_summary entries carry a real `.summary` string.
154
+ if ((entry.type === "compaction" || entry.type === "branch_summary") &&
155
+ "summary" in entry &&
156
+ typeof entry.summary === "string") {
157
+ capturedEvents.push({
158
+ id: `context-summary-${entry.id}`,
159
+ type: "context_summary",
160
+ session_entry_id: entry.id,
161
+ content: entry.summary,
183
162
  timestamp,
184
- tags: ["pi-context", "checkout", "summary"],
163
+ tags: ["pi-context", "summary"],
185
164
  details: {
186
- origin: summaryMatch[3],
187
- summary_message: summaryMatch[4].trim(),
188
- entry_id: summaryMatch[1],
165
+ entry_id: entry.id,
166
+ summary_message: entry.summary,
189
167
  },
190
- };
191
- }
192
- if (event) {
193
- capturedEvents.push(event);
168
+ });
194
169
  }
195
170
  }
196
- // Append events directly to the context_events collection
197
- if (collectionDef) {
198
- ensureDir(collectionDef.path);
199
- const existingIds = new Set();
200
- if (collectionDef.dedupField && fs.existsSync(collectionDef.path)) {
201
- const data = fs.readFileSync(collectionDef.path, "utf-8");
202
- for (const line of data.split("\n").filter(Boolean)) {
203
- try {
204
- existingIds.add(JSON.parse(line)[collectionDef.dedupField]);
205
- }
206
- catch {
207
- /* ignore malformed */
208
- }
171
+ // Append events to the context_events collection with dedup
172
+ ensureDir(collectionDef.path);
173
+ const existingIds = new Set();
174
+ if (collectionDef.dedupField && fs.existsSync(collectionDef.path)) {
175
+ const data = fs.readFileSync(collectionDef.path, "utf-8");
176
+ for (const line of data.split("\n").filter(Boolean)) {
177
+ try {
178
+ existingIds.add(JSON.parse(line)[collectionDef.dedupField]);
179
+ }
180
+ catch {
181
+ /* ignore malformed */
209
182
  }
210
183
  }
211
- let appended = 0;
212
- for (const event of capturedEvents) {
213
- if (collectionDef.dedupField && existingIds.has(event[collectionDef.dedupField])) {
184
+ }
185
+ let appended = 0;
186
+ for (const capturedEvent of capturedEvents) {
187
+ if (collectionDef.dedupField) {
188
+ const dedupVal = capturedEvent[collectionDef.dedupField];
189
+ if (existingIds.has(dedupVal))
214
190
  continue;
215
- }
216
- fs.appendFileSync(collectionDef.path, `${JSON.stringify(event)}\n`);
217
- if (collectionDef.dedupField)
218
- existingIds.add(event[collectionDef.dedupField]);
219
- appended++;
191
+ fs.appendFileSync(collectionDef.path, `${JSON.stringify(capturedEvent)}\n`);
192
+ existingIds.add(dedupVal);
220
193
  }
221
- if (appended > 0 && ctx.ui?.notify) {
222
- ctx.ui.notify(`pi-context: captured ${appended} event(s) → ${collectionDef.path}`, "info");
194
+ else {
195
+ fs.appendFileSync(collectionDef.path, `${JSON.stringify(capturedEvent)}\n`);
223
196
  }
197
+ appended++;
198
+ }
199
+ if (appended > 0 && ctx.ui?.notify) {
200
+ ctx.ui.notify(`pi-context: captured ${appended} event(s) → ${collectionDef.path}`, "info");
224
201
  }
225
202
  }
226
203
  catch (e) {
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Canonical list of pi extensions required/optional for vault-mind.
2
+ * Canonical extension list for the legacy `/vm setup` and Obsidian setup
3
+ * surfaces. The standalone `pvm` CLI has a broader package-managed runtime
4
+ * contract in `runtime/pvm-extensions.json`.
3
5
  *
4
- * This is the SINGLE SOURCE OF TRUTH. Shell scripts and the Obsidian plugin
5
- * consume generated JSON (see scripts/generate-extension-packages-json.mjs).
6
- * When adding/removing a package, edit ONLY this file and regenerate.
6
+ * Generate this surface with scripts/generate-extension-packages-json.mjs.
7
7
  */
8
8
  export declare const PI_REQUIRED_EXTENSIONS: readonly ["npm:pi-vault-mind", "npm:pi-context", "npm:@kylebrodeur/pi-model-discovery", "npm:@kylebrodeur/pi-model-router"];
9
9
  export declare const PI_OPTIONAL_EXTENSIONS: readonly ["npm:pi-1password"];