@spikedpunch/mast 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/README.md +180 -9
- package/assets/prime.md +18 -0
- package/assets/signals.md +51 -0
- package/assets/skill.md +6 -47
- package/dist/cli/docs-cmd.d.ts +1 -0
- package/dist/cli/docs-cmd.d.ts.map +1 -1
- package/dist/cli/docs-cmd.js +2 -1
- package/dist/cli/docs-cmd.js.map +1 -1
- package/dist/cli/hook-cmd.d.ts +3 -0
- package/dist/cli/hook-cmd.d.ts.map +1 -0
- package/dist/cli/hook-cmd.js +13 -0
- package/dist/cli/hook-cmd.js.map +1 -0
- package/dist/cli/hook-state-dir.d.ts +24 -0
- package/dist/cli/hook-state-dir.d.ts.map +1 -0
- package/dist/cli/hook-state-dir.js +57 -0
- package/dist/cli/hook-state-dir.js.map +1 -0
- package/dist/cli/hook.d.ts +44 -0
- package/dist/cli/hook.d.ts.map +1 -0
- package/dist/cli/hook.js +200 -0
- package/dist/cli/hook.js.map +1 -0
- package/dist/cli/index.js +12 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/installed-artifacts.d.ts +23 -0
- package/dist/cli/installed-artifacts.d.ts.map +1 -0
- package/dist/cli/installed-artifacts.js +39 -0
- package/dist/cli/installed-artifacts.js.map +1 -0
- package/dist/cli/prime-cmd.d.ts +17 -0
- package/dist/cli/prime-cmd.d.ts.map +1 -0
- package/dist/cli/prime-cmd.js +70 -0
- package/dist/cli/prime-cmd.js.map +1 -0
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +6 -0
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/setup-cmd.d.ts +34 -0
- package/dist/cli/setup-cmd.d.ts.map +1 -0
- package/dist/cli/setup-cmd.js +205 -0
- package/dist/cli/setup-cmd.js.map +1 -0
- package/dist/cli/setup-command.d.ts +32 -0
- package/dist/cli/setup-command.d.ts.map +1 -0
- package/dist/cli/setup-command.js +43 -0
- package/dist/cli/setup-command.js.map +1 -0
- package/dist/cli/setup-plan.d.ts +52 -0
- package/dist/cli/setup-plan.d.ts.map +1 -0
- package/dist/cli/setup-plan.js +221 -0
- package/dist/cli/setup-plan.js.map +1 -0
- package/dist/cli/setup-rules.d.ts +41 -0
- package/dist/cli/setup-rules.d.ts.map +1 -0
- package/dist/cli/setup-rules.js +76 -0
- package/dist/cli/setup-rules.js.map +1 -0
- package/dist/cli/setup-static.d.ts +13 -0
- package/dist/cli/setup-static.d.ts.map +1 -0
- package/dist/cli/setup-static.js +116 -0
- package/dist/cli/setup-static.js.map +1 -0
- package/dist/cli/skill-install.d.ts +2 -0
- package/dist/cli/skill-install.d.ts.map +1 -1
- package/dist/cli/skill-install.js +2 -0
- package/dist/cli/skill-install.js.map +1 -1
- package/dist/cli/upgrade-cmd.d.ts +5 -1
- package/dist/cli/upgrade-cmd.d.ts.map +1 -1
- package/dist/cli/upgrade-cmd.js +28 -3
- package/dist/cli/upgrade-cmd.js.map +1 -1
- package/dist/indexer/watcher.d.ts +80 -2
- package/dist/indexer/watcher.d.ts.map +1 -1
- package/dist/indexer/watcher.js +172 -27
- package/dist/indexer/watcher.js.map +1 -1
- package/dist/mcp/instructions.d.ts +7 -0
- package/dist/mcp/instructions.d.ts.map +1 -0
- package/dist/mcp/instructions.js +10 -0
- package/dist/mcp/instructions.js.map +1 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +17 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/store/config.d.ts.map +1 -1
- package/dist/store/config.js +4 -5
- package/dist/store/config.js.map +1 -1
- package/dist/store/defaults.d.ts +9 -0
- package/dist/store/defaults.d.ts.map +1 -0
- package/dist/store/defaults.js +12 -0
- package/dist/store/defaults.js.map +1 -0
- 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
|
|
250
|
-
|
|
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`,
|
|
492
|
-
|
|
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,117 @@ 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
|
|
512
|
-
|
|
513
|
-
not find it", not "it does not exist", which is the single most
|
|
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`. A project dependency cannot be installed with `--global`: a
|
|
670
|
+
user-level hook pointing into one project's `node_modules` breaks in every other project.
|
|
671
|
+
For VS Code the relative `node_modules/.bin/mast` is unverified, because its docs do not
|
|
672
|
+
say what directory hooks run in.
|
|
673
|
+
|
|
674
|
+
For hook files it only ever touches entries whose command ends in `hook <harness> session-start`
|
|
675
|
+
or `hook <harness> search`. Every other key, hook and field in the file is kept, in order, and
|
|
676
|
+
the file keeps its indentation and trailing newline. Re-running changes nothing and does not
|
|
677
|
+
write any file; a changed install (say, source to global) replaces the entry in place. A file
|
|
678
|
+
that is not valid JSON, or whose `hooks` have an unexpected shape, is reported and left
|
|
679
|
+
untouched (exit 1). `--remove` deletes only mast's entries, and for `vscode` deletes
|
|
680
|
+
`mast.json` once nothing else is in it. `--check` cannot be combined with `--remove` or
|
|
681
|
+
`--dry-run` (exit 2), nor can an unknown harness be given (exit 2). For `cursor`, `--check`
|
|
682
|
+
exits 0 only if both the hooks file and the rules file are current. Nothing here has been
|
|
683
|
+
run inside Cursor, VS Code, Windsurf or Zed.
|
|
515
684
|
|
|
516
685
|
---
|
|
517
686
|
|
|
@@ -522,7 +691,9 @@ Check for a newer release; print how to install it, and what it will cost.
|
|
|
522
691
|
**Why:** it detects how MAST was installed and prints the matching command rather than
|
|
523
692
|
running it, because a CLI cannot reliably distinguish a global install from a dev
|
|
524
693
|
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.
|
|
694
|
+
full reindex on the next `serve` — and your package manager cannot tell you that. When
|
|
695
|
+
mast's hooks, rules files or skill blocks are installed, it lists the `mast setup <harness>
|
|
696
|
+
--check` and `mast skill --install` commands to re-run afterwards.
|
|
526
697
|
|
|
527
698
|
---
|
|
528
699
|
|
package/assets/prime.md
ADDED
|
@@ -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
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
package/dist/cli/docs-cmd.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cli/docs-cmd.js
CHANGED
|
@@ -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);
|
package/dist/cli/docs-cmd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"docs-cmd.js","sourceRoot":"","sources":["../../src/cli/docs-cmd.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/cli/hook.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,QAAQ,CAAC;AAEnD,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACrB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,wEAAwE;IACxE,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,8FAA8F;IAC9F,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED,eAAO,MAAM,eAAe,QAEyC,CAAC;AAuFtE;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAKtG;AAeD;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvF;AAoCD,yFAAyF;AACzF,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtF"}
|