mind-palace-graph 0.3.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 (51) hide show
  1. package/INSTALL.md +387 -0
  2. package/README.md +602 -0
  3. package/dist/api.d.ts +682 -0
  4. package/dist/api.js +660 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/cli.d.ts +95 -0
  7. package/dist/cli.js +856 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/format.d.ts +16 -0
  10. package/dist/format.js +199 -0
  11. package/dist/format.js.map +1 -0
  12. package/dist/fuzzy.d.ts +45 -0
  13. package/dist/fuzzy.js +150 -0
  14. package/dist/fuzzy.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.js +528 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/mcp-server.d.ts +24 -0
  19. package/dist/mcp-server.js +187 -0
  20. package/dist/mcp-server.js.map +1 -0
  21. package/dist/mind-palace.d.ts +148 -0
  22. package/dist/mind-palace.js +780 -0
  23. package/dist/mind-palace.js.map +1 -0
  24. package/dist/nodes.d.ts +57 -0
  25. package/dist/nodes.js +220 -0
  26. package/dist/nodes.js.map +1 -0
  27. package/dist/pagination.d.ts +41 -0
  28. package/dist/pagination.js +63 -0
  29. package/dist/pagination.js.map +1 -0
  30. package/dist/palace-format.d.ts +30 -0
  31. package/dist/palace-format.js +146 -0
  32. package/dist/palace-format.js.map +1 -0
  33. package/dist/rg.d.ts +34 -0
  34. package/dist/rg.js +288 -0
  35. package/dist/rg.js.map +1 -0
  36. package/dist/sources.d.ts +87 -0
  37. package/dist/sources.js +457 -0
  38. package/dist/sources.js.map +1 -0
  39. package/dist/tokens.d.ts +35 -0
  40. package/dist/tokens.js +95 -0
  41. package/dist/tokens.js.map +1 -0
  42. package/dist/types.d.ts +236 -0
  43. package/dist/types.js +8 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +67 -0
  46. package/skills/mpg-context/SKILL.md +556 -0
  47. package/skills/mpg-context/references/anti-patterns.md +133 -0
  48. package/skills/mpg-context/references/integration.md +123 -0
  49. package/skills/mpg-context/references/mind-palace.md +217 -0
  50. package/skills/mpg-context/references/multi-agent.md +147 -0
  51. package/skills/mpg-context/references/sources.md +120 -0
package/dist/cli.js ADDED
@@ -0,0 +1,856 @@
1
+ /**
2
+ * CLI argument parser and configuration resolution.
3
+ *
4
+ * We hand-roll the parser to avoid a dependency. The grammar is:
5
+ *
6
+ * mpg [pattern] [options]
7
+ *
8
+ * Pattern is the first positional argument. Options are flags below.
9
+ * The parser applies effort presets, then CLI overrides, then
10
+ * validates the final config.
11
+ */
12
+ export const EFFORT_PRESETS = {
13
+ scan: { before: 20, after: 20, maxNodes: 100000 },
14
+ quick: { before: 200, after: 200, maxNodes: 10 },
15
+ normal: { before: 500, after: 500, maxNodes: 30 },
16
+ deep: { before: 2000, after: 2000, maxNodes: 100 },
17
+ auto: { before: 500, after: 500, maxNodes: 30 },
18
+ };
19
+ export const HELP = `mpg — node-centric context retrieval for LLM harnesses
20
+
21
+ USAGE
22
+ mpg <pattern> [options]
23
+
24
+ Pattern is a regular expression (ripgrep syntax). Use -F/--fixed-strings
25
+ for literal matching.
26
+
27
+ SOURCES (at least one is required unless reading stdin)
28
+ -i, --in <path> [<path>...] Path(s), file(s), or glob(s) to search.
29
+ Repeatable. Greedy: consumes non-flag args.
30
+ Accepts: a file, a directory (recurses),
31
+ a glob, @file (read paths from a file),
32
+ @- (read paths from stdin), or a
33
+ comma-separated list.
34
+ Paths can also be passed as trailing
35
+ positionals: mpg "TODO" src/ test/
36
+ --cmd <command> Search the stdout of a shell command
37
+ --stdin Read content from stdin (auto-detected when piped)
38
+ -u, --url <url> Search the body of an HTTP(S) URL
39
+
40
+ NODE SIZING
41
+ -b, --before <tokens> Tokens of context before each match [default: 500]
42
+ -a, --after <tokens> Tokens of context after each match [default: 500]
43
+ -n, --max-nodes <n> Maximum number of nodes to return [default: 30]
44
+ --max-tokens <n> Total token budget across all nodes
45
+ --strategy <mode> How to use --max-tokens: fill|deep [default: fill]
46
+ -e, --effort <level> Preset: scan|quick|normal|deep|auto [default: quick]
47
+ scan=20t/uncapped (index mode: every hit gets a
48
+ tiny disambiguating window; matches rg recall
49
+ AND precision regardless of hit count; combine
50
+ with --sort recent for a time-ordered index)
51
+ quick=200t/10n, normal=500t/30n, deep=2000t/100n
52
+ --sort <mode> default|recent|oldest [default: default]
53
+ Order returned nodes by source file mtime.
54
+ Pairs naturally with scan: "scan + --sort recent"
55
+ gives you the most-recently-changed hits first
56
+ (a time-ordered memory index). Paginate to dig
57
+ back into history.
58
+ --window-curve <fn> flat|linear|log [default: flat]
59
+ Per-node token-window decay across the result list.
60
+ flat: every node gets the full window (classic).
61
+ linear: full at rank 0, decays to ~10% at last rank.
62
+ log: window = full / log2(rank+2). Gentler decay.
63
+ Combine with --sort recent for "rich context on
64
+ what just changed, tight window on older history."
65
+ --clip <N> Sub-line clip mode. Drops line context; trims the
66
+ matched line itself to N chars around the matched
67
+ span (with ellipsis markers). Cheapest possible
68
+ node — for disambiguation, not synthesis.
69
+ Combine with --effort scan for a minimal index
70
+ (~30 tokens per hit at N=20).
71
+ --fuzzy Typo-tolerant search. Transforms a literal pattern
72
+ so up to 2 extra chars may appear between each
73
+ letter. Skipped if the pattern already looks like
74
+ a regex. Pure plain-text patterns only.
75
+ Default is quick: small windows, small node cap.
76
+ "Scan first, dig deeper" is the recommended pattern
77
+ for agents — start with scan or quick; bump to
78
+ normal or deep only when the small result was
79
+ ambiguous. Use multiple targeted parallel calls
80
+ instead of one huge deep call.
81
+
82
+ OUTPUT
83
+ -f, --format <fmt> llm|markdown|json|text [default: llm]
84
+ --json Alias for --format json (matches rg/gh/jq).
85
+ --color / --no-color Force or disable ANSI color [default: auto]
86
+
87
+ SEARCH OPTIONS (forwarded to ripgrep)
88
+ -I, --ignore-case Case-insensitive match
89
+ -w, --word Match whole words only
90
+ -F, --fixed-strings Treat pattern as literal
91
+ -U, --multiline Allow multi-line patterns
92
+ --hidden Search hidden files and directories
93
+ --no-ignore Don't respect .gitignore
94
+ --include <glob> Include files matching glob (repeatable)
95
+ --exclude <glob> Exclude files matching glob (repeatable)
96
+ --type <lang> ripgrep file type filter (e.g. ts, rust, py)
97
+
98
+ OTHER
99
+ -h, --help Show this help
100
+ -v, --version Show version
101
+
102
+ PAGINATION (for finer-grained traversal of large result sets)
103
+ --page <n> Show only the Nth page of results (1-indexed).
104
+ When set, paginates nodes (in search and
105
+ --mp-get) or stashes (in --mp-list).
106
+ --page-size <n> Items per page (default 10 for nodes,
107
+ 20 for stashes).
108
+ --all Disable pagination; return everything.
109
+
110
+ MIND PALACE (the LLM's instantiable short-term memory)
111
+ A mind palace is a JSON file (default ./.mpg/mind-palace.json) that
112
+ holds named "stashes" of search results. The LLM harness can stash
113
+ results, recall them, and compose them across multiple invocations.
114
+
115
+ --mp-stash <name> <note> Stash the current search's results
116
+ under <name> with <note>. Adds to an
117
+ existing stash by default (dedup by
118
+ file:line); pass --mp-replace to
119
+ overwrite.
120
+ --mp-stash-tag <tag> Tag the stash (repeatable).
121
+ --mp-replace Replace an existing stash outright.
122
+ --mp-list [--mp-list-tag t] List all stashes (optionally filtered
123
+ by tag).
124
+ --mp-get <name> Print a stash. Default: **card view**
125
+ (note, tags, relations, sources,
126
+ counts — no per-node context). Add
127
+ --with-nodes or --full to include
128
+ the captured node block (the legacy
129
+ behavior). Card view is what an agent
130
+ almost always wants; the nodes block
131
+ is 5–6× more expensive in tokens.
132
+ --with-nodes, --full Opt-in to the full node dump on
133
+ --mp-get. Synonyms.
134
+ --mp-drop <name> Remove a stash from the palace.
135
+ --mp-from <name> Use a stashed file list as the search
136
+ target. The search re-runs fresh.
137
+ --mp-compose <a> <b> ... Union of multiple stashes' file lists
138
+ as the search target.
139
+ --mp-path <file> Path to the mind-palace.json file
140
+ (default: ./.mpg/mind-palace.json,
141
+ or the closest one walking up from
142
+ CWD). Use this for isolated sessions.
143
+
144
+ PRUNING & TTL (keep the palace from growing unbounded)
145
+ --mp-ttl <duration> Auto-expiry for this stash (e.g. 2h,
146
+ 7d, 30m). The stash is marked with
147
+ expires_at; expired stashes can be
148
+ cleaned with --mp-prune-expired.
149
+ --mp-prune-older-than <d> Remove stashes older than duration.
150
+ --mp-prune-keep <n> Keep only the n most recent stashes.
151
+ --mp-prune-tag <tag> Remove all stashes with a given tag.
152
+ --mp-prune-expired Remove all expired stashes (those
153
+ whose --mp-ttl has elapsed).
154
+ --mp-prune-all Remove all stashes.
155
+ --mp-prune-dry-run Show what would be removed.
156
+ --mp-prune-confirm Required for --mp-prune-all.
157
+
158
+ RELATIONSHIPS (make the "graph" in mind-palace-graph real)
159
+ --mp-link <from> <to> <type> [note]
160
+ Create a directed edge between stashes.
161
+ Types: depends-on, related-to, see-also,
162
+ parent-of, child-of, supersedes, or
163
+ any custom string.
164
+ --mp-unlink <from> <to> Remove a relationship.
165
+ --mp-related <name> Show all stashes connected to <name>
166
+ (both inbound and outbound edges).
167
+ --mp-graph <name> [depth] Traversal graph from <name> up to
168
+ [depth] levels (default 3).
169
+
170
+ EXAMPLES
171
+ # Find TODOs in src/, with 500 tokens of context, up to 20 nodes
172
+ mpg "TODO" --in src/ --max-nodes 20
173
+
174
+ # Paginate: 5 nodes per page, start at page 1
175
+ mpg "TODO" --in src/ --max-nodes 100 --page 1 --page-size 5
176
+
177
+ # Browse a large stash 5 nodes at a time
178
+ mpg --mp-get auth-issues --page 2 --page-size 5
179
+
180
+ # Browse a long list of stashes
181
+ mpg --mp-list --page 1 --page-size 20
182
+
183
+ # Stash this search's results into the mind palace
184
+ mpg "TODO" --in src/ --mp-stash auth-todos "Auth TODOs to review"
185
+
186
+ # Use a stashed file list as the search target
187
+ mpg "rate" --mp-from auth-todos
188
+
189
+ # Search across multiple stashes' file lists
190
+ mpg "error" --mp-compose auth-todos perf-hotspots
191
+
192
+ # List all stashes
193
+ mpg --mp-list
194
+
195
+ # Inspect a stash
196
+ mpg --mp-get auth-todos
197
+
198
+ # Free a slot
199
+ mpg --mp-drop auth-todos
200
+
201
+ # Multiple paths in one flag (greedy)
202
+ mpg "TODO" --in src/ test/ docs/
203
+
204
+ # Trailing positional paths (rg-style)
205
+ mpg "TODO" src/ test/
206
+
207
+ # Read path list from a file
208
+ mpg "TODO" --in @filelist.txt
209
+
210
+ # Read path list from stdin
211
+ echo -e "src/\ntest/" | mpg "TODO" --in @-
212
+
213
+ # Comma-separated
214
+ mpg "TODO" --in src/,test/,docs/
215
+
216
+ # Quick recon: narrow context, 5 nodes
217
+ mpg "auth" --in . --effort quick --max-nodes 5
218
+
219
+ # Deep dive: wide context, capped at 16k tokens
220
+ mpg "session" --in src/auth/ --effort deep --max-tokens 16000
221
+
222
+ # Search the output of a command
223
+ mpg "error" --cmd "git log --oneline -100"
224
+
225
+ # Pipe content in
226
+ cat README.md | mpg "install"
227
+
228
+ # JSON for programmatic harness integration
229
+ mpg "TODO" --in src/ --format json
230
+
231
+ # Markdown for pasting into a doc or chat
232
+ mpg "TODO" --in src/ --format markdown
233
+ `;
234
+ export function parseArgs(argv) {
235
+ const args = {
236
+ inPaths: [],
237
+ stdin: false,
238
+ hidden: false,
239
+ noIgnore: false,
240
+ ignoreCase: false,
241
+ word: false,
242
+ fixedStrings: false,
243
+ multiline: false,
244
+ includeGlobs: [],
245
+ excludeGlobs: [],
246
+ mpStashTags: [],
247
+ mpStashReplace: false,
248
+ mpList: false,
249
+ mpListTags: [],
250
+ mpCompose: [],
251
+ mpExceptNames: [],
252
+ mpIntersect: [],
253
+ mpPruneAll: false,
254
+ mpPruneExpired: false,
255
+ mpPruneConfirm: false,
256
+ mpPruneDryRun: false,
257
+ mpGraphDepth: 3,
258
+ all: false,
259
+ ls: false,
260
+ mpStashLocations: false,
261
+ mpGetWithNodes: false,
262
+ noAutoTune: false,
263
+ fuzzy: false,
264
+ help: false,
265
+ version: false,
266
+ };
267
+ let i = 0;
268
+ while (i < argv.length) {
269
+ const a = argv[i];
270
+ // Long options with --no- prefix (boolean negation).
271
+ if (a === "--no-color") {
272
+ args.color = false;
273
+ i++;
274
+ continue;
275
+ }
276
+ if (a === "--color") {
277
+ args.color = true;
278
+ i++;
279
+ continue;
280
+ }
281
+ if (a === "-h" || a === "--help") {
282
+ args.help = true;
283
+ i++;
284
+ continue;
285
+ }
286
+ if (a === "-v" || a === "--version") {
287
+ args.version = true;
288
+ i++;
289
+ continue;
290
+ }
291
+ if (a === "-i" || a === "--in") {
292
+ // Greedy: consume every subsequent non-flag arg as a path.
293
+ // This lets users write `--in src/ test/ docs/` and get three paths.
294
+ // To pass a path starting with `-`, prefix it with `./` or use
295
+ // `--in=./-weird-name`. We also support comma-separated and the
296
+ // special `@file` / `@-` syntax (see resolveInputList).
297
+ i++;
298
+ while (i < argv.length && !argv[i].startsWith("-")) {
299
+ for (const p of argv[i].split(",").filter(Boolean)) {
300
+ args.inPaths.push(p);
301
+ }
302
+ i++;
303
+ }
304
+ continue;
305
+ }
306
+ if (a === "--cmd") {
307
+ args.cmd = requireValue(a, argv, ++i);
308
+ i++;
309
+ continue;
310
+ }
311
+ if (a === "--stdin") {
312
+ args.stdin = true;
313
+ i++;
314
+ continue;
315
+ }
316
+ if (a === "-u" || a === "--url") {
317
+ args.url = requireValue(a, argv, ++i);
318
+ i++;
319
+ continue;
320
+ }
321
+ if (a === "-b" || a === "--before") {
322
+ args.before = parseInt(requireValue(a, argv, ++i), 10);
323
+ i++;
324
+ continue;
325
+ }
326
+ if (a === "-a" || a === "--after") {
327
+ args.after = parseInt(requireValue(a, argv, ++i), 10);
328
+ i++;
329
+ continue;
330
+ }
331
+ if (a === "-n" || a === "--max-nodes") {
332
+ args.maxNodes = parseInt(requireValue(a, argv, ++i), 10);
333
+ i++;
334
+ continue;
335
+ }
336
+ if (a === "--max-tokens") {
337
+ args.maxTokens = parseInt(requireValue(a, argv, ++i), 10);
338
+ i++;
339
+ continue;
340
+ }
341
+ if (a === "--strategy") {
342
+ const v = requireValue(a, argv, ++i);
343
+ if (v !== "fill" && v !== "deep")
344
+ throw new Error(`--strategy must be fill or deep, got: ${v}`);
345
+ args.strategy = v;
346
+ i++;
347
+ continue;
348
+ }
349
+ if (a === "-e" || a === "--effort") {
350
+ const v = requireValue(a, argv, ++i);
351
+ if (!["scan", "quick", "normal", "deep", "auto"].includes(v)) {
352
+ throw new Error(`--effort must be scan|quick|normal|deep|auto, got: ${v}`);
353
+ }
354
+ args.effort = v;
355
+ i++;
356
+ continue;
357
+ }
358
+ if (a === "-f" || a === "--format") {
359
+ const v = requireValue(a, argv, ++i);
360
+ if (!["llm", "markdown", "json", "text"].includes(v)) {
361
+ throw new Error(`--format must be llm|markdown|json|text, got: ${v}`);
362
+ }
363
+ args.format = v;
364
+ i++;
365
+ continue;
366
+ }
367
+ // Common ecosystem alias — rg/gh/jq all use --json as a shorthand.
368
+ if (a === "--json") {
369
+ args.format = "json";
370
+ i++;
371
+ continue;
372
+ }
373
+ if (a === "-I" || a === "--ignore-case") {
374
+ args.ignoreCase = true;
375
+ i++;
376
+ continue;
377
+ }
378
+ if (a === "-w" || a === "--word") {
379
+ args.word = true;
380
+ i++;
381
+ continue;
382
+ }
383
+ if (a === "-F" || a === "--fixed-strings") {
384
+ args.fixedStrings = true;
385
+ i++;
386
+ continue;
387
+ }
388
+ if (a === "-U" || a === "--multiline") {
389
+ args.multiline = true;
390
+ i++;
391
+ continue;
392
+ }
393
+ if (a === "--hidden") {
394
+ args.hidden = true;
395
+ i++;
396
+ continue;
397
+ }
398
+ if (a === "--no-ignore") {
399
+ args.noIgnore = true;
400
+ i++;
401
+ continue;
402
+ }
403
+ if (a === "--include") {
404
+ args.includeGlobs.push(requireValue(a, argv, ++i));
405
+ i++;
406
+ continue;
407
+ }
408
+ if (a === "--exclude") {
409
+ args.excludeGlobs.push(requireValue(a, argv, ++i));
410
+ i++;
411
+ continue;
412
+ }
413
+ if (a === "--type") {
414
+ args.type = requireValue(a, argv, ++i);
415
+ i++;
416
+ continue;
417
+ }
418
+ // Mind palace flags.
419
+ if (a === "--mp-stash") {
420
+ // Consume two args: name and note.
421
+ args.mpStashName = requireValue(a, argv, ++i);
422
+ i++;
423
+ args.mpStashNote = requireValue("--mp-stash <note>", argv, i);
424
+ i++;
425
+ continue;
426
+ }
427
+ if (a === "--mp-stash-note") {
428
+ args.mpStashNote = requireValue(a, argv, ++i);
429
+ i++;
430
+ continue;
431
+ }
432
+ if (a === "--mp-stash-tag" || a === "--mp-tag") {
433
+ args.mpStashTags.push(requireValue(a, argv, ++i));
434
+ i++;
435
+ continue;
436
+ }
437
+ if (a === "--mp-replace") {
438
+ args.mpStashReplace = true;
439
+ i++;
440
+ continue;
441
+ }
442
+ if (a === "--mp-list") {
443
+ args.mpList = true;
444
+ i++;
445
+ continue;
446
+ }
447
+ if (a === "--mp-list-tag") {
448
+ args.mpListTags.push(requireValue(a, argv, ++i));
449
+ i++;
450
+ continue;
451
+ }
452
+ if (a === "--mp-get") {
453
+ args.mpGet = requireValue(a, argv, ++i);
454
+ i++;
455
+ continue;
456
+ }
457
+ if (a === "--with-nodes" || a === "--full") {
458
+ args.mpGetWithNodes = true;
459
+ i++;
460
+ continue;
461
+ }
462
+ if (a === "--mp-drop") {
463
+ args.mpDrop = requireValue(a, argv, ++i);
464
+ i++;
465
+ continue;
466
+ }
467
+ if (a === "--mp-from") {
468
+ args.mpFrom = requireValue(a, argv, ++i);
469
+ i++;
470
+ continue;
471
+ }
472
+ if (a === "--mp-compose") {
473
+ // Greedy: take every non-flag arg as a stash name.
474
+ i++;
475
+ while (i < argv.length && !argv[i].startsWith("-")) {
476
+ for (const p of argv[i].split(",").filter(Boolean)) {
477
+ args.mpCompose.push(p);
478
+ }
479
+ i++;
480
+ }
481
+ continue;
482
+ }
483
+ if (a === "--mp-except") {
484
+ args.mpExcept = requireValue(a, argv, ++i);
485
+ i++;
486
+ // Greedy remainder = the stashes to exclude.
487
+ while (i < argv.length && !argv[i].startsWith("-")) {
488
+ for (const p of argv[i].split(",").filter(Boolean)) {
489
+ args.mpExceptNames.push(p);
490
+ }
491
+ i++;
492
+ }
493
+ continue;
494
+ }
495
+ if (a === "--mp-intersect") {
496
+ // Greedy: take every non-flag arg as a stash name.
497
+ i++;
498
+ while (i < argv.length && !argv[i].startsWith("-")) {
499
+ for (const p of argv[i].split(",").filter(Boolean)) {
500
+ args.mpIntersect.push(p);
501
+ }
502
+ i++;
503
+ }
504
+ continue;
505
+ }
506
+ if (a === "--mp-path") {
507
+ args.mpPath = requireValue(a, argv, ++i);
508
+ i++;
509
+ continue;
510
+ }
511
+ if (a === "--mp-ttl") {
512
+ args.mpTtl = requireValue(a, argv, ++i);
513
+ i++;
514
+ continue;
515
+ }
516
+ // Pruning.
517
+ if (a === "--mp-prune-older-than") {
518
+ args.mpPruneOlderThan = requireValue(a, argv, ++i);
519
+ i++;
520
+ continue;
521
+ }
522
+ if (a === "--mp-prune-keep") {
523
+ args.mpPruneKeep = parseInt(requireValue(a, argv, ++i), 10);
524
+ i++;
525
+ continue;
526
+ }
527
+ if (a === "--mp-prune-tag") {
528
+ args.mpPruneTag = requireValue(a, argv, ++i);
529
+ i++;
530
+ continue;
531
+ }
532
+ if (a === "--mp-prune-all") {
533
+ args.mpPruneAll = true;
534
+ i++;
535
+ continue;
536
+ }
537
+ if (a === "--mp-prune-expired") {
538
+ args.mpPruneExpired = true;
539
+ i++;
540
+ continue;
541
+ }
542
+ if (a === "--mp-prune-confirm") {
543
+ args.mpPruneConfirm = true;
544
+ i++;
545
+ continue;
546
+ }
547
+ if (a === "--mp-prune-dry-run") {
548
+ args.mpPruneDryRun = true;
549
+ i++;
550
+ continue;
551
+ }
552
+ // Relationships.
553
+ if (a === "--mp-link") {
554
+ args.mpLinkFrom = requireValue(a, argv, ++i);
555
+ i++;
556
+ args.mpLinkTo = requireValue("--mp-link <to>", argv, i);
557
+ i++;
558
+ args.mpLinkType = requireValue("--mp-link <type>", argv, i);
559
+ i++;
560
+ // Optional note.
561
+ if (i < argv.length && !argv[i].startsWith("-")) {
562
+ args.mpLinkNote = argv[i];
563
+ i++;
564
+ }
565
+ continue;
566
+ }
567
+ if (a === "--mp-unlink") {
568
+ args.mpUnlinkFrom = requireValue(a, argv, ++i);
569
+ i++;
570
+ args.mpUnlinkTo = requireValue("--mp-unlink <to>", argv, i);
571
+ i++;
572
+ continue;
573
+ }
574
+ if (a === "--mp-related") {
575
+ args.mpRelated = requireValue(a, argv, ++i);
576
+ i++;
577
+ continue;
578
+ }
579
+ if (a === "--mp-graph") {
580
+ args.mpGraph = requireValue(a, argv, ++i);
581
+ i++;
582
+ // Optional depth.
583
+ if (i < argv.length && !argv[i].startsWith("-")) {
584
+ args.mpGraphDepth = parseInt(argv[i], 10);
585
+ i++;
586
+ }
587
+ continue;
588
+ }
589
+ // Pagination.
590
+ if (a === "--page") {
591
+ args.page = parseInt(requireValue(a, argv, ++i), 10);
592
+ i++;
593
+ continue;
594
+ }
595
+ if (a === "--page-size") {
596
+ args.pageSize = parseInt(requireValue(a, argv, ++i), 10);
597
+ i++;
598
+ continue;
599
+ }
600
+ if (a === "--all") {
601
+ args.all = true;
602
+ i++;
603
+ continue;
604
+ }
605
+ if (a === "--no-auto-tune") {
606
+ args.noAutoTune = true;
607
+ i++;
608
+ continue;
609
+ }
610
+ if (a === "--sort") {
611
+ const v = requireValue(a, argv, ++i);
612
+ if (!["default", "recent", "oldest"].includes(v)) {
613
+ throw new Error(`--sort must be default|recent|oldest, got: ${v}`);
614
+ }
615
+ args.sort = v;
616
+ i++;
617
+ continue;
618
+ }
619
+ if (a === "--window-curve") {
620
+ const v = requireValue(a, argv, ++i);
621
+ if (!["flat", "linear", "log"].includes(v)) {
622
+ throw new Error(`--window-curve must be flat|linear|log, got: ${v}`);
623
+ }
624
+ args.windowCurve = v;
625
+ i++;
626
+ continue;
627
+ }
628
+ if (a === "--clip") {
629
+ const v = requireValue(a, argv, ++i);
630
+ const n = parseInt(v, 10);
631
+ if (!Number.isFinite(n) || n < 0) {
632
+ throw new Error(`--clip must be a non-negative integer, got: ${v}`);
633
+ }
634
+ args.clipChars = n;
635
+ i++;
636
+ continue;
637
+ }
638
+ if (a === "--fuzzy") {
639
+ args.fuzzy = true;
640
+ i++;
641
+ continue;
642
+ }
643
+ if (a === "--ls" || a === "--tree") {
644
+ args.ls = true;
645
+ i++;
646
+ continue;
647
+ }
648
+ if (a === "--mp-stash-locations") {
649
+ args.mpStashLocations = true;
650
+ i++;
651
+ continue;
652
+ }
653
+ // Positional: first non-flag is the pattern. Any subsequent
654
+ // non-flag args are treated as input paths (like `rg`).
655
+ if (!a.startsWith("-") && args.pattern === undefined) {
656
+ args.pattern = a;
657
+ i++;
658
+ continue;
659
+ }
660
+ if (!a.startsWith("-") && args.pattern !== undefined) {
661
+ // Trailing positionals after the pattern are paths.
662
+ for (const p of a.split(",").filter(Boolean)) {
663
+ args.inPaths.push(p);
664
+ }
665
+ i++;
666
+ continue;
667
+ }
668
+ throw new Error(`Unknown argument: ${a}`);
669
+ }
670
+ return args;
671
+ }
672
+ function requireValue(flag, argv, i) {
673
+ if (i >= argv.length)
674
+ throw new Error(`Missing value for ${flag}`);
675
+ return argv[i];
676
+ }
677
+ /** Resolve a RawArgs into a fully-specified config. */
678
+ export function resolveConfig(raw) {
679
+ if (raw.help)
680
+ throw new HelpRequestedError();
681
+ if (raw.version)
682
+ throw new VersionRequestedError();
683
+ // Pattern is optional: required for searches and for --mp-from /
684
+ // --mp-compose, but not for --mp-list, --mp-get, or --mp-drop.
685
+ const pattern = raw.pattern ?? process.env.MPG_PATTERN;
686
+ const needsPattern = !raw.ls && (raw.inPaths.length > 0 ||
687
+ raw.cmd !== undefined ||
688
+ raw.url !== undefined ||
689
+ raw.stdin ||
690
+ raw.mpFrom !== undefined ||
691
+ (raw.mpCompose && raw.mpCompose.length > 0) ||
692
+ raw.mpExcept !== undefined ||
693
+ (raw.mpIntersect && raw.mpIntersect.length > 0) ||
694
+ raw.mpStashName !== undefined);
695
+ if (needsPattern && !pattern) {
696
+ throw new Error("No pattern provided. Pass it as the first positional argument, e.g. `mpg \"TODO\"`.");
697
+ }
698
+ // Apply effort preset, then explicit overrides.
699
+ const effort = raw.effort ?? "quick";
700
+ const preset = EFFORT_PRESETS[effort];
701
+ const before = raw.before ?? preset.before;
702
+ const after = raw.after ?? preset.after;
703
+ const maxNodes = raw.maxNodes ?? preset.maxNodes;
704
+ const strategy = raw.strategy ?? "fill";
705
+ const format = raw.format ?? "llm";
706
+ const color = raw.color ?? (process.stdout.isTTY ?? false);
707
+ // Build the source input list. Resolution to actual Source objects
708
+ // happens in the orchestrator (index.ts) since some sources need
709
+ // async I/O (commands, urls, stdin).
710
+ const inputs = [];
711
+ if (raw.inPaths.length > 0) {
712
+ for (const p of raw.inPaths)
713
+ inputs.push({ type: "path", path: p });
714
+ }
715
+ if (raw.cmd)
716
+ inputs.push({ type: "command", command: raw.cmd });
717
+ if (raw.url)
718
+ inputs.push({ type: "url", url: raw.url });
719
+ if (raw.stdin || (!process.stdin.isTTY && inputs.length === 0 && !needsPattern)) {
720
+ inputs.push({ type: "stdin" });
721
+ }
722
+ // If --mp-from or --mp-compose is given, we still need a pattern but
723
+ // can skip the source check: the orchestrator will derive sources
724
+ // from the palace. The check below catches "no inputs AND no palace".
725
+ const hasPalaceInput = raw.mpFrom !== undefined ||
726
+ (raw.mpCompose && raw.mpCompose.length > 0) ||
727
+ raw.mpExcept !== undefined ||
728
+ (raw.mpIntersect && raw.mpIntersect.length > 0);
729
+ if (inputs.length === 0 && !hasPalaceInput && !raw.mpStashName && !raw.mpList && !raw.mpGet && !raw.mpDrop) {
730
+ throw new Error("No source provided. Use --in <path>, --cmd <command>, --url <url>, --mp-from, --mp-compose, or pipe via stdin.");
731
+ }
732
+ const rgOptions = {};
733
+ if (raw.ignoreCase)
734
+ rgOptions.case_insensitive = true;
735
+ if (raw.word)
736
+ rgOptions.word_match = true;
737
+ if (raw.fixedStrings)
738
+ rgOptions.fixed_strings = true;
739
+ if (raw.multiline)
740
+ rgOptions.multiline = true;
741
+ if (raw.hidden)
742
+ rgOptions.hidden = true;
743
+ if (raw.noIgnore)
744
+ rgOptions.no_ignore = true;
745
+ if (raw.includeGlobs.length > 0)
746
+ rgOptions.include_globs = raw.includeGlobs;
747
+ if (raw.excludeGlobs.length > 0)
748
+ rgOptions.exclude_globs = raw.excludeGlobs;
749
+ if (raw.type)
750
+ rgOptions.type = raw.type;
751
+ // Build mind palace ops object if any palace flag was given.
752
+ let mind_palace;
753
+ if (raw.mpStashName || raw.mpList || raw.mpGet || raw.mpDrop ||
754
+ raw.mpFrom || (raw.mpCompose && raw.mpCompose.length > 0) ||
755
+ raw.mpExcept || (raw.mpIntersect && raw.mpIntersect.length > 0) ||
756
+ raw.mpPruneOlderThan || raw.mpPruneKeep !== undefined || raw.mpPruneTag ||
757
+ raw.mpPruneAll || raw.mpPruneExpired || raw.mpTtl || raw.mpPath ||
758
+ raw.mpLinkFrom || raw.mpUnlinkFrom || raw.mpRelated || raw.mpGraph) {
759
+ mind_palace = {
760
+ path: raw.mpPath,
761
+ prune_all: raw.mpPruneAll,
762
+ prune_confirm: raw.mpPruneConfirm,
763
+ prune_dry_run: raw.mpPruneDryRun,
764
+ };
765
+ if (raw.mpStashName) {
766
+ mind_palace.stash = {
767
+ name: raw.mpStashName,
768
+ note: raw.mpStashNote ?? "",
769
+ tags: raw.mpStashTags,
770
+ replace: raw.mpStashReplace,
771
+ };
772
+ }
773
+ if (raw.mpList)
774
+ mind_palace.list = { tags: raw.mpListTags };
775
+ if (raw.mpGet)
776
+ mind_palace.get = { name: raw.mpGet, with_nodes: raw.mpGetWithNodes };
777
+ if (raw.mpDrop)
778
+ mind_palace.drop = raw.mpDrop;
779
+ if (raw.mpFrom)
780
+ mind_palace.from = raw.mpFrom;
781
+ if (raw.mpCompose && raw.mpCompose.length > 0)
782
+ mind_palace.compose = raw.mpCompose;
783
+ if (raw.mpExcept) {
784
+ mind_palace.except = { base: raw.mpExcept, exclude: raw.mpExceptNames };
785
+ }
786
+ if (raw.mpIntersect && raw.mpIntersect.length > 0) {
787
+ mind_palace.intersect = raw.mpIntersect;
788
+ }
789
+ if (raw.mpTtl)
790
+ mind_palace.ttl = raw.mpTtl;
791
+ // Relationships.
792
+ if (raw.mpLinkFrom && raw.mpLinkTo && raw.mpLinkType) {
793
+ mind_palace.link = {
794
+ from: raw.mpLinkFrom,
795
+ to: raw.mpLinkTo,
796
+ type: raw.mpLinkType,
797
+ note: raw.mpLinkNote ?? "",
798
+ };
799
+ }
800
+ if (raw.mpUnlinkFrom && raw.mpUnlinkTo) {
801
+ mind_palace.unlink = { from: raw.mpUnlinkFrom, to: raw.mpUnlinkTo };
802
+ }
803
+ if (raw.mpRelated)
804
+ mind_palace.related = raw.mpRelated;
805
+ if (raw.mpGraph)
806
+ mind_palace.graph = { name: raw.mpGraph, depth: raw.mpGraphDepth ?? 3 };
807
+ // Pruning.
808
+ if (raw.mpPruneOlderThan)
809
+ mind_palace.prune_older_than = raw.mpPruneOlderThan;
810
+ if (raw.mpPruneKeep !== undefined)
811
+ mind_palace.prune_keep = raw.mpPruneKeep;
812
+ if (raw.mpPruneTag)
813
+ mind_palace.prune_tag = raw.mpPruneTag;
814
+ if (raw.mpPruneAll)
815
+ mind_palace.prune_all = true;
816
+ if (raw.mpPruneExpired)
817
+ mind_palace.prune_expired = true;
818
+ mind_palace.prune_confirm = raw.mpPruneConfirm;
819
+ mind_palace.prune_dry_run = raw.mpPruneDryRun;
820
+ }
821
+ return {
822
+ ...(pattern !== undefined ? { pattern } : {}),
823
+ before_tokens: before,
824
+ after_tokens: after,
825
+ max_nodes: maxNodes,
826
+ max_tokens: raw.maxTokens,
827
+ strategy,
828
+ effort,
829
+ format,
830
+ color,
831
+ inputs,
832
+ rg_options: rgOptions,
833
+ mind_palace,
834
+ page: raw.page,
835
+ page_size: raw.pageSize,
836
+ all: raw.all,
837
+ ls: raw.ls,
838
+ mp_stash_locations: raw.mpStashLocations,
839
+ no_auto_tune: raw.noAutoTune,
840
+ auto_tune_eligible: !raw.noAutoTune && raw.before === undefined && raw.after === undefined,
841
+ sort: raw.sort,
842
+ window_curve: raw.windowCurve,
843
+ clip_chars: raw.clipChars,
844
+ fuzzy: raw.fuzzy,
845
+ };
846
+ }
847
+ // (Intentionally no extra validation hook here — --page/--all
848
+ // compatibility is checked at apply time where the combination is
849
+ // actually meaningful.)
850
+ export class HelpRequestedError extends Error {
851
+ constructor() { super("help requested"); this.name = "HelpRequestedError"; }
852
+ }
853
+ export class VersionRequestedError extends Error {
854
+ constructor() { super("version requested"); this.name = "VersionRequestedError"; }
855
+ }
856
+ //# sourceMappingURL=cli.js.map