planmode 0.3.0 → 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/mcp.js CHANGED
@@ -1,112 +1,361 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/mcp.ts
4
- import fs12 from "fs";
5
- import path12 from "path";
6
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
- import { z } from "zod/v4";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
9
11
 
10
12
  // src/lib/logger.ts
11
- var RESET = "\x1B[0m";
12
- var RED = "\x1B[31m";
13
- var GREEN = "\x1B[32m";
14
- var YELLOW = "\x1B[33m";
15
- var CYAN = "\x1B[36m";
16
- var DIM = "\x1B[2m";
17
- var BOLD = "\x1B[1m";
18
13
  function stripAnsi(str) {
19
14
  return str.replace(/\x1b\[[0-9;]*m/g, "");
20
15
  }
21
- var capturing = false;
22
- var captured = [];
23
- var logger = {
24
- capture() {
25
- capturing = true;
26
- captured = [];
27
- },
28
- flush() {
29
- const messages = captured;
30
- captured = [];
16
+ var RESET, RED, GREEN, YELLOW, CYAN, DIM, BOLD, capturing, captured, logger;
17
+ var init_logger = __esm({
18
+ "src/lib/logger.ts"() {
19
+ "use strict";
20
+ RESET = "\x1B[0m";
21
+ RED = "\x1B[31m";
22
+ GREEN = "\x1B[32m";
23
+ YELLOW = "\x1B[33m";
24
+ CYAN = "\x1B[36m";
25
+ DIM = "\x1B[2m";
26
+ BOLD = "\x1B[1m";
31
27
  capturing = false;
32
- return messages;
33
- },
34
- isCapturing() {
35
- return capturing;
36
- },
37
- info(msg) {
38
- const text2 = `info ${msg}`;
39
- if (capturing) {
40
- captured.push(stripAnsi(text2));
41
- } else {
42
- console.log(`${CYAN}info${RESET} ${msg}`);
43
- }
44
- },
45
- success(msg) {
46
- const text2 = `\u2713 ${msg}`;
47
- if (capturing) {
48
- captured.push(stripAnsi(text2));
49
- } else {
50
- console.log(`${GREEN}\u2713${RESET} ${msg}`);
51
- }
52
- },
53
- warn(msg) {
54
- const text2 = `warn ${msg}`;
55
- if (capturing) {
56
- captured.push(stripAnsi(text2));
57
- } else {
58
- console.log(`${YELLOW}warn${RESET} ${msg}`);
59
- }
60
- },
61
- error(msg) {
62
- const text2 = `error ${msg}`;
63
- if (capturing) {
64
- captured.push(stripAnsi(text2));
65
- } else {
66
- console.error(`${RED}error${RESET} ${msg}`);
67
- }
68
- },
69
- dim(msg) {
70
- if (capturing) {
71
- captured.push(msg);
72
- } else {
73
- console.log(`${DIM}${msg}${RESET}`);
74
- }
75
- },
76
- bold(msg) {
77
- if (capturing) {
78
- captured.push(msg);
79
- } else {
80
- console.log(`${BOLD}${msg}${RESET}`);
81
- }
82
- },
83
- table(headers, rows) {
84
- const colWidths = headers.map(
85
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
86
- );
87
- const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
88
- if (capturing) {
89
- captured.push(` ${header}`);
90
- for (const row of rows) {
91
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
92
- captured.push(` ${line}`);
28
+ captured = [];
29
+ logger = {
30
+ capture() {
31
+ capturing = true;
32
+ captured = [];
33
+ },
34
+ flush() {
35
+ const messages = captured;
36
+ captured = [];
37
+ capturing = false;
38
+ return messages;
39
+ },
40
+ isCapturing() {
41
+ return capturing;
42
+ },
43
+ info(msg) {
44
+ const text2 = `info ${msg}`;
45
+ if (capturing) {
46
+ captured.push(stripAnsi(text2));
47
+ } else {
48
+ console.log(`${CYAN}info${RESET} ${msg}`);
49
+ }
50
+ },
51
+ success(msg) {
52
+ const text2 = `\u2713 ${msg}`;
53
+ if (capturing) {
54
+ captured.push(stripAnsi(text2));
55
+ } else {
56
+ console.log(`${GREEN}\u2713${RESET} ${msg}`);
57
+ }
58
+ },
59
+ warn(msg) {
60
+ const text2 = `warn ${msg}`;
61
+ if (capturing) {
62
+ captured.push(stripAnsi(text2));
63
+ } else {
64
+ console.log(`${YELLOW}warn${RESET} ${msg}`);
65
+ }
66
+ },
67
+ error(msg) {
68
+ const text2 = `error ${msg}`;
69
+ if (capturing) {
70
+ captured.push(stripAnsi(text2));
71
+ } else {
72
+ console.error(`${RED}error${RESET} ${msg}`);
73
+ }
74
+ },
75
+ dim(msg) {
76
+ if (capturing) {
77
+ captured.push(msg);
78
+ } else {
79
+ console.log(`${DIM}${msg}${RESET}`);
80
+ }
81
+ },
82
+ bold(msg) {
83
+ if (capturing) {
84
+ captured.push(msg);
85
+ } else {
86
+ console.log(`${BOLD}${msg}${RESET}`);
87
+ }
88
+ },
89
+ table(headers, rows) {
90
+ const colWidths = headers.map(
91
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
92
+ );
93
+ const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
94
+ if (capturing) {
95
+ captured.push(` ${header}`);
96
+ for (const row of rows) {
97
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
98
+ captured.push(` ${line}`);
99
+ }
100
+ } else {
101
+ console.log(` ${DIM}${header}${RESET}`);
102
+ for (const row of rows) {
103
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
104
+ console.log(` ${line}`);
105
+ }
106
+ }
107
+ },
108
+ blank() {
109
+ if (capturing) {
110
+ captured.push("");
111
+ } else {
112
+ console.log();
113
+ }
93
114
  }
94
- } else {
95
- console.log(` ${DIM}${header}${RESET}`);
96
- for (const row of rows) {
97
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
98
- console.log(` ${line}`);
115
+ };
116
+ }
117
+ });
118
+
119
+ // src/lib/context.ts
120
+ var context_exports = {};
121
+ __export(context_exports, {
122
+ addContextRepo: () => addContextRepo,
123
+ formatSize: () => formatSize,
124
+ getContextSummary: () => getContextSummary,
125
+ readContextIndex: () => readContextIndex,
126
+ reindexContext: () => reindexContext,
127
+ removeContextRepo: () => removeContextRepo,
128
+ walkDirectory: () => walkDirectory,
129
+ writeContextIndex: () => writeContextIndex
130
+ });
131
+ import fs12 from "fs";
132
+ import path12 from "path";
133
+ import { parse as parse4, stringify as stringify6 } from "yaml";
134
+ function getContextPath(projectDir) {
135
+ return path12.join(projectDir, CONTEXT_DIR, CONTEXT_FILE);
136
+ }
137
+ function emptyIndex() {
138
+ return { version: 1, repos: [] };
139
+ }
140
+ function readContextIndex(projectDir = process.cwd()) {
141
+ const filePath = getContextPath(projectDir);
142
+ try {
143
+ const raw = fs12.readFileSync(filePath, "utf-8");
144
+ const data = parse4(raw);
145
+ return data ?? emptyIndex();
146
+ } catch {
147
+ return emptyIndex();
148
+ }
149
+ }
150
+ function writeContextIndex(index, projectDir = process.cwd()) {
151
+ const dirPath = path12.join(projectDir, CONTEXT_DIR);
152
+ fs12.mkdirSync(dirPath, { recursive: true });
153
+ const filePath = getContextPath(projectDir);
154
+ fs12.writeFileSync(filePath, stringify6(index), "utf-8");
155
+ }
156
+ function walkDirectory(dirPath) {
157
+ const files = [];
158
+ function walk(currentPath) {
159
+ let entries;
160
+ try {
161
+ entries = fs12.readdirSync(currentPath, { withFileTypes: true });
162
+ } catch {
163
+ return;
164
+ }
165
+ for (const entry of entries) {
166
+ if (entry.name.startsWith(".") && IGNORED_DIRS.has(entry.name)) continue;
167
+ if (IGNORED_DIRS.has(entry.name)) continue;
168
+ const fullPath = path12.join(currentPath, entry.name);
169
+ if (entry.isDirectory()) {
170
+ walk(fullPath);
171
+ } else if (entry.isFile()) {
172
+ const ext = path12.extname(entry.name).toLowerCase();
173
+ if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
174
+ try {
175
+ const stat = fs12.statSync(fullPath);
176
+ const relativePath = path12.relative(dirPath, fullPath);
177
+ files.push({
178
+ path: relativePath,
179
+ extension: ext,
180
+ size: stat.size,
181
+ modified_at: stat.mtime.toISOString()
182
+ });
183
+ } catch {
184
+ }
99
185
  }
100
186
  }
101
- },
102
- blank() {
103
- if (capturing) {
104
- captured.push("");
105
- } else {
106
- console.log();
187
+ }
188
+ walk(dirPath);
189
+ return files;
190
+ }
191
+ function addContextRepo(repoPath, options = {}) {
192
+ const projectDir = options.projectDir ?? process.cwd();
193
+ const absolutePath = path12.resolve(projectDir, repoPath);
194
+ if (!fs12.existsSync(absolutePath)) {
195
+ throw new Error(`Directory not found: ${repoPath}`);
196
+ }
197
+ if (!fs12.statSync(absolutePath).isDirectory()) {
198
+ throw new Error(`Not a directory: ${repoPath}`);
199
+ }
200
+ const index = readContextIndex(projectDir);
201
+ const relative = path12.relative(projectDir, absolutePath);
202
+ const isInsideProject = !relative.startsWith("..") && !path12.isAbsolute(relative);
203
+ const storedPath = isInsideProject ? relative : absolutePath;
204
+ const existing = index.repos.find(
205
+ (r) => r.repo.path === storedPath || r.repo.name === options.name
206
+ );
207
+ if (existing) {
208
+ throw new Error(
209
+ `Context repo already exists: ${existing.repo.name ?? existing.repo.path}. Use \`planmode context reindex\` to refresh.`
210
+ );
211
+ }
212
+ logger.info(`Scanning ${absolutePath}...`);
213
+ const files = walkDirectory(absolutePath);
214
+ const now = (/* @__PURE__ */ new Date()).toISOString();
215
+ const repoIndex = {
216
+ repo: {
217
+ path: storedPath,
218
+ name: options.name,
219
+ added_at: now
220
+ },
221
+ files,
222
+ indexed_at: now,
223
+ file_count: files.length,
224
+ total_size: files.reduce((sum, f) => sum + f.size, 0)
225
+ };
226
+ index.repos.push(repoIndex);
227
+ writeContextIndex(index, projectDir);
228
+ logger.success(`Added "${options.name ?? storedPath}" \u2014 ${files.length} file(s), ${formatSize(repoIndex.total_size)}`);
229
+ const breakdown = getTypeBreakdown(files);
230
+ if (breakdown.length > 0) {
231
+ logger.dim(` ${breakdown.join(", ")}`);
232
+ }
233
+ return repoIndex;
234
+ }
235
+ function removeContextRepo(pathOrName, projectDir = process.cwd()) {
236
+ const index = readContextIndex(projectDir);
237
+ const idx = index.repos.findIndex(
238
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
239
+ );
240
+ if (idx === -1) {
241
+ throw new Error(`Context repo not found: ${pathOrName}`);
242
+ }
243
+ const removed = index.repos[idx];
244
+ index.repos.splice(idx, 1);
245
+ writeContextIndex(index, projectDir);
246
+ logger.success(`Removed "${removed.repo.name ?? removed.repo.path}"`);
247
+ }
248
+ function reindexContext(pathOrName, projectDir = process.cwd()) {
249
+ const index = readContextIndex(projectDir);
250
+ if (index.repos.length === 0) {
251
+ throw new Error("No context repos configured. Use `planmode context add <path>` first.");
252
+ }
253
+ const targets = pathOrName ? index.repos.filter(
254
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
255
+ ) : index.repos;
256
+ if (pathOrName && targets.length === 0) {
257
+ throw new Error(`Context repo not found: ${pathOrName}`);
258
+ }
259
+ for (const repo of targets) {
260
+ const absolutePath = path12.resolve(projectDir, repo.repo.path);
261
+ if (!fs12.existsSync(absolutePath)) {
262
+ logger.warn(`Directory not found, skipping: ${repo.repo.path}`);
263
+ continue;
107
264
  }
265
+ logger.info(`Re-scanning ${repo.repo.name ?? repo.repo.path}...`);
266
+ const files = walkDirectory(absolutePath);
267
+ repo.files = files;
268
+ repo.indexed_at = (/* @__PURE__ */ new Date()).toISOString();
269
+ repo.file_count = files.length;
270
+ repo.total_size = files.reduce((sum, f) => sum + f.size, 0);
271
+ logger.success(`Reindexed "${repo.repo.name ?? repo.repo.path}" \u2014 ${files.length} file(s), ${formatSize(repo.total_size)}`);
108
272
  }
109
- };
273
+ writeContextIndex(index, projectDir);
274
+ }
275
+ function getContextSummary(projectDir = process.cwd()) {
276
+ const index = readContextIndex(projectDir);
277
+ return {
278
+ totalRepos: index.repos.length,
279
+ totalFiles: index.repos.reduce((sum, r) => sum + r.file_count, 0),
280
+ totalSize: index.repos.reduce((sum, r) => sum + r.total_size, 0),
281
+ repos: index.repos.map((r) => ({
282
+ name: r.repo.name ?? r.repo.path,
283
+ path: r.repo.path,
284
+ fileCount: r.file_count,
285
+ totalSize: r.total_size,
286
+ typeBreakdown: getTypeBreakdown(r.files),
287
+ indexedAt: r.indexed_at
288
+ }))
289
+ };
290
+ }
291
+ function getTypeBreakdown(files) {
292
+ const counts = /* @__PURE__ */ new Map();
293
+ for (const file of files) {
294
+ counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
295
+ }
296
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`);
297
+ }
298
+ function formatSize(bytes) {
299
+ if (bytes < 1024) return `${bytes} B`;
300
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
301
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
302
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
303
+ }
304
+ var CONTEXT_DIR, CONTEXT_FILE, SUPPORTED_EXTENSIONS, IGNORED_DIRS;
305
+ var init_context = __esm({
306
+ "src/lib/context.ts"() {
307
+ "use strict";
308
+ init_logger();
309
+ CONTEXT_DIR = ".planmode";
310
+ CONTEXT_FILE = "context.yaml";
311
+ SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
312
+ ".txt",
313
+ ".md",
314
+ ".markdown",
315
+ ".pdf",
316
+ ".rtf",
317
+ ".doc",
318
+ ".docx",
319
+ ".csv",
320
+ ".tsv",
321
+ ".json",
322
+ ".yaml",
323
+ ".yml",
324
+ ".xml",
325
+ ".html",
326
+ ".htm",
327
+ ".rst",
328
+ ".org",
329
+ ".tex",
330
+ ".log"
331
+ ]);
332
+ IGNORED_DIRS = /* @__PURE__ */ new Set([
333
+ "node_modules",
334
+ ".git",
335
+ "dist",
336
+ "build",
337
+ ".next",
338
+ "__pycache__",
339
+ ".venv",
340
+ "venv",
341
+ ".tox",
342
+ "target",
343
+ "out",
344
+ ".cache",
345
+ ".turbo",
346
+ "coverage",
347
+ ".nyc_output"
348
+ ]);
349
+ }
350
+ });
351
+
352
+ // src/mcp.ts
353
+ init_logger();
354
+ import fs13 from "fs";
355
+ import path13 from "path";
356
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
357
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
358
+ import { z } from "zod/v4";
110
359
 
111
360
  // src/lib/registry.ts
112
361
  import fs2 from "fs";
@@ -651,6 +900,9 @@ function coerceValue(raw, def) {
651
900
  }
652
901
  }
653
902
 
903
+ // src/lib/installer.ts
904
+ init_logger();
905
+
654
906
  // src/lib/analytics.ts
655
907
  var API_BASE = "https://api.planmode.org";
656
908
  function trackDownload(packageName) {
@@ -1052,6 +1304,7 @@ function createPackage(options) {
1052
1304
  }
1053
1305
 
1054
1306
  // src/lib/publisher.ts
1307
+ init_logger();
1055
1308
  async function publishPackage(options = {}) {
1056
1309
  const cwd = options.projectDir ?? process.cwd();
1057
1310
  const interactive = options.interactive ?? false;
@@ -1852,6 +2105,7 @@ function detectCategory(data) {
1852
2105
  }
1853
2106
 
1854
2107
  // src/mcp.ts
2108
+ init_context();
1855
2109
  function withCapture(fn) {
1856
2110
  logger.capture();
1857
2111
  try {
@@ -2129,14 +2383,14 @@ server.registerTool(
2129
2383
  const entry = lockfile.packages[packageName];
2130
2384
  if (!entry) {
2131
2385
  const candidates = [
2132
- path12.join(dir, "plans", `${packageName}.md`),
2133
- path12.join(dir, ".claude", "rules", `${packageName}.md`),
2134
- path12.join(dir, "prompts", `${packageName}.md`)
2386
+ path13.join(dir, "plans", `${packageName}.md`),
2387
+ path13.join(dir, ".claude", "rules", `${packageName}.md`),
2388
+ path13.join(dir, "prompts", `${packageName}.md`)
2135
2389
  ];
2136
2390
  for (const candidate of candidates) {
2137
- if (fs12.existsSync(candidate)) {
2138
- const content2 = fs12.readFileSync(candidate, "utf-8");
2139
- const relativePath = path12.relative(dir, candidate);
2391
+ if (fs13.existsSync(candidate)) {
2392
+ const content2 = fs13.readFileSync(candidate, "utf-8");
2393
+ const relativePath = path13.relative(dir, candidate);
2140
2394
  return textResult(`# ${packageName}
2141
2395
  **Location:** ${relativePath}
2142
2396
 
@@ -2150,14 +2404,14 @@ ${content2}`);
2150
2404
  true
2151
2405
  );
2152
2406
  }
2153
- const fullPath = path12.join(dir, entry.installed_to);
2154
- if (!fs12.existsSync(fullPath)) {
2407
+ const fullPath = path13.join(dir, entry.installed_to);
2408
+ if (!fs13.existsSync(fullPath)) {
2155
2409
  return textResult(
2156
2410
  `Package '${packageName}' is in the lockfile but the file is missing at ${entry.installed_to}. Try reinstalling with planmode_install.`,
2157
2411
  true
2158
2412
  );
2159
2413
  }
2160
- const content = fs12.readFileSync(fullPath, "utf-8");
2414
+ const content = fs13.readFileSync(fullPath, "utf-8");
2161
2415
  return textResult(
2162
2416
  `# ${packageName} (${entry.type} v${entry.version})
2163
2417
  **Location:** ${entry.installed_to}
@@ -2344,17 +2598,17 @@ server.registerTool(
2344
2598
  async ({ prompt: promptName, variables, projectDir }) => {
2345
2599
  try {
2346
2600
  const dir = projectDir ?? process.cwd();
2347
- const localPath = path12.join(dir, "prompts", `${promptName}.md`);
2348
- const localManifestPath = path12.join(dir, "prompts", promptName, "planmode.yaml");
2601
+ const localPath = path13.join(dir, "prompts", `${promptName}.md`);
2602
+ const localManifestPath = path13.join(dir, "prompts", promptName, "planmode.yaml");
2349
2603
  let content;
2350
2604
  let manifest;
2351
- if (fs12.existsSync(localManifestPath)) {
2352
- const raw = fs12.readFileSync(localManifestPath, "utf-8");
2605
+ if (fs13.existsSync(localManifestPath)) {
2606
+ const raw = fs13.readFileSync(localManifestPath, "utf-8");
2353
2607
  manifest = parseManifest(raw);
2354
- const promptDir = path12.join(dir, "prompts", promptName);
2608
+ const promptDir = path13.join(dir, "prompts", promptName);
2355
2609
  content = readPackageContent(promptDir, manifest);
2356
- } else if (fs12.existsSync(localPath)) {
2357
- content = fs12.readFileSync(localPath, "utf-8");
2610
+ } else if (fs13.existsSync(localPath)) {
2611
+ content = fs13.readFileSync(localPath, "utf-8");
2358
2612
  } else {
2359
2613
  return textResult(
2360
2614
  `Prompt '${promptName}' not found locally. Install it first using the planmode_install tool.`,
@@ -2536,6 +2790,128 @@ server.registerTool(
2536
2790
  }
2537
2791
  }
2538
2792
  );
2793
+ server.registerTool(
2794
+ "planmode_context_add",
2795
+ {
2796
+ description: "Add a document directory to the project context. Indexes all text-based files (md, pdf, txt, json, yaml, csv, html, etc.) and stores their metadata in .planmode/context.yaml. The AI can then see what documents are available and read them on demand.",
2797
+ inputSchema: {
2798
+ path: z.string().describe("Path to the document directory (relative to project or absolute)"),
2799
+ name: z.string().optional().describe("Human-readable label for this directory"),
2800
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2801
+ }
2802
+ },
2803
+ async ({ path: dirPath, name, projectDir }) => {
2804
+ try {
2805
+ const { result, messages } = withCapture(
2806
+ () => addContextRepo(dirPath, { name, projectDir })
2807
+ );
2808
+ const breakdown = result.files.length > 0 ? `
2809
+ Types: ${getTypeBreakdownText(result)}` : "";
2810
+ return textResult(
2811
+ formatMessages(
2812
+ messages,
2813
+ `Added "${name ?? result.repo.path}" \u2014 ${result.file_count} file(s), ${formatSize(result.total_size)}${breakdown}`
2814
+ )
2815
+ );
2816
+ } catch (err) {
2817
+ return errorResult("Error adding context repo", err);
2818
+ }
2819
+ }
2820
+ );
2821
+ server.registerTool(
2822
+ "planmode_context_remove",
2823
+ {
2824
+ description: "Remove a document directory from the project context",
2825
+ inputSchema: {
2826
+ pathOrName: z.string().describe("Path or name of the context repo to remove"),
2827
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2828
+ }
2829
+ },
2830
+ async ({ pathOrName, projectDir }) => {
2831
+ try {
2832
+ const { messages } = withCapture(
2833
+ () => removeContextRepo(pathOrName, projectDir)
2834
+ );
2835
+ return textResult(formatMessages(messages) || `Removed "${pathOrName}" from context.`);
2836
+ } catch (err) {
2837
+ return errorResult("Error removing context repo", err);
2838
+ }
2839
+ }
2840
+ );
2841
+ server.registerTool(
2842
+ "planmode_context_list",
2843
+ {
2844
+ description: "List all document directories in the project context with file counts, sizes, and type breakdowns. Use this to see what reference documents are available for the project.",
2845
+ inputSchema: {
2846
+ detailed: z.boolean().optional().describe("Include full file listings for each repo (default: false, shows only summaries)"),
2847
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2848
+ }
2849
+ },
2850
+ async ({ detailed, projectDir }) => {
2851
+ try {
2852
+ const summary = getContextSummary(projectDir);
2853
+ if (summary.totalRepos === 0) {
2854
+ return textResult("No context repos configured. Use planmode_context_add to add a document directory.");
2855
+ }
2856
+ const lines = [
2857
+ `**${summary.totalRepos} context repo(s)** \u2014 ${summary.totalFiles} file(s), ${formatSize(summary.totalSize)}`,
2858
+ ""
2859
+ ];
2860
+ for (const repo of summary.repos) {
2861
+ lines.push(`### ${repo.name}`);
2862
+ lines.push(`- **Path:** ${repo.path}`);
2863
+ lines.push(`- **Files:** ${repo.fileCount} (${formatSize(repo.totalSize)})`);
2864
+ if (repo.typeBreakdown.length > 0) {
2865
+ lines.push(`- **Types:** ${repo.typeBreakdown.join(", ")}`);
2866
+ }
2867
+ lines.push(`- **Indexed:** ${repo.indexedAt}`);
2868
+ if (detailed) {
2869
+ const index = (await Promise.resolve().then(() => (init_context(), context_exports))).readContextIndex(projectDir);
2870
+ const repoIndex = index.repos.find(
2871
+ (r) => r.repo.path === repo.path || r.repo.name === repo.name
2872
+ );
2873
+ if (repoIndex && repoIndex.files.length > 0) {
2874
+ lines.push("", "**Files:**");
2875
+ for (const file of repoIndex.files) {
2876
+ lines.push(`- \`${file.path}\` (${file.extension}, ${formatSize(file.size)})`);
2877
+ }
2878
+ }
2879
+ }
2880
+ lines.push("");
2881
+ }
2882
+ return textResult(lines.join("\n"));
2883
+ } catch (err) {
2884
+ return errorResult("Error listing context", err);
2885
+ }
2886
+ }
2887
+ );
2888
+ server.registerTool(
2889
+ "planmode_context_reindex",
2890
+ {
2891
+ description: "Re-scan files in one or all context directories to update the file index",
2892
+ inputSchema: {
2893
+ pathOrName: z.string().optional().describe("Path or name of a specific repo to reindex (omit to reindex all)"),
2894
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2895
+ }
2896
+ },
2897
+ async ({ pathOrName, projectDir }) => {
2898
+ try {
2899
+ const { messages } = withCapture(
2900
+ () => reindexContext(pathOrName, projectDir)
2901
+ );
2902
+ return textResult(formatMessages(messages) || "Reindex complete.");
2903
+ } catch (err) {
2904
+ return errorResult("Error reindexing context", err);
2905
+ }
2906
+ }
2907
+ );
2908
+ function getTypeBreakdownText(repoIndex) {
2909
+ const counts = /* @__PURE__ */ new Map();
2910
+ for (const file of repoIndex.files) {
2911
+ counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
2912
+ }
2913
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`).join(", ");
2914
+ }
2539
2915
  server.registerResource(
2540
2916
  "installed-packages",
2541
2917
  new ResourceTemplate("planmode://packages/{name}", {
@@ -2568,10 +2944,10 @@ server.registerResource(
2568
2944
  }]
2569
2945
  };
2570
2946
  }
2571
- const fullPath = path12.join(process.cwd(), entry.installed_to);
2947
+ const fullPath = path13.join(process.cwd(), entry.installed_to);
2572
2948
  let content;
2573
2949
  try {
2574
- content = fs12.readFileSync(fullPath, "utf-8");
2950
+ content = fs13.readFileSync(fullPath, "utf-8");
2575
2951
  } catch {
2576
2952
  content = `File not found at ${entry.installed_to}. The package may need to be reinstalled.`;
2577
2953
  }