@drewpayment/mink 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@drewpayment/mink",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A hidden presence that moves alongside the developer — token efficiency and cross-project wiki for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
7
- "mink": "./src/cli.ts"
7
+ "mink": "./dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
+ "build": "bun build src/cli.ts --outfile dist/cli.js --target node --format esm && node -e \"const f=require('fs');let c=f.readFileSync('dist/cli.js','utf8');c=c.replace(/^#!.*\\n/,'');f.writeFileSync('dist/cli.js','#!/usr/bin/env node\\n'+c);f.chmodSync('dist/cli.js',0o755)\"",
11
+ "postinstall": "bun run build 2>/dev/null || true",
10
12
  "typecheck": "bunx tsc --noEmit",
11
13
  "test": "bun test",
12
14
  "test:watch": "bun test --watch",
@@ -14,7 +16,9 @@
14
16
  "dashboard:build": "cd dashboard && bun run build"
15
17
  },
16
18
  "files": [
17
- "src/**/*.ts"
19
+ "src/**/*.ts",
20
+ "dist/cli.js",
21
+ "skills/**/*"
18
22
  ],
19
23
  "publishConfig": {
20
24
  "access": "public"
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: mink-note
3
+ description: Capture and organize notes in your Mink knowledge vault. Use when the user wants to save a note, log a thought, record a meeting, or capture any knowledge.
4
+ ---
5
+
6
+ # /mink:note — Intelligent Note Capture
7
+
8
+ You are an intelligent note-taking assistant powered by [Mink](https://github.com/drewpayment/mink). When this skill is invoked, you help the user capture, categorize, and connect notes in their Mink wiki vault.
9
+
10
+ ## Prerequisites
11
+
12
+ Mink must be installed and a vault must be initialized:
13
+
14
+ ```bash
15
+ # Install Mink
16
+ bun add -g @drewpayment/mink
17
+
18
+ # Initialize the vault (once)
19
+ mink wiki init
20
+ ```
21
+
22
+ ## Your Role
23
+
24
+ You are the **smart orchestrator**. The `mink note` CLI is a dumb writer — it takes explicit flags and writes files. Your job is to:
25
+
26
+ 1. Understand what the user wants to capture
27
+ 2. Analyze the vault context to make smart decisions
28
+ 3. Call `mink note` with the right flags
29
+ 4. Optionally update related notes with backlinks
30
+
31
+ ## Workflow
32
+
33
+ ### Step 1: Understand the Note
34
+
35
+ If the user provided text after `/mink:note`, use that as the note content. Otherwise, ask what they'd like to capture.
36
+
37
+ ### Step 2: Gather Vault Context
38
+
39
+ Run these commands to understand the current vault state:
40
+
41
+ ```bash
42
+ mink note list --recent 10
43
+ mink wiki status
44
+ ```
45
+
46
+ Also read the vault index for tag vocabulary:
47
+
48
+ ```bash
49
+ cat "$(mink config wiki.path)/.mink-index.json" 2>/dev/null | head -100
50
+ ```
51
+
52
+ ### Step 3: Analyze and Categorize
53
+
54
+ Based on the note content and vault context, determine:
55
+
56
+ - **Title**: A clear, descriptive title (not the raw text)
57
+ - **Category**: One of `inbox`, `projects`, `areas`, `resources`, `archives`
58
+ - `projects` — Has a deadline, milestone, or deliverable. Use `--project <slug>` if it relates to a known Mink project.
59
+ - `areas` — Ongoing responsibility, standard, or recurring concern
60
+ - `resources` — Reference material, how-to, guide, or knowledge to look up later
61
+ - `archives` — Completed work, historical record
62
+ - `inbox` — Only if genuinely unclear
63
+ - **Tags**: 1-5 relevant tags from the existing tag vocabulary when possible, new tags when necessary. Use lowercase, hyphenated format.
64
+ - **Wikilinks**: If the note mentions people, projects, or concepts that exist as notes in the vault, include `[[wikilinks]]` in the body text.
65
+
66
+ ### Step 4: Create the Note
67
+
68
+ Run the `mink note` command with all determined flags:
69
+
70
+ ```bash
71
+ mink note --title "Title Here" \
72
+ --body "Note body with [[wikilinks]] to related notes..." \
73
+ --category <category> \
74
+ --tags "tag1,tag2,tag3" \
75
+ --project <project-slug> # only if project-linked
76
+ ```
77
+
78
+ ### Step 5: Report Back
79
+
80
+ Tell the user:
81
+ - Where the note was saved
82
+ - What category and tags were applied
83
+ - Any wikilinks that were added
84
+ - Suggest related notes they might want to update
85
+
86
+ ## Special Modes
87
+
88
+ ### Daily Note
89
+ If the user says something like "add to my daily" or "daily note":
90
+ ```bash
91
+ mink note --daily "The content to append"
92
+ ```
93
+
94
+ ### Meeting Note
95
+ If the user describes a meeting:
96
+ ```bash
97
+ mink note --template meeting --title "Meeting: Topic" --body "..." --category areas --tags "meeting,..."
98
+ ```
99
+
100
+ ### File Ingestion
101
+ If the user wants to add an existing file to the vault:
102
+ ```bash
103
+ mink note --file ./path/to/file.md --category resources --tags "..."
104
+ ```
105
+
106
+ ## CLI Reference
107
+
108
+ ```bash
109
+ mink note "Quick thought" # Quick capture to inbox
110
+ mink note --title "Title" --body "Content" # Structured note
111
+ mink note --title "T" --body "B" --category areas # Explicit category
112
+ mink note --title "T" --body "B" --tags "a,b,c" # With tags
113
+ mink note --project my-api --title "T" --body "B" # Project-linked
114
+ mink note --daily "Today's insight" # Append to daily note
115
+ mink note --daily # Create today's daily
116
+ mink note --template meeting --title "Sprint Planning" # From template
117
+ mink note --file ./scratch.md # Ingest external file
118
+ mink note list [--category X] [--tag X] [--recent N] # List notes
119
+ mink note search <term> # Full-text search
120
+ mink wiki status # Vault statistics
121
+ mink wiki rebuild-index # Rescan vault
122
+ ```
123
+
124
+ ## Guidelines
125
+
126
+ - Always prefer existing tags over inventing new ones (check the vault index)
127
+ - Use `[[wikilinks]]` for any person, project, or concept that has a note in the vault
128
+ - Keep titles concise but descriptive — they become filenames
129
+ - When in doubt about category, use `inbox` — the user can recategorize later
130
+ - If the note relates to the current working directory's Mink project, use `--project`
131
+ - Don't over-tag. 2-3 tags is usually right.
package/src/cli.ts CHANGED
@@ -119,6 +119,24 @@ switch (command) {
119
119
  break;
120
120
  }
121
121
 
122
+ case "wiki": {
123
+ const { wiki } = await import("./commands/wiki");
124
+ await wiki(cwd, process.argv.slice(3));
125
+ break;
126
+ }
127
+
128
+ case "note": {
129
+ const { note } = await import("./commands/note");
130
+ await note(cwd, process.argv.slice(3));
131
+ break;
132
+ }
133
+
134
+ case "skill": {
135
+ const { skill } = await import("./commands/skill");
136
+ await skill(process.argv.slice(3));
137
+ break;
138
+ }
139
+
122
140
  case "bug-search": {
123
141
  const { bugSearch } = await import("./commands/bug-search");
124
142
  bugSearch(cwd, process.argv.slice(3).join(" "));
@@ -139,6 +157,24 @@ switch (command) {
139
157
  break;
140
158
  }
141
159
 
160
+ case "version":
161
+ case "--version":
162
+ case "-v": {
163
+ const { resolve, dirname } = await import("path");
164
+ const cliPath = resolve(dirname(new URL(import.meta.url).pathname));
165
+ const { readFileSync } = await import("fs");
166
+ try {
167
+ const pkg = JSON.parse(
168
+ readFileSync(resolve(cliPath, "../package.json"), "utf-8")
169
+ );
170
+ console.log(`mink ${pkg.version}`);
171
+ } catch {
172
+ console.log("mink (unknown version)");
173
+ }
174
+ console.log(` location: ${cliPath}`);
175
+ break;
176
+ }
177
+
142
178
  case "help":
143
179
  case "--help":
144
180
  case "-h":
@@ -151,6 +187,16 @@ switch (command) {
151
187
  console.log(" status Display project health at a glance");
152
188
  console.log(" scan [--check] Force a full file index rescan");
153
189
  console.log(" config [key] [value] Manage global user settings");
190
+ console.log();
191
+ console.log("Notes & Wiki:");
192
+ console.log(" wiki <cmd> Manage the notes/wiki vault (init|status|rebuild-index|organize)");
193
+ console.log(" note \"text\" Capture a note to the vault");
194
+ console.log(" note --daily [text] Create or append to today's daily note");
195
+ console.log(" note list [filters] List notes (--category, --tag, --recent)");
196
+ console.log(" note search <term> Full-text search across the vault");
197
+ console.log(" skill install Install /mink:note skill for Claude Code");
198
+ console.log();
199
+ console.log("Automation & Analysis:");
154
200
  console.log(" dashboard [--port=N] Open the real-time web dashboard");
155
201
  console.log(" daemon <cmd> Manage the background daemon (start|stop|restart|logs)");
156
202
  console.log(" cron <cmd> [id] Manage scheduled tasks (list|run|retry)");
@@ -3,7 +3,13 @@ import { mkdirSync, existsSync } from "fs";
3
3
  import { resolve, dirname, basename, join } from "path";
4
4
  import { projectDir, projectMetaPath } from "../core/paths";
5
5
  import { generateProjectId } from "../core/project-id";
6
- import { atomicWriteJson, safeReadJson } from "../core/fs-utils";
6
+ import { atomicWriteJson, atomicWriteText, safeReadJson } from "../core/fs-utils";
7
+ import {
8
+ isWikiEnabled,
9
+ isVaultInitialized,
10
+ isInsideVault,
11
+ vaultProjects,
12
+ } from "../core/vault";
7
13
 
8
14
  interface HookCommand {
9
15
  type: "command";
@@ -26,11 +32,39 @@ export function detectRuntime(): "bun" | "node" {
26
32
  }
27
33
  }
28
34
 
35
+ export function resolveCliPath(): string {
36
+ // When running from the compiled bundle, import.meta.url points to dist/cli.js
37
+ // When running from source, it points to src/commands/init.ts
38
+ const selfPath = new URL(import.meta.url).pathname;
39
+ const selfDir = dirname(selfPath);
40
+
41
+ // If we're running from dist/cli.js, use that directly
42
+ if (selfPath.endsWith("dist/cli.js")) {
43
+ return selfPath;
44
+ }
45
+
46
+ // Check for compiled dist/cli.js relative to project root
47
+ // From src/commands/ go up two levels to project root
48
+ const projectRoot = resolve(selfDir, "../..");
49
+ const distPath = join(projectRoot, "dist", "cli.js");
50
+ if (existsSync(distPath)) return distPath;
51
+
52
+ // Fall back to src/cli.ts (requires bun)
53
+ return resolve(selfDir, "../cli.ts");
54
+ }
55
+
29
56
  export function buildHooksConfig(
30
57
  runtime: "bun" | "node",
31
58
  cliPath: string
32
59
  ): HooksConfig {
33
- const prefix = runtime === "bun" ? `bun run ${cliPath}` : `node ${cliPath}`;
60
+ // If using compiled JS, always use node (universally available)
61
+ // If using .ts source, must use bun
62
+ const isTsSource = cliPath.endsWith(".ts");
63
+ const prefix = isTsSource
64
+ ? `bun run ${cliPath}`
65
+ : runtime === "bun"
66
+ ? `bun run ${cliPath}`
67
+ : `node ${cliPath}`;
34
68
  const hook = (cmd: string): HookCommand[] => [{ type: "command", command: cmd }];
35
69
  return {
36
70
  SessionStart: [{ matcher: "", hooks: hook(`${prefix} session-start`) }],
@@ -100,7 +134,7 @@ function isExistingInstallation(cwd: string): boolean {
100
134
 
101
135
  export async function init(cwd: string): Promise<void> {
102
136
  const runtime = detectRuntime();
103
- const cliPath = resolve(dirname(new URL(import.meta.url).pathname), "../cli.ts");
137
+ const cliPath = resolveCliPath();
104
138
  const hooks = buildHooksConfig(runtime, cliPath);
105
139
  const settingsPath = resolve(cwd, ".claude", "settings.json");
106
140
  const dir = projectDir(cwd);
@@ -119,6 +153,10 @@ export async function init(cwd: string): Promise<void> {
119
153
 
120
154
  const projectId = generateProjectId(cwd);
121
155
 
156
+ // Detect notes project type
157
+ const isNotesProject =
158
+ isWikiEnabled() && isVaultInitialized() && isInsideVault(cwd);
159
+
122
160
  // Write project metadata
123
161
  const metaPath = projectMetaPath(cwd);
124
162
  const existingMeta = safeReadJson(metaPath) as Record<string, unknown> | null;
@@ -128,6 +166,7 @@ export async function init(cwd: string): Promise<void> {
128
166
  name: basename(cwd),
129
167
  initTimestamp: existingMeta?.initTimestamp ?? new Date().toISOString(),
130
168
  version: "0.1.0",
169
+ ...(isNotesProject ? { projectType: "notes" } : {}),
131
170
  });
132
171
 
133
172
  if (upgrading) {
@@ -152,8 +191,42 @@ export async function init(cwd: string): Promise<void> {
152
191
  if (!existsSync(memPath)) {
153
192
  const { seedLearningMemory } = await import("../core/seed");
154
193
  const { serializeLearningMemory } = await import("../core/learning-memory");
155
- const { atomicWriteText } = await import("../core/fs-utils");
156
194
  const mem = seedLearningMemory(cwd);
157
195
  atomicWriteText(memPath, serializeLearningMemory(mem));
158
196
  }
197
+
198
+ // Create wiki project overview if wiki is enabled
199
+ if (isWikiEnabled() && isVaultInitialized() && !isNotesProject) {
200
+ try {
201
+ const projectSlug = basename(cwd);
202
+ const overviewPath = join(vaultProjects(projectSlug), "overview.md");
203
+ if (!existsSync(overviewPath)) {
204
+ const now = new Date().toISOString();
205
+ const overview = [
206
+ `---`,
207
+ `created: "${now}"`,
208
+ `updated: "${now}"`,
209
+ `tags: [project, ${projectSlug}]`,
210
+ `category: projects`,
211
+ `---`,
212
+ ``,
213
+ `# ${projectSlug}`,
214
+ ``,
215
+ `**Path**: \`${cwd}\``,
216
+ `**Initialized**: ${now.split("T")[0]}`,
217
+ ``,
218
+ `## Overview`,
219
+ ``,
220
+ `## Key Decisions`,
221
+ ``,
222
+ `## Links`,
223
+ ``,
224
+ ].join("\n");
225
+ atomicWriteText(overviewPath, overview);
226
+ console.log(` wiki: ${overviewPath}`);
227
+ }
228
+ } catch {
229
+ // Non-critical — don't fail init
230
+ }
231
+ }
159
232
  }
@@ -0,0 +1,267 @@
1
+ import { resolve } from "path";
2
+ import { existsSync, readFileSync } from "fs";
3
+ import {
4
+ isVaultInitialized,
5
+ isWikiEnabled,
6
+ resolveVaultPath,
7
+ } from "../core/vault";
8
+ import { resolveConfigValue } from "../core/global-config";
9
+ import { generateProjectId } from "../core/project-id";
10
+ import {
11
+ createNote,
12
+ appendToDaily,
13
+ ingestFile,
14
+ slugifyTitle,
15
+ } from "../core/note-writer";
16
+ import {
17
+ updateVaultIndexForFile,
18
+ searchVaultIndex,
19
+ getRecentNotes,
20
+ loadVaultIndex,
21
+ } from "../core/note-index";
22
+ import { updateMasterIndex } from "../core/note-linker";
23
+ import type { NoteCategory, NoteMetadata } from "../types/note";
24
+
25
+ export async function note(
26
+ cwd: string,
27
+ args: string[]
28
+ ): Promise<void> {
29
+ if (!isWikiEnabled()) {
30
+ console.error("[mink] wiki feature is disabled");
31
+ console.error(" Enable with: mink config wiki.enabled true");
32
+ process.exit(1);
33
+ }
34
+
35
+ if (!isVaultInitialized()) {
36
+ console.error("[mink] vault not initialized");
37
+ console.error(" Run 'mink wiki init' first.");
38
+ process.exit(1);
39
+ }
40
+
41
+ // Handle subcommands
42
+ if (args[0] === "list") {
43
+ noteList(args.slice(1));
44
+ return;
45
+ }
46
+ if (args[0] === "search") {
47
+ noteSearch(args.slice(1).join(" "));
48
+ return;
49
+ }
50
+
51
+ // Parse flags
52
+ const parsed = parseNoteArgs(args);
53
+
54
+ // Handle daily note
55
+ if (parsed.daily) {
56
+ const date = new Date().toISOString().split("T")[0];
57
+ const content = parsed.positional || parsed.body || "";
58
+ const filePath = appendToDaily(date, content);
59
+ updateVaultIndexForFile(
60
+ filePath,
61
+ readFileSync(filePath, "utf-8")
62
+ );
63
+ console.log(`[mink] daily note: ${filePath}`);
64
+ return;
65
+ }
66
+
67
+ // Handle file ingestion
68
+ if (parsed.file) {
69
+ const sourcePath = resolve(cwd, parsed.file);
70
+ if (!existsSync(sourcePath)) {
71
+ console.error(`[mink] file not found: ${sourcePath}`);
72
+ process.exit(1);
73
+ }
74
+ const result = ingestFile(sourcePath, {
75
+ category: parsed.category,
76
+ tags: parsed.tags,
77
+ projectSlug: parsed.project,
78
+ sourceProject: detectSourceProject(cwd),
79
+ });
80
+ updateVaultIndexForFile(result.filePath, result.content);
81
+ console.log(`[mink] ingested: ${result.filePath}`);
82
+ return;
83
+ }
84
+
85
+ // Regular note creation
86
+ const title =
87
+ parsed.title ||
88
+ (parsed.positional
89
+ ? parsed.positional.split("\n")[0].slice(0, 80)
90
+ : `note-${Date.now()}`);
91
+
92
+ const body = parsed.body || parsed.positional || "";
93
+
94
+ const meta: NoteMetadata = {
95
+ title,
96
+ category: parsed.category,
97
+ tags: parsed.tags,
98
+ created: new Date().toISOString(),
99
+ updated: new Date().toISOString(),
100
+ template: parsed.template,
101
+ projectSlug: parsed.project,
102
+ sourceProject: detectSourceProject(cwd),
103
+ body,
104
+ };
105
+
106
+ const result = createNote(meta);
107
+ updateVaultIndexForFile(result.filePath, result.content);
108
+ updateMasterIndex(resolveVaultPath());
109
+
110
+ const tagsStr =
111
+ meta.tags.length > 0 ? ` [${meta.tags.join(", ")}]` : "";
112
+ console.log(
113
+ `[mink] note saved: ${result.filePath}${tagsStr}`
114
+ );
115
+ }
116
+
117
+ interface ParsedNoteArgs {
118
+ title: string;
119
+ body: string;
120
+ category: NoteCategory;
121
+ tags: string[];
122
+ project: string;
123
+ template: string;
124
+ daily: boolean;
125
+ file: string;
126
+ positional: string;
127
+ }
128
+
129
+ function parseNoteArgs(args: string[]): ParsedNoteArgs {
130
+ const result: ParsedNoteArgs = {
131
+ title: "",
132
+ body: "",
133
+ category: resolveConfigValue("notes.default-category").value as NoteCategory,
134
+ tags: [],
135
+ project: "",
136
+ template: "",
137
+ daily: false,
138
+ file: "",
139
+ positional: "",
140
+ };
141
+
142
+ const positionalParts: string[] = [];
143
+ let i = 0;
144
+
145
+ while (i < args.length) {
146
+ const arg = args[i];
147
+
148
+ if (arg === "--title" && i + 1 < args.length) {
149
+ result.title = args[++i];
150
+ } else if (arg === "--body" && i + 1 < args.length) {
151
+ result.body = args[++i];
152
+ } else if (arg === "--category" && i + 1 < args.length) {
153
+ result.category = args[++i] as NoteCategory;
154
+ } else if (arg === "--tags" && i + 1 < args.length) {
155
+ result.tags = args[++i].split(",").map((t) => t.trim()).filter(Boolean);
156
+ } else if (arg === "--project" && i + 1 < args.length) {
157
+ result.project = args[++i];
158
+ if (result.category === "inbox") {
159
+ result.category = "projects";
160
+ }
161
+ } else if (arg === "--template" && i + 1 < args.length) {
162
+ result.template = args[++i];
163
+ } else if (arg === "--daily") {
164
+ result.daily = true;
165
+ } else if (arg === "--file" && i + 1 < args.length) {
166
+ result.file = args[++i];
167
+ } else if (!arg.startsWith("--")) {
168
+ positionalParts.push(arg);
169
+ }
170
+
171
+ i++;
172
+ }
173
+
174
+ result.positional = positionalParts.join(" ");
175
+ return result;
176
+ }
177
+
178
+ function detectSourceProject(cwd: string): string | undefined {
179
+ try {
180
+ const vaultPath = resolveVaultPath();
181
+ // Don't mark notes created from within the vault itself
182
+ if (cwd.startsWith(vaultPath)) return undefined;
183
+ return generateProjectId(cwd);
184
+ } catch {
185
+ return undefined;
186
+ }
187
+ }
188
+
189
+ function noteList(args: string[]): void {
190
+ const index = loadVaultIndex();
191
+ let entries = Object.values(index.entries);
192
+
193
+ // Parse filters
194
+ let categoryFilter = "";
195
+ let tagFilter = "";
196
+ let recent = 20;
197
+
198
+ for (let i = 0; i < args.length; i++) {
199
+ if (args[i] === "--category" && i + 1 < args.length) {
200
+ categoryFilter = args[++i];
201
+ } else if (args[i] === "--tag" && i + 1 < args.length) {
202
+ tagFilter = args[++i];
203
+ } else if (args[i] === "--recent" && i + 1 < args.length) {
204
+ recent = parseInt(args[++i], 10) || 20;
205
+ }
206
+ }
207
+
208
+ if (categoryFilter) {
209
+ entries = entries.filter((e) => e.category === categoryFilter);
210
+ }
211
+ if (tagFilter) {
212
+ entries = entries.filter((e) =>
213
+ e.tags.some((t) => t.toLowerCase().includes(tagFilter.toLowerCase()))
214
+ );
215
+ }
216
+
217
+ // Sort by last modified, most recent first
218
+ entries.sort((a, b) => b.lastModified.localeCompare(a.lastModified));
219
+ entries = entries.slice(0, recent);
220
+
221
+ if (entries.length === 0) {
222
+ console.log("[mink] no notes found");
223
+ return;
224
+ }
225
+
226
+ console.log(`[mink] ${entries.length} notes:`);
227
+ console.log();
228
+
229
+ for (const entry of entries) {
230
+ const tags =
231
+ entry.tags.length > 0 ? ` [${entry.tags.join(", ")}]` : "";
232
+ console.log(` ${entry.category.padEnd(10)} ${entry.title}${tags}`);
233
+ console.log(` ${entry.filePath}`);
234
+ }
235
+ }
236
+
237
+ function noteSearch(term: string): void {
238
+ if (!term.trim()) {
239
+ console.error("Usage: mink note search <term>");
240
+ process.exit(1);
241
+ }
242
+
243
+ const results = searchVaultIndex(term);
244
+
245
+ if (results.length === 0) {
246
+ console.log(`[mink] no notes matching "${term}"`);
247
+ return;
248
+ }
249
+
250
+ console.log(`[mink] ${results.length} notes matching "${term}":`);
251
+ console.log();
252
+
253
+ for (const entry of results.slice(0, 20)) {
254
+ const tags =
255
+ entry.tags.length > 0 ? ` [${entry.tags.join(", ")}]` : "";
256
+ console.log(` ${entry.category.padEnd(10)} ${entry.title}${tags}`);
257
+ if (entry.description) {
258
+ console.log(` ${entry.description}`);
259
+ }
260
+ console.log(` ${entry.filePath}`);
261
+ }
262
+
263
+ if (results.length > 20) {
264
+ console.log();
265
+ console.log(` ... and ${results.length - 20} more results`);
266
+ }
267
+ }
@@ -3,6 +3,8 @@ import { createSessionState } from "../core/session";
3
3
  import { projectDir, sessionPath, actionLogPath } from "../core/paths";
4
4
  import { atomicWriteJson } from "../core/fs-utils";
5
5
  import { createActionLogWriter } from "../core/action-log";
6
+ import { isWikiEnabled, isVaultInitialized, isInsideVault } from "../core/vault";
7
+ import { loadVaultIndex } from "../core/note-index";
6
8
 
7
9
  export function sessionStart(cwd: string): void {
8
10
  const dir = projectDir(cwd);
@@ -18,4 +20,28 @@ export function sessionStart(cwd: string): void {
18
20
  } catch {
19
21
  // Never crash hooks
20
22
  }
23
+
24
+ // Emit vault context if wiki is enabled
25
+ try {
26
+ if (isWikiEnabled() && isVaultInitialized()) {
27
+ const index = loadVaultIndex();
28
+ const inboxCount = Object.values(index.entries).filter(
29
+ (e) => e.category === "inbox"
30
+ ).length;
31
+
32
+ if (inboxCount > 0) {
33
+ console.error(
34
+ `[mink] vault: ${inboxCount} notes in inbox need categorization`
35
+ );
36
+ }
37
+
38
+ if (isInsideVault(cwd)) {
39
+ console.error(
40
+ `[mink] notes project detected — vault has ${index.totalNotes} notes`
41
+ );
42
+ }
43
+ }
44
+ } catch {
45
+ // Never crash hooks
46
+ }
21
47
  }