@spikedpunch/mast 0.3.0 → 0.4.1

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 (81) hide show
  1. package/README.md +183 -9
  2. package/assets/prime.md +18 -0
  3. package/assets/signals.md +51 -0
  4. package/assets/skill.md +6 -47
  5. package/dist/cli/docs-cmd.d.ts +1 -0
  6. package/dist/cli/docs-cmd.d.ts.map +1 -1
  7. package/dist/cli/docs-cmd.js +2 -1
  8. package/dist/cli/docs-cmd.js.map +1 -1
  9. package/dist/cli/hook-cmd.d.ts +3 -0
  10. package/dist/cli/hook-cmd.d.ts.map +1 -0
  11. package/dist/cli/hook-cmd.js +13 -0
  12. package/dist/cli/hook-cmd.js.map +1 -0
  13. package/dist/cli/hook-state-dir.d.ts +24 -0
  14. package/dist/cli/hook-state-dir.d.ts.map +1 -0
  15. package/dist/cli/hook-state-dir.js +57 -0
  16. package/dist/cli/hook-state-dir.js.map +1 -0
  17. package/dist/cli/hook.d.ts +44 -0
  18. package/dist/cli/hook.d.ts.map +1 -0
  19. package/dist/cli/hook.js +200 -0
  20. package/dist/cli/hook.js.map +1 -0
  21. package/dist/cli/index.js +12 -2
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/installed-artifacts.d.ts +23 -0
  24. package/dist/cli/installed-artifacts.d.ts.map +1 -0
  25. package/dist/cli/installed-artifacts.js +39 -0
  26. package/dist/cli/installed-artifacts.js.map +1 -0
  27. package/dist/cli/prime-cmd.d.ts +17 -0
  28. package/dist/cli/prime-cmd.d.ts.map +1 -0
  29. package/dist/cli/prime-cmd.js +70 -0
  30. package/dist/cli/prime-cmd.js.map +1 -0
  31. package/dist/cli/program.d.ts.map +1 -1
  32. package/dist/cli/program.js +6 -0
  33. package/dist/cli/program.js.map +1 -1
  34. package/dist/cli/setup-cmd.d.ts +34 -0
  35. package/dist/cli/setup-cmd.d.ts.map +1 -0
  36. package/dist/cli/setup-cmd.js +214 -0
  37. package/dist/cli/setup-cmd.js.map +1 -0
  38. package/dist/cli/setup-command.d.ts +47 -0
  39. package/dist/cli/setup-command.d.ts.map +1 -0
  40. package/dist/cli/setup-command.js +76 -0
  41. package/dist/cli/setup-command.js.map +1 -0
  42. package/dist/cli/setup-plan.d.ts +52 -0
  43. package/dist/cli/setup-plan.d.ts.map +1 -0
  44. package/dist/cli/setup-plan.js +221 -0
  45. package/dist/cli/setup-plan.js.map +1 -0
  46. package/dist/cli/setup-rules.d.ts +41 -0
  47. package/dist/cli/setup-rules.d.ts.map +1 -0
  48. package/dist/cli/setup-rules.js +76 -0
  49. package/dist/cli/setup-rules.js.map +1 -0
  50. package/dist/cli/setup-static.d.ts +13 -0
  51. package/dist/cli/setup-static.d.ts.map +1 -0
  52. package/dist/cli/setup-static.js +116 -0
  53. package/dist/cli/setup-static.js.map +1 -0
  54. package/dist/cli/skill-install.d.ts +2 -0
  55. package/dist/cli/skill-install.d.ts.map +1 -1
  56. package/dist/cli/skill-install.js +2 -0
  57. package/dist/cli/skill-install.js.map +1 -1
  58. package/dist/cli/upgrade-cmd.d.ts +5 -1
  59. package/dist/cli/upgrade-cmd.d.ts.map +1 -1
  60. package/dist/cli/upgrade-cmd.js +28 -3
  61. package/dist/cli/upgrade-cmd.js.map +1 -1
  62. package/dist/indexer/watcher.d.ts +80 -2
  63. package/dist/indexer/watcher.d.ts.map +1 -1
  64. package/dist/indexer/watcher.js +172 -27
  65. package/dist/indexer/watcher.js.map +1 -1
  66. package/dist/mcp/instructions.d.ts +7 -0
  67. package/dist/mcp/instructions.d.ts.map +1 -0
  68. package/dist/mcp/instructions.js +10 -0
  69. package/dist/mcp/instructions.js.map +1 -0
  70. package/dist/mcp/server.d.ts +8 -0
  71. package/dist/mcp/server.d.ts.map +1 -1
  72. package/dist/mcp/server.js +17 -4
  73. package/dist/mcp/server.js.map +1 -1
  74. package/dist/store/config.d.ts.map +1 -1
  75. package/dist/store/config.js +4 -5
  76. package/dist/store/config.js.map +1 -1
  77. package/dist/store/defaults.d.ts +9 -0
  78. package/dist/store/defaults.d.ts.map +1 -0
  79. package/dist/store/defaults.js +12 -0
  80. package/dist/store/defaults.js.map +1 -0
  81. package/package.json +2 -1
package/README.md CHANGED
@@ -142,12 +142,21 @@ mast index --incremental # reindex only what changed
142
142
  mast install-hooks # reindex automatically after commits and checkouts
143
143
  ```
144
144
 
145
+ Then tell your agent to use it. One command installs what your harness can take — hooks
146
+ that prime the agent at session start and remind it before a built-in search, or a rules
147
+ file where there are no hooks ([details](#tell-the-assistant-how-to-use-it)):
148
+
149
+ ```bash
150
+ mast setup claude # or cursor, vscode, windsurf, zed, desktop
151
+ ```
152
+
145
153
  Everything shipped with your build is readable offline, so you never have to work out
146
154
  which docs match your version:
147
155
 
148
156
  ```bash
149
157
  mast docs # list the topics
150
158
  mast docs spec # the full behavioural specification
159
+ mast docs signals # how to read the flags on an answer (stale, truncated, empty)
151
160
  mast skill # the instructions to paste into an agent prompt
152
161
  ```
153
162
 
@@ -170,6 +179,8 @@ claude mcp add mast -- mast serve
170
179
  Add `--scope project` to write `.mcp.json` into the repository so your team picks it up
171
180
  from the checkout.
172
181
 
182
+ To prime the agent at session start and remind it before a search: `mast setup claude`.
183
+
173
184
  ### Claude Desktop
174
185
 
175
186
  `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
@@ -190,6 +201,10 @@ from the checkout.
190
201
  Claude Desktop does not run in your project directory, so `MAST_STATE_DIR` must be
191
202
  absolute. The CLI and editor integrations below infer it from the working directory.
192
203
 
204
+ Claude Desktop has no hook system and no rules file mast can write. The instructions string
205
+ `mast serve` sends in the MCP handshake is its only channel, and it needs no setup;
206
+ `mast setup desktop` just says so.
207
+
193
208
  ### Cursor
194
209
 
195
210
  `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` globally:
@@ -202,6 +217,8 @@ absolute. The CLI and editor integrations below infer it from the working direct
202
217
  }
203
218
  ```
204
219
 
220
+ To add the hooks and a rules file: `mast setup cursor`.
221
+
205
222
  ### VS Code (GitHub Copilot)
206
223
 
207
224
  `.vscode/mcp.json`:
@@ -214,6 +231,8 @@ absolute. The CLI and editor integrations below infer it from the working direct
214
231
  }
215
232
  ```
216
233
 
234
+ To add the session-primer hook: `mast setup vscode`.
235
+
217
236
  ### Windsurf
218
237
 
219
238
  `~/.codeium/windsurf/mcp_config.json`:
@@ -226,6 +245,8 @@ absolute. The CLI and editor integrations below infer it from the working direct
226
245
  }
227
246
  ```
228
247
 
248
+ To add a rules file: `mast setup windsurf`.
249
+
229
250
  ### Zed
230
251
 
231
252
  `settings.json`:
@@ -238,6 +259,9 @@ absolute. The CLI and editor integrations below infer it from the working direct
238
259
  }
239
260
  ```
240
261
 
262
+ To add mast's instructions to a rules file you already keep (`.rules`, `AGENTS.md`, and the
263
+ others Zed reads): `mast setup zed`.
264
+
241
265
  ### Any other MCP client
242
266
 
243
267
  Run `mast serve` over stdio from the project root. It advertises eleven tools — ten that
@@ -246,8 +270,32 @@ read and `mast_reindex`, which writes — and needs no arguments beyond `serve`.
246
270
  ### Tell the assistant how to use it
247
271
 
248
272
  Registering the server gives the model the tools; it does not tell it *when* to reach for
249
- them, or how to read a flagged answer. `mast skill` prints instructions written for that —
250
- paste them into your system prompt, `CLAUDE.md`, `.cursorrules`, or a skill file:
273
+ them. `mast setup <harness>` installs what each harness can take:
274
+
275
+ | harness | session primer | search reminder | static instructions |
276
+ |---|---|---|---|
277
+ | `claude` (Claude Code) | yes | yes, before `Grep` and `Glob` | none written |
278
+ | `cursor` | yes | yes, but after the search runs | `.cursor/rules/mast.mdc` |
279
+ | `vscode` (VS Code Copilot) | yes | no | none written |
280
+ | `windsurf` | no | no | `.windsurf/rules/mast.md`, or `.devin/rules/mast.md` if `.devin/` exists |
281
+ | `zed` | no | no | a marked block in the first rules file that exists |
282
+ | `desktop` (Claude Desktop) | no | no | none written |
283
+
284
+ The session primer is the rule set plus the live index state, given at session start. The
285
+ search reminder is one line naming `mast_search`, attached to a built-in search. Windsurf,
286
+ Zed and Claude Desktop have no hook system mast can use, so for them `mast setup` installs
287
+ the static file only, and says so. `mast serve` also sends a short instructions string when
288
+ the MCP connection opens; whether a harness passes that to the model is the harness's choice.
289
+
290
+ ```bash
291
+ mast setup claude # project scope; add --global for ~/.claude/settings.json
292
+ mast setup cursor --check # exit 0 only if the hooks and the rules file are current
293
+ mast setup windsurf --dry-run
294
+ mast setup zed --remove
295
+ ```
296
+
297
+ Anything else, or a harness not listed, can use `mast skill`, which prints the same short
298
+ instructions to paste into a system prompt, `CLAUDE.md`, or a skill file:
251
299
 
252
300
  ```bash
253
301
  mast skill # print it
@@ -261,6 +309,12 @@ marked block, so re-running after an upgrade replaces the previous copy instead
261
309
  a second one. It never runs on its own, and it never creates a config file you did not
262
310
  already keep.
263
311
 
312
+ **What is verified.** In Claude Code, both hooks were observed delivering their text to the
313
+ model in one session. For Cursor, VS Code and Windsurf, the hook formats and rules-file
314
+ locations were read from the vendor's documentation and nothing was run inside the tool.
315
+ The list of files Zed reads was not checked against Zed's documentation. Whether any of
316
+ this changes how often an agent uses mast has not been measured.
317
+
264
318
  ---
265
319
 
266
320
  ## Upgrading
@@ -279,6 +333,13 @@ it on the next `serve` or `index`. Nothing is lost that cannot be rebuilt — th
279
333
  derived state — but on a large monorepo it is minutes, and it is better known in advance
280
334
  than discovered as an unexplained stall.
281
335
 
336
+ If mast's hooks, rules files or skill blocks are installed in the project (or hook files
337
+ under your home directory), the report ends with an "After upgrading" block listing the
338
+ commands to re-run, such as `mast setup claude --check` and `mast skill --install`. It runs
339
+ on the version you have now, so it cannot know what the new version would write: it only
340
+ knows the files exist, and `--check` is what compares them. A project with none of these
341
+ gets no such block.
342
+
282
343
  ---
283
344
 
284
345
  ## Using MAST in a monorepo
@@ -488,8 +549,9 @@ tool that does not exist lists the ones that do.
488
549
 
489
550
  ### `mast docs [topic]`
490
551
 
491
- Print documentation shipped with the installed build — `readme`, `spec`, or `skill`. No
492
- argument lists the topics with the version they belong to.
552
+ Print documentation shipped with the installed build — `readme`, `spec`, `skill`, or
553
+ `signals` (the staleness, truncation and emptiness flags on an answer). No argument lists
554
+ the topics with the version they belong to.
493
555
 
494
556
  **Why:** removes the step where a reader looks up their version and then finds docs for a
495
557
  different one. What `mast docs` prints is what the binary in your `node_modules` does.
@@ -508,10 +570,120 @@ Options:
508
570
  ```
509
571
 
510
572
  **Why:** registering the MCP server gives a model the tools but not the judgement — when to
511
- search instead of reading, that code tokens beat prose in a query, and how to read a
512
- staleness or truncation flag. It also tells the model that an empty result means "MAST did
513
- not find it", not "it does not exist", which is the single most consequential thing to get
514
- right about a search tool.
573
+ search instead of reading, and that code tokens beat prose in a query. The text is short
574
+ (under 4,000 characters) so it fits a rules file. It also tells the model that an empty
575
+ result means "MAST did not find it", not "it does not exist", which is the single most
576
+ consequential thing to get right about a search tool, and points at `mast docs signals` for
577
+ how to read the individual flags.
578
+
579
+ ---
580
+
581
+ ### `mast prime [path]`
582
+
583
+ Print the session primer: a short rule set for using mast, plus the live index health.
584
+
585
+ ```
586
+ Options:
587
+ --state-dir <dir> State directory
588
+ ```
589
+
590
+ **Why:** it is the text an agent should be given at the start of a session. When the index
591
+ is fresh or stale it prints the rules and the file count and age of the index (a stale
592
+ index adds the changed/unindexed/deleted split and a `mast_reindex` instruction). When
593
+ there is no index, or the index describes a different tree, it withholds the rules and says
594
+ what is wrong. It exits 0 in every state, unlike `mast status`.
595
+
596
+ ---
597
+
598
+ ### `mast hook <harness> <event>`
599
+
600
+ The entry point for agent hooks. Reads the harness's hook JSON on stdin and writes that
601
+ harness's JSON envelope on stdout, or writes nothing. `<harness>` is `claude`, `cursor` or
602
+ `vscode`; `<event>` is `session-start` or `search`.
603
+
604
+ - `session-start` emits the same text `mast prime` prints for the project (the hook's `cwd`,
605
+ else the first workspace root, else the current directory), in every index state.
606
+ - `search` emits a one-line reminder to try `mast_search` first. It stays silent when no
607
+ index exists for the project, or when the search is scoped by `type`, `glob` or `path` to
608
+ a language mast does not index. "Indexes" follows the project's own `file_extensions`
609
+ where it sets one, so a project that indexes `.mjs` is reminded on a `*.mjs` search.
610
+
611
+ ```
612
+ Arguments:
613
+ harness claude | cursor | vscode
614
+ event session-start | search
615
+ ```
616
+
617
+ **Why:** a hook that fails breaks the user's session, so this command exits 0 for every
618
+ ordinary condition (empty or malformed stdin, an unknown harness or event, a missing index)
619
+ and leaves stdout empty, with one line on stderr where something went wrong. `search` runs
620
+ before every Grep, so it is dispatched before the rest of the CLI loads.
621
+
622
+ ---
623
+
624
+ ### `mast setup <harness> [path]`
625
+
626
+ Install what tells an agent to use mast: hooks that prime it at session start and remind it
627
+ before a built-in search, and rules files for harnesses that read them. `<harness>` is
628
+ `claude`, `cursor`, `vscode`, `windsurf`, `zed` or `desktop`; `[path]` is the project root
629
+ (default: the current directory).
630
+
631
+ | harness | project file (default) | `--global` |
632
+ |---|---|---|
633
+ | `claude` | `.claude/settings.json` | `~/.claude/settings.json` |
634
+ | `cursor` | `.cursor/hooks.json` and `.cursor/rules/mast.mdc` | `~/.cursor/hooks.json` only; Cursor has no user-level rules file, and a note says so |
635
+ | `vscode` | `.github/hooks/mast.json` | `~/.copilot/hooks/mast.json` |
636
+ | `windsurf` | `.windsurf/rules/mast.md`, or `.devin/rules/mast.md` if a `.devin` directory exists | refused (exit 1) |
637
+ | `zed` | a marked block in the first existing of `.rules`, `.cursorrules`, `.windsurfrules`, `.clinerules`, `.github/copilot-instructions.md`, `AGENT.md`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` | refused (exit 1) |
638
+ | `desktop` | nothing | nothing |
639
+
640
+ ```
641
+ Options:
642
+ --global Write the user-level file instead of the project one
643
+ --check Write nothing; exit 0 only if everything is installed and current
644
+ --remove Remove what mast installed
645
+ --dry-run Print what would be written, and write nothing
646
+ ```
647
+
648
+ `claude` and `cursor` get both hooks (`mast hook <harness> session-start` and
649
+ `mast hook <harness> search`). **`vscode` gets the session primer only:** VS Code ignores hook
650
+ matchers, so a search hook would run before every tool call, and the name of its search tool
651
+ is not documented. **`windsurf`, `zed` and `desktop` have no hook system mast can use**, so
652
+ the first line of output says that only static instructions are installed. `desktop` writes
653
+ nothing: its only channel is the instructions string `mast serve` sends in the MCP handshake,
654
+ which needs no setup, and every flag exits 0 after saying so.
655
+
656
+ The rules files carry the text `mast skill` prints. `mast.mdc` (frontmatter `description` and
657
+ `alwaysApply: true`) and the Windsurf `mast.md` (frontmatter `trigger: always_on`) belong to
658
+ mast outright, and are overwritten if they differ; there is no marker, the name is the claim.
659
+ The Windsurf file stays under Windsurf's documented 12,000-character limit, checked in a test.
660
+ `zed` never creates a file: if none of Zed's candidates exists it writes nothing and exits 1,
661
+ telling you to create `.rules` and run it again, and otherwise it splices the same marked
662
+ block `mast skill --install` uses into the first one that exists. `--remove` takes out that
663
+ block, from every candidate that carries it, and leaves the file. The Zed file list is
664
+ from memory of Zed's documentation and has not been checked against it.
665
+
666
+ **Why:** the hook command depends on how mast is installed. A global install writes
667
+ `mast hook ...`; a source checkout writes `node "<path to dist/cli/index.js>" hook ...`, a
668
+ path specific to your machine, so do not commit that file; a project dependency writes a path
669
+ into `node_modules/.bin`. When the package that depends on mast lives in a subdirectory
670
+ (`typescript/node_modules`, say), run `setup` with the binary from that directory and the
671
+ command points there; `setup` looks beside the `node_modules` it is running out of before
672
+ the project root's. A project dependency cannot be installed with `--global`: a
673
+ user-level hook pointing into one project's `node_modules` breaks in every other project.
674
+ For VS Code the relative `node_modules/.bin/mast` is unverified, because its docs do not
675
+ say what directory hooks run in.
676
+
677
+ For hook files it only ever touches entries whose command ends in `hook <harness> session-start`
678
+ or `hook <harness> search`. Every other key, hook and field in the file is kept, in order, and
679
+ the file keeps its indentation and trailing newline. Re-running changes nothing and does not
680
+ write any file; a changed install (say, source to global) replaces the entry in place. A file
681
+ that is not valid JSON, or whose `hooks` have an unexpected shape, is reported and left
682
+ untouched (exit 1). `--remove` deletes only mast's entries, and for `vscode` deletes
683
+ `mast.json` once nothing else is in it. `--check` cannot be combined with `--remove` or
684
+ `--dry-run` (exit 2), nor can an unknown harness be given (exit 2). For `cursor`, `--check`
685
+ exits 0 only if both the hooks file and the rules file are current. Nothing here has been
686
+ run inside Cursor, VS Code, Windsurf or Zed.
515
687
 
516
688
  ---
517
689
 
@@ -522,7 +694,9 @@ Check for a newer release; print how to install it, and what it will cost.
522
694
  **Why:** it detects how MAST was installed and prints the matching command rather than
523
695
  running it, because a CLI cannot reliably distinguish a global install from a dev
524
696
  dependency. It also reports whether the upgrade bumps the index schema — which forces a
525
- full reindex on the next `serve` — and your package manager cannot tell you that.
697
+ full reindex on the next `serve` — and your package manager cannot tell you that. When
698
+ mast's hooks, rules files or skill blocks are installed, it lists the `mast setup <harness>
699
+ --check` and `mast skill --install` commands to re-run afterwards.
526
700
 
527
701
  ---
528
702
 
@@ -0,0 +1,18 @@
1
+ # Using mast
2
+
3
+ mast indexes this repository's TypeScript, JavaScript and Markdown at the AST level.
4
+
5
+ - To find code (a symbol, its callers, a module's exports), call mast before grep, glob or
6
+ reading whole files. It returns ranked declarations, not line matches.
7
+ - Put code tokens in the query: function, type and column names. An exact symbol name
8
+ anchors its declaration to the top. If a query returns nothing, change the vocabulary.
9
+ - `mast_search`: discover code by name or token.
10
+ - `mast_signature`: a symbol's declaration and resolved parameter types.
11
+ - `mast_callers`: who calls a function. Run it before any refactor.
12
+ - `mast_exports`: a module's public API, without reading the file.
13
+ - `mast_rename_impact`: the checklist for a rename.
14
+ - Use grep for other languages, non-code files, or an exact regex over text.
15
+ - An empty result is not proof of absence. Check `index_empty`, `unindexed_files` and
16
+ `stale` on the response before concluding something does not exist.
17
+ - Call `mast_reindex` after creating or renaming files. A new file is invisible to every
18
+ read tool until an index pass runs.
@@ -0,0 +1,51 @@
1
+ # MAST signal reference
2
+
3
+ The fields MAST puts on an answer to say what it does not know, and how it treats a file that
4
+ changed after it was indexed. `mast skill` carries the short rules; this is the detail behind them.
5
+
6
+ ## How MAST handles a file that changed since it was indexed
7
+
8
+ Two mechanisms, and which one you get depends on the tool. Neither can see a file that
9
+ was never indexed at all.
10
+
11
+ - **Re-parsed for you, before the answer** — `mast_signature`, `mast_callers`,
12
+ `mast_exports`, `mast_dependencies`, `mast_rename_impact`. If the re-parse loses a
13
+ race with a writer, the result carries `file_busy_returning_stale_cache`.
14
+ - **Flagged, not re-parsed** — `mast_search`, `mast_implementors`. Affected results
15
+ carry `stale: true`. The code and line numbers shown may be out of date; call one of
16
+ the tools above, or `mast_reindex`, to get the current version.
17
+
18
+ Both only cover files already in the index. That is why creating a file needs an
19
+ explicit `mast_reindex`.
20
+
21
+ ## Reading the answers honestly
22
+
23
+ MAST reports what it does not know. These signals are load-bearing. All but the last are
24
+ **omitted entirely when they do not apply** — so their absence is meaningful, and their
25
+ presence is never `false`. `truncated` is the exception: it is always present on a
26
+ `type_context` entry, so check its value rather than its presence.
27
+
28
+ | signal | on | means |
29
+ |---|---|---|
30
+ | `stale` | per result of `mast_search`, `mast_implementors` | this result's file changed since indexing; line numbers may be wrong |
31
+ | `file_busy_returning_stale_cache` | the five re-parsing tools | a refresh was attempted and lost to a writer; retry shortly |
32
+ | `index_empty` | the empty answer of any of the eight tools that return a result set | **nothing is indexed at all.** This is not "no match" — run `mast_reindex`, or check that `mast_status` names the tree you meant |
33
+ | `unindexed_files` | `mast_search`, `mast_callers`, `mast_implementors`, `mast_rename_impact`, `mast_project_skeleton` (always); `mast_signature`, `mast_exports`, `mast_dependencies` (on an empty answer only) | that many files on disk are not in the index, so this answer was computed over an incomplete corpus. **An empty result carrying this is not evidence of absence.** The set-returning tools report it even with hits — a short list of callers reads exactly like a complete one |
34
+ | `results_truncated` | `mast_signature`, `mast_implementors` | you got the first page, not the answer. The field carries the real total; raise `limit` or narrow the query |
35
+ | `exports_truncated` | `mast_exports` | the same, for a module's export list |
36
+ | `potential_truncated` | `mast_callers`, `mast_rename_impact` | the unresolved-candidate set was capped; the real count is larger |
37
+ | `truncated` | a type in `mast_signature`'s `type_context` | that declaration was clipped at 50 lines |
38
+
39
+ Two more things that are not flags:
40
+
41
+ - In `mast_callers` and `mast_rename_impact`, **`verified_callers` and
42
+ `potential_matches` are not the same claim.** A verified caller carries a `resolution`
43
+ and is safe to act on. A potential match carries a `reason` and is a name match with
44
+ no proven edge — review it before editing it.
45
+ - An **empty result is not proof of absence.** MAST indexes TypeScript, JavaScript, and
46
+ Markdown only — a symbol defined in Python, Go, Java, or any other language is absent
47
+ from the index, not absent from the repository. Check `index_empty` and
48
+ `unindexed_files` before concluding "it isn't there", and **never delete or rewrite
49
+ code on the strength of an empty result alone.** Every result-set tool carries
50
+ `unindexed_files` now, not just `mast_search` — so an empty `mast_callers` answer that
51
+ carries it is not a green light to delete the function.
package/assets/skill.md CHANGED
@@ -18,7 +18,7 @@ graph, so prefer it over reading files or grepping.
18
18
  - When a query returns nothing, **change vocabulary — do not repeat it**.
19
19
  - **Call `mast_reindex` after creating or renaming files**, before any query that depends
20
20
  on what you just wrote. Editing the *body* of a file MAST already knows is handled for
21
- you (see below); a **new** file is not, and is invisible until an index pass runs.
21
+ you; a **new** file is not, and is invisible until an index pass runs.
22
22
 
23
23
  ## Picking the right tool
24
24
 
@@ -36,49 +36,8 @@ graph, so prefer it over reading files or grepping.
36
36
  | `mast_status` | index freshness and health |
37
37
  | `mast_reindex` | refresh the index after edits |
38
38
 
39
- ## How MAST handles a file that changed since it was indexed
40
-
41
- Two mechanisms, and which one you get depends on the tool. Neither can see a file that
42
- was never indexed at all.
43
-
44
- - **Re-parsed for you, before the answer** — `mast_signature`, `mast_callers`,
45
- `mast_exports`, `mast_dependencies`, `mast_rename_impact`. If the re-parse loses a
46
- race with a writer, the result carries `file_busy_returning_stale_cache`.
47
- - **Flagged, not re-parsed** — `mast_search`, `mast_implementors`. Affected results
48
- carry `stale: true`. The code and line numbers shown may be out of date; call one of
49
- the tools above, or `mast_reindex`, to get the current version.
50
-
51
- Both only cover files already in the index. That is why creating a file needs an
52
- explicit `mast_reindex`.
53
-
54
- ## Reading the answers honestly
55
-
56
- MAST reports what it does not know. These signals are load-bearing. All but the last are
57
- **omitted entirely when they do not apply** — so their absence is meaningful, and their
58
- presence is never `false`. `truncated` is the exception: it is always present on a
59
- `type_context` entry, so check its value rather than its presence.
60
-
61
- | signal | on | means |
62
- |---|---|---|
63
- | `stale` | per result of `mast_search`, `mast_implementors` | this result's file changed since indexing; line numbers may be wrong |
64
- | `file_busy_returning_stale_cache` | the five re-parsing tools | a refresh was attempted and lost to a writer; retry shortly |
65
- | `index_empty` | the empty answer of any of the eight tools that return a result set | **nothing is indexed at all.** This is not "no match" — run `mast_reindex`, or check that `mast_status` names the tree you meant |
66
- | `unindexed_files` | `mast_search`, `mast_callers`, `mast_implementors`, `mast_rename_impact`, `mast_project_skeleton` (always); `mast_signature`, `mast_exports`, `mast_dependencies` (on an empty answer only) | that many files on disk are not in the index, so this answer was computed over an incomplete corpus. **An empty result carrying this is not evidence of absence.** The set-returning tools report it even with hits — a short list of callers reads exactly like a complete one |
67
- | `results_truncated` | `mast_signature`, `mast_implementors` | you got the first page, not the answer. The field carries the real total; raise `limit` or narrow the query |
68
- | `exports_truncated` | `mast_exports` | the same, for a module's export list |
69
- | `potential_truncated` | `mast_callers`, `mast_rename_impact` | the unresolved-candidate set was capped; the real count is larger |
70
- | `truncated` | a type in `mast_signature`'s `type_context` | that declaration was clipped at 50 lines |
71
-
72
- Two more things that are not flags:
73
-
74
- - In `mast_callers` and `mast_rename_impact`, **`verified_callers` and
75
- `potential_matches` are not the same claim.** A verified caller carries a `resolution`
76
- and is safe to act on. A potential match carries a `reason` and is a name match with
77
- no proven edge — review it before editing it.
78
- - An **empty result is not proof of absence.** MAST indexes TypeScript, JavaScript, and
79
- Markdown only — a symbol defined in Python, Go, Java, or any other language is absent
80
- from the index, not absent from the repository. Check `index_empty` and
81
- `unindexed_files` before concluding "it isn't there", and **never delete or rewrite
82
- code on the strength of an empty result alone.** Every result-set tool carries
83
- `unindexed_files` now, not just `mast_search` — so an empty `mast_callers` answer that
84
- carries it is not a green light to delete the function.
39
+ An empty result is not proof of absence. MAST indexes TypeScript, JavaScript, and Markdown
40
+ only, so a symbol in any other language is absent from the index, not from the repository.
41
+ Check `index_empty` and `unindexed_files` on the response before concluding "it isn't
42
+ there", and never delete or rewrite code on an empty result alone. `mast docs signals`
43
+ prints the full signal reference.
@@ -9,6 +9,7 @@ import type { Command } from 'commander';
9
9
  */
10
10
  export declare class DocsError extends Error {
11
11
  }
12
+ export declare const PACKAGE_ROOT: string;
12
13
  export interface DocTopic {
13
14
  readonly name: string;
14
15
  readonly file: string;
@@ -1 +1 @@
1
- {"version":3,"file":"docs-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/docs-cmd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;CAAG;AAMvC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAIzC,CAAC;AAEF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc5C;AAED,wBAAgB,QAAQ,IAAI,MAAM,CASjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAY1D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwC3D"}
1
+ {"version":3,"file":"docs-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/docs-cmd.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;CAAG;AAIvC,eAAO,MAAM,YAAY,QAA4D,CAAC;AAEtF,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAKzC,CAAC;AAEF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc5C;AAED,wBAAgB,QAAQ,IAAI,MAAM,CASjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAY1D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwC3D"}
@@ -16,11 +16,12 @@ export class DocsError extends Error {
16
16
  }
17
17
  // `dist/cli/` and `src/cli/` both sit two levels below the package root, so this
18
18
  // resolves identically under vitest and in the published tarball.
19
- const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
19
+ export const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
20
20
  export const DOC_TOPICS = [
21
21
  { name: 'readme', file: 'README.md', summary: 'Install, quick start, CLI and MCP tool reference' },
22
22
  { name: 'spec', file: 'MAST_SPEC.md', summary: 'Full behavioural specification — schemas, tool contracts, invariants' },
23
23
  { name: 'skill', file: 'assets/skill.md', summary: 'The instructions to paste into an agent prompt or skill file' },
24
+ { name: 'signals', file: 'assets/signals.md', summary: 'The staleness, truncation and emptiness signals on an answer, and how to read them' },
24
25
  ];
25
26
  export function readDoc(name) {
26
27
  const topic = DOC_TOPICS.find((t) => t.name === name);
@@ -1 +1 @@
1
- {"version":3,"file":"docs-cmd.js","sourceRoot":"","sources":["../../src/cli/docs-cmd.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"docs-cmd.js","sourceRoot":"","sources":["../../src/cli/docs-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;CAAG;AAEvC,iFAAiF;AACjF,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAQtF,MAAM,CAAC,MAAM,UAAU,GAAwB;IAC7C,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAM,OAAO,EAAE,kDAAkD,EAAE;IACtG,EAAE,IAAI,EAAE,MAAM,EAAI,IAAI,EAAE,cAAc,EAAG,OAAO,EAAE,sEAAsE,EAAE;IAC1H,EAAE,IAAI,EAAE,OAAO,EAAG,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,8DAA8D,EAAE;IACpH,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,oFAAoF,EAAE;CAC9I,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,SAAS,CACjB,uBAAuB,IAAI,wBAAwB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CACjB,GAAG,KAAK,CAAC,IAAI,yCAAyC,YAAY,IAAI,WAAW,EAAE,CACpF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,OAAO;QACL,GAAG,YAAY,IAAI,WAAW,0CAA0C;QACxE,EAAE;QACF,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACnE,EAAE;QACF,oCAAoC;KACrC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,4EAA4E,CAAC;SACzF,MAAM,CAAC,CAAC,KAAyB,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,wEAAwE,CAAC;SACrF,MAAM,CAAC,WAAW,EAAE,0EAA0E,CAAC;SAC/F,MAAM,CAAC,WAAW,EAAE,0DAA0D,CAAC;SAC/E,MAAM,CAAC,CAAC,WAA+B,EAAE,IAA6C,EAAE,EAAE;QACzF,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC/B,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;gBACnC,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,qBAAqB,CAAC;YAC/E,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,0EAA0E;gBAC1E,sEAAsE;gBACtE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,CAAC,kCAAkC,IAAI,GAAG,EAAE,EAAE;oBAC7C,8EAA8E;oBAC9E,uFAAuF,EAAE,EAAE;oBAC3F,2BAA2B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;YACpC,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;gBAC/E,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM;gBACzB,CAAC,CAAC,mEAAmE;gBACrE,CAAC,CAAC,oGAAoG,CAAC,CAAC;QAC5G,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerHookCommand(program: Command): void;
3
+ //# sourceMappingURL=hook-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/hook-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAU1D"}
@@ -0,0 +1,13 @@
1
+ import { runHookFromProcess } from './hook.js';
2
+ export function registerHookCommand(program) {
3
+ program
4
+ .command('hook <harness> <event>')
5
+ .description('Hook entry point: read a harness hook JSON on stdin, write its envelope on stdout (claude|cursor|vscode, session-start|search)')
6
+ .action(async (harness, event) => {
7
+ // In normal use `cli/index.ts` dispatches `hook` before this program is ever
8
+ // imported, to avoid its startup cost; this registration exists so `mast --help`
9
+ // lists the command and the README drift guard sees it.
10
+ await runHookFromProcess(harness, event);
11
+ });
12
+ }
13
+ //# sourceMappingURL=hook-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-cmd.js","sourceRoot":"","sources":["../../src/cli/hook-cmd.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,OAAO;SACJ,OAAO,CAAC,wBAAwB,CAAC;SACjC,WAAW,CAAC,gIAAgI,CAAC;SAC7I,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,KAAa,EAAE,EAAE;QAC/C,6EAA6E;QAC7E,iFAAiF;QACjF,wDAAwD;QACxD,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** The two facts the search hook needs from the project's configuration. */
2
+ export interface HookConfig {
3
+ readonly stateDir: string;
4
+ readonly fileExtensions: readonly string[];
5
+ }
6
+ /**
7
+ * Resolves the state directory and the indexed extensions the way `resolveConfig`
8
+ * does, without zod.
9
+ *
10
+ * This is a second producer of two facts, tolerated because the hook runs before every
11
+ * Grep and `resolveConfig` drags in zod through `env.ts`. `hook-state-dir.test.ts`
12
+ * runs both over the same cases; change one and that test tells you about the other.
13
+ *
14
+ * Precedence mirrors `resolveConfig` minus the CLI flags, which a hook never has.
15
+ * State dir: MAST_STATE_DIR, then `state_dir` in `<root>/mast.config.json`, then the
16
+ * default, resolved against the project root (an absolute value stays as it is).
17
+ * Extensions: `file_extensions` in `mast.config.json`, then the one persisted in
18
+ * `<state dir>/config.json` (what `mast init --extensions` wrote), then the default.
19
+ *
20
+ * Throws where `resolveConfig` throws (an empty MAST_STATE_DIR, an unparseable
21
+ * config file), so the hook reports the same broken setup the CLI would.
22
+ */
23
+ export declare function resolveHookConfigLight(projectRoot: string, env: Record<string, string | undefined>): HookConfig;
24
+ //# sourceMappingURL=hook-state-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-state-dir.d.ts","sourceRoot":"","sources":["../../src/cli/hook-state-dir.ts"],"names":[],"mappings":"AAKA,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,UAAU,CAcZ"}
@@ -0,0 +1,57 @@
1
+ // Imports Node built-ins and builtin-only repo modules only; see cli/hook.ts.
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { DEFAULT_FILE_EXTENSIONS, DEFAULT_STATE_DIR } from '../store/defaults.js';
5
+ /**
6
+ * Resolves the state directory and the indexed extensions the way `resolveConfig`
7
+ * does, without zod.
8
+ *
9
+ * This is a second producer of two facts, tolerated because the hook runs before every
10
+ * Grep and `resolveConfig` drags in zod through `env.ts`. `hook-state-dir.test.ts`
11
+ * runs both over the same cases; change one and that test tells you about the other.
12
+ *
13
+ * Precedence mirrors `resolveConfig` minus the CLI flags, which a hook never has.
14
+ * State dir: MAST_STATE_DIR, then `state_dir` in `<root>/mast.config.json`, then the
15
+ * default, resolved against the project root (an absolute value stays as it is).
16
+ * Extensions: `file_extensions` in `mast.config.json`, then the one persisted in
17
+ * `<state dir>/config.json` (what `mast init --extensions` wrote), then the default.
18
+ *
19
+ * Throws where `resolveConfig` throws (an empty MAST_STATE_DIR, an unparseable
20
+ * config file), so the hook reports the same broken setup the CLI would.
21
+ */
22
+ export function resolveHookConfigLight(projectRoot, env) {
23
+ const root = resolve(projectRoot);
24
+ const fileConfig = readJsonObject(join(root, 'mast.config.json'));
25
+ const fromEnv = env['MAST_STATE_DIR'];
26
+ if (fromEnv === '')
27
+ throw new Error('MAST_STATE_DIR is set but empty');
28
+ const stateDir = resolve(root, fromEnv ?? stateDirOf(fileConfig) ?? DEFAULT_STATE_DIR);
29
+ const fileExtensions = stringArrayOf(fileConfig, 'file_extensions') ??
30
+ stringArrayOf(readJsonObject(join(stateDir, 'config.json')), 'file_extensions') ??
31
+ DEFAULT_FILE_EXTENSIONS;
32
+ return { stateDir, fileExtensions };
33
+ }
34
+ function readJsonObject(file) {
35
+ if (!existsSync(file))
36
+ return null;
37
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
38
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
39
+ return null;
40
+ return Object.fromEntries(Object.entries(parsed));
41
+ }
42
+ function stateDirOf(fileConfig) {
43
+ const value = fileConfig?.['state_dir'];
44
+ if (value === undefined || value === null)
45
+ return undefined;
46
+ if (typeof value !== 'string')
47
+ throw new Error('mast.config.json: state_dir must be a string');
48
+ return value;
49
+ }
50
+ function stringArrayOf(source, key) {
51
+ const value = source?.[key];
52
+ if (!Array.isArray(value))
53
+ return undefined;
54
+ const strings = value.filter((entry) => typeof entry === 'string');
55
+ return strings.length === value.length ? strings : undefined;
56
+ }
57
+ //# sourceMappingURL=hook-state-dir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-state-dir.js","sourceRoot":"","sources":["../../src/cli/hook-state-dir.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQlF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAmB,EACnB,GAAuC;IAEvC,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,iBAAiB,CAAC,CAAC;IAEvF,MAAM,cAAc,GAClB,aAAa,CAAC,UAAU,EAAE,iBAAiB,CAAC;QAC5C,aAAa,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,EAAE,iBAAiB,CAAC;QAC/E,uBAAuB,CAAC;IAE1B,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,UAAU,CAAC,UAA0C;IAC5D,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC,WAAW,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC/F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,MAAsC,EAAE,GAAW;IACxE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;IACpF,OAAO,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,44 @@
1
+ export type Harness = 'claude' | 'cursor' | 'vscode';
2
+ export type HookEvent = 'session-start' | 'search';
3
+ /** What the hook needs from the outside world; injected so the shell is testable. */
4
+ export interface HookIo {
5
+ readStdin(): Promise<string>;
6
+ write(text: string): void;
7
+ warn(line: string): void;
8
+ env: Record<string, string | undefined>;
9
+ cwd(): string;
10
+ fileExists(path: string): boolean;
11
+ isDirectory(path: string): boolean;
12
+ /** Same text `mast prime` prints for this project root. May be slow. */
13
+ prime(projectRoot: string): Promise<string>;
14
+ }
15
+ export interface HookFacts {
16
+ readonly indexExists: boolean;
17
+ readonly primeText: string;
18
+ /**
19
+ * True when the search's `path` names an existing directory. A directory called
20
+ * `next.js` or `site.io` would otherwise read as a file with an unindexed extension
21
+ * and silence the reminder for every search under it.
22
+ */
23
+ readonly searchPathIsDirectory: boolean;
24
+ /** The extensions this project indexes: its own list where it sets one, else the defaults. */
25
+ readonly indexedExtensions: readonly string[];
26
+ }
27
+ export declare const SEARCH_REMINDER: string;
28
+ /**
29
+ * The whole decision, pure. Returns the harness's envelope, or null for "print nothing".
30
+ * Unknown harness or event is null rather than a throw: see `runHook` for why.
31
+ */
32
+ export declare function decide(harness: string, event: string, input: unknown, facts: HookFacts): object | null;
33
+ /**
34
+ * I/O shell around `decide`.
35
+ *
36
+ * Never throws and never sets a failing exit code. A hook that fails breaks the user's
37
+ * session, and anything on stdout that is not the envelope is a malformed hook response,
38
+ * so every problem here ends as empty stdout plus one stderr line. Ordinary quiet cases
39
+ * (no index, a search scoped to another language) say nothing at all.
40
+ */
41
+ export declare function runHook(harness: string, event: string, io: HookIo): Promise<void>;
42
+ /** Production wiring, called from `cli/index.ts` and from the commander registration. */
43
+ export declare function runHookFromProcess(harness: string, event: string): Promise<void>;
44
+ //# sourceMappingURL=hook.d.ts.map