@spikedpunch/mast 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MAST_SPEC.md +169 -44
- package/README.md +86 -11
- package/assets/skill.md +49 -10
- package/dist/ast/types.d.ts +137 -1
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/cli/query.d.ts +40 -1
- package/dist/cli/query.d.ts.map +1 -1
- package/dist/cli/query.js +36 -2
- package/dist/cli/query.js.map +1 -1
- package/dist/cli/search-cmd.d.ts +6 -1
- package/dist/cli/search-cmd.d.ts.map +1 -1
- package/dist/cli/search-cmd.js +9 -2
- package/dist/cli/search-cmd.js.map +1 -1
- package/dist/cli/serve.d.ts +6 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +14 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/status.d.ts +15 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +32 -6
- package/dist/cli/status.js.map +1 -1
- package/dist/indexer/freshness.d.ts +10 -0
- package/dist/indexer/freshness.d.ts.map +1 -1
- package/dist/indexer/freshness.js +7 -1
- package/dist/indexer/freshness.js.map +1 -1
- package/dist/indexer/import-resolver.js +28 -16
- package/dist/indexer/import-resolver.js.map +1 -1
- package/dist/indexer/index.d.ts +38 -3
- package/dist/indexer/index.d.ts.map +1 -1
- package/dist/indexer/index.js +47 -4
- package/dist/indexer/index.js.map +1 -1
- package/dist/indexer/watcher.d.ts +16 -0
- package/dist/indexer/watcher.d.ts.map +1 -1
- package/dist/indexer/watcher.js +8 -0
- package/dist/indexer/watcher.js.map +1 -1
- package/dist/mcp/context.d.ts +16 -0
- package/dist/mcp/context.d.ts.map +1 -1
- package/dist/mcp/freshness-probe.d.ts +82 -0
- package/dist/mcp/freshness-probe.d.ts.map +1 -0
- package/dist/mcp/freshness-probe.js +111 -0
- package/dist/mcp/freshness-probe.js.map +1 -0
- package/dist/mcp/server.d.ts +46 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +65 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/_helpers.d.ts +40 -0
- package/dist/mcp/tools/_helpers.d.ts.map +1 -1
- package/dist/mcp/tools/_helpers.js +27 -0
- package/dist/mcp/tools/_helpers.js.map +1 -1
- package/dist/mcp/tools/callers.d.ts.map +1 -1
- package/dist/mcp/tools/callers.js +6 -1
- package/dist/mcp/tools/callers.js.map +1 -1
- package/dist/mcp/tools/dependencies.d.ts.map +1 -1
- package/dist/mcp/tools/dependencies.js +2 -1
- package/dist/mcp/tools/dependencies.js.map +1 -1
- package/dist/mcp/tools/exports.d.ts.map +1 -1
- package/dist/mcp/tools/exports.js +2 -1
- package/dist/mcp/tools/exports.js.map +1 -1
- package/dist/mcp/tools/implementors.d.ts.map +1 -1
- package/dist/mcp/tools/implementors.js +2 -1
- package/dist/mcp/tools/implementors.js.map +1 -1
- package/dist/mcp/tools/project-skeleton.d.ts.map +1 -1
- package/dist/mcp/tools/project-skeleton.js +2 -1
- package/dist/mcp/tools/project-skeleton.js.map +1 -1
- package/dist/mcp/tools/reindex.d.ts.map +1 -1
- package/dist/mcp/tools/reindex.js +3 -0
- package/dist/mcp/tools/reindex.js.map +1 -1
- package/dist/mcp/tools/rename-impact.d.ts.map +1 -1
- package/dist/mcp/tools/rename-impact.js +4 -1
- package/dist/mcp/tools/rename-impact.js.map +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +13 -2
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/signature.d.ts.map +1 -1
- package/dist/mcp/tools/signature.js +2 -1
- package/dist/mcp/tools/signature.js.map +1 -1
- package/dist/mcp/tools/status.d.ts.map +1 -1
- package/dist/mcp/tools/status.js +8 -2
- package/dist/mcp/tools/status.js.map +1 -1
- package/package.json +4 -2
package/MAST_SPEC.md
CHANGED
|
@@ -41,13 +41,14 @@ the pre-deletion system is preserved at the git tag
|
|
|
41
41
|
- Code generation or explanation.
|
|
42
42
|
- PR review, wiki generation, story generation.
|
|
43
43
|
- A persistent background daemon — freshness is handled by the startup check and
|
|
44
|
-
`mast_reindex`. *Narrow carve-out:* `mast serve
|
|
45
|
-
|
|
46
|
-
serve process lifetime (not a daemon)
|
|
47
|
-
|
|
48
|
-
searchable
|
|
49
|
-
handling (§9.0)
|
|
50
|
-
already-indexed files without it
|
|
44
|
+
`mast_reindex`. *Narrow carve-out:* `mast serve`'s file watcher (§11.4, on by
|
|
45
|
+
default since 2026-09-03; `--no-watch` opts out, and the SDD pipeline passes
|
|
46
|
+
it). It is scoped to the serve process lifetime (not a daemon) and is a
|
|
47
|
+
discovery-freshness optimization — it lets new files and symbols become
|
|
48
|
+
searchable without waiting for an explicit `mast_reindex`. JIT staleness
|
|
49
|
+
handling (§9.0) guarantees line-coordinate and content correctness for
|
|
50
|
+
already-indexed files without it, but is structurally blind to files the
|
|
51
|
+
index has never seen, which is what the watcher covers.
|
|
51
52
|
- Support for non-TypeScript/JavaScript projects in v1 (AST layer is extensible but
|
|
52
53
|
v1 targets the SDD stack).
|
|
53
54
|
|
|
@@ -157,7 +158,7 @@ The claude-runner passes `--state-dir` at serve time:
|
|
|
157
158
|
"mcpServers": {
|
|
158
159
|
"mast": {
|
|
159
160
|
"command": "mast",
|
|
160
|
-
"args": ["serve", "--state-dir", "/workspace/.kluster/.mast"],
|
|
161
|
+
"args": ["serve", "--no-watch", "--state-dir", "/workspace/.kluster/.mast"],
|
|
161
162
|
"type": "stdio"
|
|
162
163
|
}
|
|
163
164
|
}
|
|
@@ -596,8 +597,15 @@ startup
|
|
|
596
597
|
(mast_search, mast_callers verified+potential, etc.)
|
|
597
598
|
```
|
|
598
599
|
|
|
599
|
-
|
|
600
|
-
|
|
600
|
+
Unless `--no-watch` was passed, the file watcher (§11.4) starts immediately
|
|
601
|
+
after Step 3's transport opens, independent of Step 4.
|
|
602
|
+
|
|
603
|
+
The freshness probe (`mcp/freshness-probe.ts`) behind `mast_search`'s
|
|
604
|
+
`unindexed_files` signal (§9.0) is primed here too — in the same background
|
|
605
|
+
task as Step 4 and strictly AFTER it, so the measurement reflects the index the
|
|
606
|
+
reindex just produced rather than racing it. Under `--no-startup-reindex` it is
|
|
607
|
+
primed immediately: no reindex is coming, so the drift the operator chose to
|
|
608
|
+
keep is exactly what wants measuring.
|
|
601
609
|
|
|
602
610
|
**`--no-startup-reindex` refusal (M6 Part A).** The empty-during-Step-4 window
|
|
603
611
|
above is legitimate and by design — but `--no-startup-reindex` disables Step 4
|
|
@@ -632,13 +640,21 @@ reindex runs in the background.
|
|
|
632
640
|
**Fast first-task latency.** With a baked seed (§13.8), Steps 1–3 typically complete
|
|
633
641
|
in **2–4 seconds** on a cold container. Step 4 then catches up any files changed
|
|
634
642
|
since the seed was built in the background — the agent can begin useful work as
|
|
635
|
-
soon as Step 3 completes
|
|
636
|
-
|
|
643
|
+
soon as Step 3 completes.
|
|
644
|
+
|
|
645
|
+
Be precise about what covers that window, because the obvious summary is wrong.
|
|
646
|
+
JIT re-parse (§9.0) makes an individual *already-indexed* file correct on read,
|
|
647
|
+
and only on the five re-parsing tools; `mast_search` and `mast_implementors`
|
|
648
|
+
flag such a file rather than refreshing it, and a file the seed never contained
|
|
649
|
+
is invisible to all seven until Step 4 reaches it. The window is therefore
|
|
650
|
+
bounded by Step 4, not eliminated by §9.0.
|
|
637
651
|
|
|
638
652
|
This is the **only hook required for the SDD pipeline**. The BT orchestrator needs no
|
|
639
653
|
reindex calls. Files committed by the previous task are picked up by Step 4's
|
|
640
|
-
filesystem scan. JIT
|
|
641
|
-
|
|
654
|
+
filesystem scan. JIT re-parse (§9) covers files *modified* mid-task before Step 4
|
|
655
|
+
has caught up to them — on the five re-parsing tools; elsewhere they are flagged.
|
|
656
|
+
Files **created** mid-task are covered by neither and need `mast_reindex` (§11.2)
|
|
657
|
+
or the watcher (§11.4).
|
|
642
658
|
|
|
643
659
|
### 7.5 Mid-Task Reindex (`mast_reindex` MCP tool)
|
|
644
660
|
|
|
@@ -773,8 +789,9 @@ Start the MCP server over stdio.
|
|
|
773
789
|
Options:
|
|
774
790
|
--state-dir <dir> State directory
|
|
775
791
|
--no-startup-reindex Skip the startup staleness check (not recommended)
|
|
792
|
+
--no-watch Do not watch source files (batch/container use — see §11.4)
|
|
776
793
|
--watch Watch source files and incrementally reindex on change
|
|
777
|
-
(
|
|
794
|
+
(the default; accepted for compatibility)
|
|
778
795
|
```
|
|
779
796
|
|
|
780
797
|
The server runs until the parent process (Claude CLI) closes stdin.
|
|
@@ -787,10 +804,12 @@ state dir with the startup reindex left enabled (the default) is unaffected
|
|
|
787
804
|
and starts normally, as does `--no-startup-reindex` against an already-indexed
|
|
788
805
|
state dir (including one indexed over a genuinely empty file set).
|
|
789
806
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
807
|
+
Watching is **on by default** as of 2026-09-03 (§11.4); `--no-watch` opts out and
|
|
808
|
+
is what the SDD container passes (§3). `--watch` is still accepted and selects
|
|
809
|
+
the default, so existing MCP client configurations keep working unchanged. The
|
|
810
|
+
watcher is closed on stdin close, SIGTERM, and SIGINT; a watcher startup failure
|
|
811
|
+
logs a warning and the server continues without watch — which is what makes the
|
|
812
|
+
default safe: it cannot prevent the server from starting.
|
|
794
813
|
|
|
795
814
|
---
|
|
796
815
|
|
|
@@ -807,6 +826,7 @@ Options:
|
|
|
807
826
|
Output:
|
|
808
827
|
```
|
|
809
828
|
state_dir: /workspace/.kluster/.mast
|
|
829
|
+
project_root: /workspace/.kluster
|
|
810
830
|
schema_version: 1.3.0
|
|
811
831
|
last_indexed: 2026-05-13T14:22:00Z (3 minutes ago)
|
|
812
832
|
indexed_files: 142
|
|
@@ -818,10 +838,22 @@ index_fresh: true
|
|
|
818
838
|
freshness_cause: none
|
|
819
839
|
```
|
|
820
840
|
|
|
841
|
+
`stale_files` prints its split inline when non-zero — `stale_files: 47 (changed
|
|
842
|
+
0, unindexed 27, deleted 20)` — because the field names one of the three things
|
|
843
|
+
it counts (§9, `mast_status`).
|
|
844
|
+
|
|
845
|
+
`project_root` names the tree the report was measured against. It is reported
|
|
846
|
+
because `state_dir` alone does not identify it and the two are set
|
|
847
|
+
independently: a relative `--state-dir` resolves against the **path argument**,
|
|
848
|
+
not the shell's working directory, so one project's index can be read while
|
|
849
|
+
another project's files are asked about.
|
|
850
|
+
|
|
821
851
|
`freshness_cause` carries the same semantics as the `mast_status` MCP tool (§9) —
|
|
822
|
-
it prints `none` in human output when the JSON value would be `null
|
|
823
|
-
|
|
824
|
-
|
|
852
|
+
it prints `none` in human output when the JSON value would be `null`. When it is
|
|
853
|
+
`root_mismatch` the table is followed by a plain-language block naming the tree
|
|
854
|
+
that was measured and saying that reindexing will not move the numbers. On a
|
|
855
|
+
never-indexed project the state directory is not created as a side effect of
|
|
856
|
+
running `status`.
|
|
825
857
|
|
|
826
858
|
---
|
|
827
859
|
|
|
@@ -834,12 +866,31 @@ Invoke any MCP tool (§9) directly from the CLI — the read tools (`mast_search
|
|
|
834
866
|
|
|
835
867
|
```
|
|
836
868
|
Options:
|
|
837
|
-
--state-dir <dir>
|
|
869
|
+
--state-dir <dir> State directory
|
|
870
|
+
--reindex Run an incremental index (§7.1) before dispatching
|
|
838
871
|
--json Emit the exact single-line MCP response text (machine
|
|
839
872
|
use); default pretty-prints the parsed response with
|
|
840
873
|
2-space indent for humans
|
|
841
874
|
```
|
|
842
875
|
|
|
876
|
+
`--reindex` (also on `mast search`) is the CLI's freshness mechanism. The CLI
|
|
877
|
+
has neither of the server's: no watcher (§11.4) and no freshness probe
|
|
878
|
+
(§9.0 `unindexed_files`), because a one-shot process has no lifetime over
|
|
879
|
+
which to amortise a TTL-cached measurement. Semantics:
|
|
880
|
+
|
|
881
|
+
- **Incremental, never full.** `mast index --full` remains the way to rebuild.
|
|
882
|
+
- **Refreshes, does not bootstrap.** It runs strictly *after* the never-indexed
|
|
883
|
+
guard below, so a missing `graph.db` still fails fast with that guard's
|
|
884
|
+
message rather than being silently created as a side effect of the flag.
|
|
885
|
+
- **Reports on stderr**, never stdout — the refresh is a side effect of the
|
|
886
|
+
query, not part of its answer, and `--json` consumers must keep a parseable
|
|
887
|
+
stdout.
|
|
888
|
+
- **Never fatal.** The run takes `structure.lock`, and with `mast serve`
|
|
889
|
+
watching by default (§11.4) losing that race to a concurrent writer is an
|
|
890
|
+
ordinary outcome. A failed refresh warns and the query is answered from the
|
|
891
|
+
existing index; failing the query over a refresh that did not happen would
|
|
892
|
+
be a worse answer than a slightly stale one.
|
|
893
|
+
|
|
843
894
|
`json` (positional, default `'{}'`) is the tool's argument object as a JSON
|
|
844
895
|
string; `path` is the project root (same resolution as every other command's
|
|
845
896
|
`[path]`).
|
|
@@ -948,8 +999,9 @@ how many files a single call's results can span:
|
|
|
948
999
|
|
|
949
1000
|
JIT re-parse covers files already known to the index. It does not discover a
|
|
950
1001
|
brand-new file or a newly-created symbol — those become searchable via the next
|
|
951
|
-
`mast_reindex` call or the
|
|
952
|
-
them.
|
|
1002
|
+
`mast_reindex` call, the startup reindex, or the watcher (§7.4/§11.4, on by
|
|
1003
|
+
default) reaching them. `mast_search` reports the size of that blind spot as
|
|
1004
|
+
`unindexed_files` when a serve process has measured it. The agent prompt should still recommend `mast_reindex` after writing new
|
|
953
1005
|
files or symbols — not because JIT leaves existing files stale (it doesn't), but
|
|
954
1006
|
because discovery of new ones requires an actual indexing pass.
|
|
955
1007
|
|
|
@@ -1045,11 +1097,12 @@ across all MCP tools, and what to do with each one:
|
|
|
1045
1097
|
| `file_busy_returning_stale_cache` | JIT-refresh tools' results/envelopes (`mast_signature`, `mast_exports`, `mast_callers`, `mast_dependencies`, `mast_rename_impact`) | A refresh **was attempted** (this file's JIT re-parse) and lost to genuine write contention (`populateFile`'s `BEGIN IMMEDIATE` exhausted its `busy_timeout`), so the previous, possibly-stale chunk was returned instead. | Contended, not wrong-by-design. Retry shortly — the contention is expected to clear (§7.6). |
|
|
1046
1098
|
| `stale` | `mast_search` / `mast_implementors` per-result (F7) | This result's `file_path` stat'd newer-on-disk than its indexed mtime, or the stat failed — **no refresh was attempted by design** (stat-and-flag, not JIT re-parse; see above). | Treat this result's line coordinates as untrustworthy. A `mast_reindex` call, or any JIT-refreshing tool call against the file, heals it. |
|
|
1047
1099
|
| `index_empty` | Every primary-result read tool's envelope (M6) | Nothing is indexed at all — the empty result set is not "no match", it is "no index (yet)". | Run `mast init`/`mast index`, or — if a startup reindex is in progress — wait and retry. |
|
|
1100
|
+
| `unindexed_files` | Every primary-result read tool's envelope (D054). The five whose answer claims to be an exhaustive *set* — `mast_search`, `mast_callers`, `mast_implementors`, `mast_rename_impact`, `mast_project_skeleton` — carry it whether or not they found anything, because a short list drawn from an incomplete corpus reads exactly like a complete one and is the form that gets acted on. The three that answer about one *named* thing — `mast_signature`, `mast_exports`, `mast_dependencies` — carry it only on an empty answer, where "absent" and "never indexed" are indistinguishable; a hit is correct however much else is missing. | This many files exist on disk and are **not in the index** — measured by the serve process and TTL-cached; the request never awaits a measurement, though it may schedule one (amortised, not free — `mcp/freshness-probe.ts` documents the residual). Where `index_empty` says "nothing is indexed", this says "the index is populated but behind": these results were ranked over a corpus missing N files. Advisory — it does not claim any of the N would have matched. Absent means either nothing is unindexed or no measurement has landed yet; the two are deliberately not distinguished on the wire, because both mean "no warning to give". | Do not conclude a symbol is absent. Call `mast_reindex`, then re-query. |
|
|
1048
1101
|
| `truncated` | `TypeContextEntry` (`mast_signature`'s `type_context`) | This referenced type's declaration was clipped at the 50-line cap. | Re-read the file directly (or call `mast_exports`/a narrower `mast_signature` query) for the full declaration if the clipped portion matters. |
|
|
1049
1102
|
| `potential_truncated` | `CallersResponse.summary` / `RenameImpactResponse.summary` (`mast_callers`, `mast_rename_impact`) | The `identifier_fts` fetch behind `potential_matches` is capped at 50 entries; this carries the real, uncapped match count when the cap is hit (F10, Stage 3). Reports RAW fetch truncation only — `potential_matches` may still be smaller than the cap even when this field is present, because verified-overlap exclusion and checker-verdict filtering run AFTER the capped fetch (already visible via `checker_classified_*`). | The potential set is incomplete — narrow the query, or run `mast index --checker` to classify candidates away. |
|
|
1050
1103
|
| `results_truncated` / `exports_truncated` | `SignatureResponse` / `ImplementorsResponse` / `ExportsResponse` envelopes (`mast_signature`, `mast_implementors`, `mast_exports`) | The result list is capped at `limit` (default 50, the same constant `potential_matches` uses); this carries the real, uncapped total when the cap is hit (D043). Unlike `potential_truncated` there is no post-cap filtering, so the returned page is always exactly `limit` long when this field is present. | A first page, not the answer. Pass a larger `limit` (max 500), or narrow with `file_path`. Before D043 these tools were unbounded: `mast_signature{symbol:'execute'}` over a 14k-file monorepo returned 580 declarations / 331k tokens in 78 s, which over MCP exceeded the client timeout and returned nothing. |
|
|
1051
1104
|
|
|
1052
|
-
`file_busy_returning_stale_cache`, `stale`, `index_empty`,
|
|
1105
|
+
`file_busy_returning_stale_cache`, `stale`, `index_empty`, `unindexed_files`,
|
|
1053
1106
|
`potential_truncated`, `results_truncated` and `exports_truncated` all follow the same **omitted-when-false /
|
|
1054
1107
|
present-only-when-true** convention (never present-and-false) established
|
|
1055
1108
|
above — `potential_truncated`'s "false" case is "the fetch came back under
|
|
@@ -1698,6 +1751,7 @@ Index health snapshot.
|
|
|
1698
1751
|
"indexed_files": 142,
|
|
1699
1752
|
"chunk_count": 1840,
|
|
1700
1753
|
"stale_files": 0,
|
|
1754
|
+
"stale_breakdown": { "changed": 0, "unindexed": 0, "deleted": 0 },
|
|
1701
1755
|
"parse_errors": 0,
|
|
1702
1756
|
"write_errors": 0,
|
|
1703
1757
|
"index_fresh": true,
|
|
@@ -1726,14 +1780,42 @@ conflated). Non-zero in either indicates files the agent should investigate.
|
|
|
1726
1780
|
`seed_commit` is present only when the state directory was bootstrapped from a
|
|
1727
1781
|
Docker-baked seed (§13.8) and reports the git revision the seed was built from.
|
|
1728
1782
|
|
|
1729
|
-
**Freshness diagnostics.** `freshness_cause`
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1783
|
+
**Freshness diagnostics.** `freshness_cause` names which of `stale_files`'
|
|
1784
|
+
categories the count is actually made of, and `null` when the index is fully
|
|
1785
|
+
fresh. `index_fresh` is `true` only when `stale_files === 0` and the index has
|
|
1786
|
+
been run at least once.
|
|
1787
|
+
|
|
1788
|
+
| value | meaning |
|
|
1789
|
+
|---|---|
|
|
1790
|
+
| `"root_mismatch"` | The index disagrees with this tree in both directions more than it agrees in either: more files here are unknown to it than known (`unindexed > walked - unindexed`), **and** it lists more absent files than known ones (`deleted > walked - unindexed`). It was built for a different project root, so reindexing will not move the numbers — the path argument or `--state-dir` is wrong. Both halves are required: the first alone would flag a mass deletion, the second alone a never-indexed project. |
|
|
1791
|
+
| `"phase1_stale"` | Indexed files whose content changed since — chunk line coordinates lag disk, corrected by JIT re-parse on read by the five re-parsing tools (§9.0), flagged `stale` by the other two, or fixed for all of them by `mast_reindex`. |
|
|
1792
|
+
| `"unindexed_files"` | Files on disk this index has never seen. |
|
|
1793
|
+
| `"deleted_files"` | Files the index still lists that are gone from disk. |
|
|
1794
|
+
|
|
1795
|
+
The `"unindexed_files"` **cause** here and `mast_search`'s `unindexed_files`
|
|
1796
|
+
**count** (§9.0) name the same population — files on disk this index has never
|
|
1797
|
+
seen — and are deliberately spelled the same. They differ in shape and in
|
|
1798
|
+
freshness: `mast_status` reports a cause label chosen from a measurement taken
|
|
1799
|
+
during that call, while `mast_search` reports an integer from the serve
|
|
1800
|
+
process's TTL-cached probe, which may be up to one TTL old. When the two
|
|
1801
|
+
disagree, `mast_status` is the newer measurement.
|
|
1802
|
+
|
|
1803
|
+
`root_mismatch` is tested first and is the only compound condition; the other
|
|
1804
|
+
three are ordered by what the caller should do about them, `phase1_stale` first
|
|
1805
|
+
because it is the one JIT re-parse corrects silently on read. The exact split
|
|
1806
|
+
always travels beside the cause in `stale_breakdown`, so a caller never has to
|
|
1807
|
+
infer the composition from the chosen label.
|
|
1808
|
+
|
|
1809
|
+
Until 2026-09-01 this field was `"phase1_stale"` for **any** non-zero count,
|
|
1810
|
+
including counts containing no changed file at all (`docs/defects/LEDGER.md`
|
|
1811
|
+
D049); it is now decided from counted categories rather than from the total.
|
|
1733
1812
|
|
|
1734
1813
|
`stale_files` counts three things, not one: files whose content changed since they
|
|
1735
1814
|
were indexed, files on disk that are **not in the index at all**, and files the index
|
|
1736
|
-
still lists that are gone from disk.
|
|
1815
|
+
still lists that are gone from disk. `stale_breakdown` reports that split as
|
|
1816
|
+
`{changed, unindexed, deleted}` — the total alone was documented as a union here
|
|
1817
|
+
and published as a scalar by both surfaces, so a caller who had read this
|
|
1818
|
+
paragraph still could not act on the distinction it draws (D049). It is computed by `indexer/freshness.ts`
|
|
1737
1819
|
`measureFreshness`, which `mast status` and `mast_status` both call — one producer,
|
|
1738
1820
|
because they answer one question. It reads the manifest and the `files.mtime` stamps
|
|
1739
1821
|
and takes the union: only the manifest can see a file that was never indexed, and only
|
|
@@ -2238,9 +2320,11 @@ candidates out of `potential_matches` and report honest counts in
|
|
|
2238
2320
|
|
|
2239
2321
|
Defined in full in §7.4. Summary: a four-step ladder that brings the whole index
|
|
2240
2322
|
(graph + FTS) online in 2–4 seconds via a Docker-baked seed index (§13.8), with all
|
|
2241
|
-
11 tools registered and ready to serve as soon as Step 3 completes —
|
|
2242
|
-
|
|
2243
|
-
|
|
2323
|
+
11 tools registered and ready to serve as soon as Step 3 completes — no tool is
|
|
2324
|
+
unavailable or degraded during startup. One *signal* does warm up: `mast_search`'s
|
|
2325
|
+
`unindexed_files` is served from a cached probe, and until the first measurement
|
|
2326
|
+
lands the count is unknown and the field is omitted (ADR 016). Step 4 then catches
|
|
2327
|
+
up any files changed since the seed was built, in the background.
|
|
2244
2328
|
|
|
2245
2329
|
This is the **only hook required for the SDD pipeline**.
|
|
2246
2330
|
|
|
@@ -2249,7 +2333,7 @@ This is the **only hook required for the SDD pipeline**.
|
|
|
2249
2333
|
The agent calls this explicitly after writes. JIT staleness handling (§9.0) already
|
|
2250
2334
|
keeps already-indexed files correct on read; `mast_reindex` is what makes a
|
|
2251
2335
|
**brand-new** file or symbol discoverable by `mast_search`/`mast_callers`/etc. before
|
|
2252
|
-
the next scheduled or
|
|
2336
|
+
the next scheduled reindex or the watcher (§11.4, on by default) reaches it. The implement prompt instructs:
|
|
2253
2337
|
|
|
2254
2338
|
> After writing or editing files, call `mast_reindex` before any search query that
|
|
2255
2339
|
> depends on symbols you just created. This is the only way to guarantee the index
|
|
@@ -2274,19 +2358,39 @@ mast index "$(git rev-parse --show-toplevel)" --incremental
|
|
|
2274
2358
|
Not required for the automated SDD pipeline — the startup hook covers the same
|
|
2275
2359
|
scenario (files changed since last index).
|
|
2276
2360
|
|
|
2277
|
-
### 11.4
|
|
2361
|
+
### 11.4 File Watching — `mast serve` (default on, `--no-watch` to opt out)
|
|
2278
2362
|
|
|
2279
2363
|
Local interactive development has no equivalent of the container's startup
|
|
2280
2364
|
ladder: git hooks are opt-in and fire only on commit/checkout, so a long-lived
|
|
2281
2365
|
interactive session can leave newly-created files and symbols undiscoverable
|
|
2282
2366
|
between explicit `mast_reindex` calls, even though JIT re-parse (§9.0) keeps
|
|
2283
|
-
every already-indexed file correct on read.
|
|
2284
|
-
|
|
2367
|
+
every already-indexed file correct on read.
|
|
2368
|
+
|
|
2369
|
+
Watching closes that gap and is **on by default** as of 2026-09-03. It was
|
|
2370
|
+
opt-in until then, on the reasoning that JIT staleness plus the startup ladder
|
|
2371
|
+
kept reads correct. That reasoning holds only for files the index already
|
|
2372
|
+
knows: JIT re-parses a *known* file whose mtime moved, and is structurally
|
|
2373
|
+
blind to one that was never indexed. So in the default configuration a file
|
|
2374
|
+
created mid-session was invisible to every read tool for the rest of the
|
|
2375
|
+
session, and `mast_search` returned `{"results":[]}` for symbols that plainly
|
|
2376
|
+
existed on disk — the §0 failure mode, reached through a default.
|
|
2377
|
+
|
|
2378
|
+
It remains a discovery-freshness optimization rather than a correctness
|
|
2285
2379
|
mechanism, and it does not reopen the §3 no-daemon non-goal (it lives and dies
|
|
2286
|
-
with the serve process).
|
|
2380
|
+
with the serve process). `--no-watch` is for batch and container use, where the
|
|
2381
|
+
tree does not change under the server and the fd cost buys nothing.
|
|
2287
2382
|
|
|
2288
2383
|
Behaviour:
|
|
2289
2384
|
|
|
2385
|
+
- **Readiness is announced.** The watcher starts *after* the MCP transport is
|
|
2386
|
+
accepting calls, and chokidar's initial scan runs with `ignoreInitial: true`
|
|
2387
|
+
— correct, because the startup ladder has already indexed the tree, but it
|
|
2388
|
+
means a file created before that scan completes is treated as pre-existing
|
|
2389
|
+
and fires **no event at all**. When the scan finishes, `mast serve` writes
|
|
2390
|
+
`[mast] watch: watching for changes` to stderr, once. Before this existed
|
|
2391
|
+
(D061) silence covered three different states — watching, not watching yet,
|
|
2392
|
+
and watcher failed to start — because only the failure paths wrote anything.
|
|
2393
|
+
A tool or operator that must not miss a change waits for this line.
|
|
2290
2394
|
- A chokidar watcher covers `file_extensions` under the project root,
|
|
2291
2395
|
respecting `exclude_patterns` **and the state directory itself** — watching
|
|
2292
2396
|
the state dir would self-trigger on every index write.
|
|
@@ -2380,7 +2484,7 @@ TypeScript (Node.js LTS). Rationale:
|
|
|
2380
2484
|
| Locking | `proper-lockfile` | PID-based advisory lock; set `stale: 10000` (10s) to handle abrupt container exits |
|
|
2381
2485
|
| CLI | `commander` | Standard TS CLI |
|
|
2382
2486
|
| File walking | `fast-glob` | Glob pattern support for `exclude_patterns` |
|
|
2383
|
-
| File watching | `chokidar` | Powers `mast serve
|
|
2487
|
+
| File watching | `chokidar` | Powers `mast serve`'s watcher (§11.4, default on; `--no-watch` opts out) |
|
|
2384
2488
|
| Token counting | `@anthropic-ai/tokenizer` | Counts `tokens_returned`/`tokens_full_file_upper_bound` for `_stats` (§14.5) |
|
|
2385
2489
|
| Identifiers | `uuid` | Per-`mast serve`-session `session_id` for metrics attribution |
|
|
2386
2490
|
|
|
@@ -2417,7 +2521,7 @@ packages/mast/
|
|
|
2417
2521
|
│ ├── indexer/
|
|
2418
2522
|
│ │ ├── index.ts # orchestrates the single indexing pass (§7.1)
|
|
2419
2523
|
│ │ ├── walker.ts # file discovery, exclude pattern matching, manifest diff
|
|
2420
|
-
│ │ ├── watcher.ts # chokidar-backed `mast serve
|
|
2524
|
+
│ │ ├── watcher.ts # chokidar-backed `mast serve` watcher (§11.4)
|
|
2421
2525
|
│ │ └── import-resolver.ts # tsconfig paths + pnpm workspace resolution (§13.7)
|
|
2422
2526
|
│ ├── graph/
|
|
2423
2527
|
│ │ ├── db.ts # better-sqlite3 + Kysely connection, schema init
|
|
@@ -2515,6 +2619,23 @@ scope — MAST indexes implementation files. Without this rule, ESM `.js` specif
|
|
|
2515
2619
|
`re_export_files` rows. See the TypeScript Modules Reference, "File extension
|
|
2516
2620
|
substitution".
|
|
2517
2621
|
|
|
2622
|
+
**0a. Trailing-slash specifiers are directory-only**
|
|
2623
|
+
|
|
2624
|
+
A specifier written with a trailing slash (`./routes/`) names a **directory** and
|
|
2625
|
+
nothing else. Node refuses it outright when only a sibling file exists —
|
|
2626
|
+
`require.resolve('./routes/')` throws `MODULE_NOT_FOUND` — so the resolver skips
|
|
2627
|
+
every file probe for such a specifier and considers only `<base>/index.<ext>`,
|
|
2628
|
+
returning `null` rather than inventing an edge the runtime does not have.
|
|
2629
|
+
|
|
2630
|
+
This is a rule about the *specifier*, not the path: `path.resolve`/`join`
|
|
2631
|
+
normalise the slash away, so the intent must be read before the base path is
|
|
2632
|
+
built. Without it the extension probes ran first and a sibling file won over the
|
|
2633
|
+
directory the author explicitly asked for — a `Routes.ts` importing `./routes/`
|
|
2634
|
+
resolved to **itself**, and `routes/index.ts`, which had a live importer, was
|
|
2635
|
+
recorded with none (`docs/defects/LEDGER.md` D047). Note the ordering is
|
|
2636
|
+
deliberate only for the slash-terminated form: without a slash, `./routes` stays
|
|
2637
|
+
file-first, which is what tsc does.
|
|
2638
|
+
|
|
2518
2639
|
**1. tsconfig `paths` aliases** (e.g. `@api/types` → `./src/types/index.ts`)
|
|
2519
2640
|
|
|
2520
2641
|
Read the nearest `tsconfig.json` at `mast init` time using `tsconfig-paths`. Build a
|
|
@@ -2570,11 +2691,15 @@ Two important properties of the seed:
|
|
|
2570
2691
|
1. **The index runs fully at build time.** The seed contains a fully-populated
|
|
2571
2692
|
`graph.db` (chunks, symbols, edges, `chunk_fts`, `identifier_fts`). The runtime
|
|
2572
2693
|
container is ready to serve at full capability immediately (Step 3 of §7.4) — no
|
|
2573
|
-
|
|
2694
|
+
tool is degraded while it starts. The one exception is `mast_search`'s
|
|
2695
|
+
`unindexed_files` count, which is omitted until the freshness probe's first
|
|
2696
|
+
measurement lands (ADR 016); it reads as unknown, never as zero.
|
|
2574
2697
|
2. **Frozen at build commit.** The seed reflects whatever code was in the image at
|
|
2575
2698
|
`docker build` time. Files modified since the build commit are picked up by
|
|
2576
2699
|
§7.4 Step 4's filesystem scan (a few seconds for typical incremental staleness),
|
|
2577
|
-
and uncommitted-tree edits
|
|
2700
|
+
and uncommitted-tree edits to files the seed already contains are re-parsed on
|
|
2701
|
+
first query by the five re-parsing tools, or flagged `stale` by the other two
|
|
2702
|
+
(§9.0). An uncommitted **new** file is in neither category until Step 4 runs.
|
|
2578
2703
|
|
|
2579
2704
|
**Runtime copy.** The container entrypoint runs:
|
|
2580
2705
|
|
|
@@ -2583,7 +2708,7 @@ Two important properties of the seed:
|
|
|
2583
2708
|
if [ -z "$(ls -A /workspace/.kluster/.mast 2>/dev/null)" ]; then
|
|
2584
2709
|
cp -r /opt/mast-seed/. /workspace/.kluster/.mast/
|
|
2585
2710
|
fi
|
|
2586
|
-
exec mast serve --state-dir /workspace/.kluster/.mast
|
|
2711
|
+
exec mast serve --no-watch --state-dir /workspace/.kluster/.mast
|
|
2587
2712
|
```
|
|
2588
2713
|
|
|
2589
2714
|
The copy is conditional on the workspace state being empty. Subsequent container
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ MAST takes a different approach:
|
|
|
33
33
|
- **AST-level chunking** — every function, class, interface, and type alias is its own chunk. The assistant gets the exact declaration it needs, not the file it happens to live in.
|
|
34
34
|
- **Ranked search** — BM25 (FTS5) handles keyword and identifier queries; a declaration-exact ranker ("ranker D") catches exact-symbol-name queries that BM25's trigram tokenizer can rank inconsistently. Both are fused via Reciprocal Rank Fusion so a chunk that both rankers agree on outranks one that only one of them found.
|
|
35
35
|
- **Structural queries** — "who calls this function?", "what implements this interface?", "what does this file import?" are answered from a pre-built symbol graph, not by grepping source. Answers are instantaneous and structurally correct.
|
|
36
|
-
- **
|
|
36
|
+
- **Staleness handling that says what it actually does** — five read tools (`mast_signature`, `mast_callers`, `mast_exports`, `mast_dependencies`, `mast_rename_impact`) re-parse a changed file inline before answering; `mast_search` and `mast_implementors` flag affected results with `stale: true` rather than re-parsing. Both only cover files the index *already knows*: a brand-new file is invisible until a reindex, which is why `mast serve` watches by default and why `mast_search` carries an `unindexed_files` warning when it does not.
|
|
37
37
|
- **Token accounting** — every tool response includes `_stats` with the token count returned and the counterfactual "what would a naive full-file read have cost?", giving a concrete measure of efficiency over time.
|
|
38
38
|
|
|
39
39
|
---
|
|
@@ -240,8 +240,8 @@ absolute. The CLI and editor integrations below infer it from the working direct
|
|
|
240
240
|
|
|
241
241
|
### Any other MCP client
|
|
242
242
|
|
|
243
|
-
Run `mast serve` over stdio from the project root. It advertises eleven
|
|
244
|
-
needs no arguments beyond `serve`.
|
|
243
|
+
Run `mast serve` over stdio from the project root. It advertises eleven tools — ten that
|
|
244
|
+
read and `mast_reindex`, which writes — and needs no arguments beyond `serve`.
|
|
245
245
|
|
|
246
246
|
### Tell the assistant how to use it
|
|
247
247
|
|
|
@@ -289,7 +289,10 @@ tool rather than one index per package.
|
|
|
289
289
|
|
|
290
290
|
**What is indexed.** `.ts`, `.tsx`, `.js`, `.jsx`, and `.md`, minus `node_modules`,
|
|
291
291
|
`dist`, `build`, `coverage`, `.next`, `.turbo`, `.mast`, and test files. Override with
|
|
292
|
-
`--extensions` and `--exclude` on `mast init`, or
|
|
292
|
+
`--extensions` and `--exclude` on `mast init`, or — for a setting the whole team should
|
|
293
|
+
get — `file_extensions` / `exclude_patterns` in `mast.config.json` at the project root.
|
|
294
|
+
Editing `.mast/config.json` also works and is read back, but that file is gitignored and
|
|
295
|
+
per-machine, so the change will not travel; `mast.config.json` outranks it.
|
|
293
296
|
|
|
294
297
|
**Other languages are not indexed, and this matters.** MAST parses TypeScript and
|
|
295
298
|
JavaScript only. A symbol defined in Python, Go, Java, or Rust is absent from the index,
|
|
@@ -297,7 +300,16 @@ which looks exactly like absent from the repository. Treat an empty result as "M
|
|
|
297
300
|
not find it", never as "it does not exist" — `mast skill` says this to the model too.
|
|
298
301
|
|
|
299
302
|
**Add `.mast/` to `.gitignore`.** It is derived state, it is large, and it is
|
|
300
|
-
machine-specific.
|
|
303
|
+
machine-specific — on a 14k-file monorepo it is around 420 MB, almost all of it `graph.db`.
|
|
304
|
+
Ignore the whole directory, including `.mast/config.json`: that file is a *resolved*
|
|
305
|
+
snapshot and carries absolute paths (`project_root`, `resolved_state_dir`) that mean
|
|
306
|
+
nothing on anyone else's machine. The file meant to be committed is `mast.config.json` at
|
|
307
|
+
the project root — see the next paragraph.
|
|
308
|
+
|
|
309
|
+
**If you move the index, move the ignore rule with it.** `.mast/` is the default location,
|
|
310
|
+
not the only one: a `state_dir` in `mast.config.json`, a `MAST_STATE_DIR` in the
|
|
311
|
+
environment, or a `--state-dir` flag all put the index somewhere else, and a `.gitignore`
|
|
312
|
+
naming `.mast/` then protects nothing.
|
|
301
313
|
|
|
302
314
|
**A custom index location is not remembered between runs.** `--state-dir` applies to the
|
|
303
315
|
one command you pass it to. Path settings are deliberately never read back out of a
|
|
@@ -351,9 +363,19 @@ Options:
|
|
|
351
363
|
-e, --exported Only exported symbols
|
|
352
364
|
-f, --file <glob> Restrict to files matching a glob
|
|
353
365
|
--state-dir <dir> State directory
|
|
366
|
+
--reindex Incrementally reindex before searching
|
|
354
367
|
--json Emit the raw MCP response instead of text
|
|
355
368
|
```
|
|
356
369
|
|
|
370
|
+
`--reindex` exists because the CLI has neither freshness mechanism the MCP server has:
|
|
371
|
+
no file watcher, and no cached freshness probe (a one-shot process has no lifetime to
|
|
372
|
+
amortise one over). Without it, a file created since the last index run is invisible —
|
|
373
|
+
JIT staleness only re-parses files the index already knows. It is opt-in because it adds
|
|
374
|
+
a whole incremental index run to what is otherwise a single query, and it reports what it
|
|
375
|
+
did on **stderr**, so `--json` consumers keep a parseable stdout. A reindex that loses
|
|
376
|
+
`structure.lock` to a concurrent writer — likelier now that `mast serve` watches by
|
|
377
|
+
default — warns and queries the existing index rather than failing the search.
|
|
378
|
+
|
|
357
379
|
**Why:** the fastest way to check what the index actually contains, and the same code path
|
|
358
380
|
the MCP `mast_search` tool uses — it dispatches through the registered handler rather than
|
|
359
381
|
re-implementing ranking, so CLI and assistant results cannot disagree. Staleness and
|
|
@@ -390,10 +412,16 @@ Start the MCP server over stdio.
|
|
|
390
412
|
Options:
|
|
391
413
|
--state-dir <dir> State directory
|
|
392
414
|
--no-startup-reindex Skip the startup staleness check (not recommended)
|
|
415
|
+
--no-watch Do not watch source files (batch/container use)
|
|
393
416
|
--watch Watch source files and incrementally reindex on change
|
|
394
|
-
(
|
|
417
|
+
(the default; accepted for compatibility)
|
|
395
418
|
```
|
|
396
419
|
|
|
420
|
+
Watching is **on by default**. JIT staleness only re-parses files the index already knows,
|
|
421
|
+
so without a watcher a file created during a session stays invisible to every read tool
|
|
422
|
+
until something reindexes — and nothing does. A watcher failure (EMFILE, permissions) logs
|
|
423
|
+
a warning and the server keeps serving, so the default cannot stop `serve` from starting.
|
|
424
|
+
|
|
397
425
|
The server implements a four-step startup ladder so MCP clients get a usable server in under a second even for large projects. See [Startup Ladder](#startup-ladder) for details.
|
|
398
426
|
|
|
399
427
|
---
|
|
@@ -442,6 +470,7 @@ Invoke any MCP read tool directly, with byte-identical output to the MCP transpo
|
|
|
442
470
|
```
|
|
443
471
|
Options:
|
|
444
472
|
--state-dir <dir> State directory
|
|
473
|
+
--reindex Incrementally reindex before querying (see `mast search --reindex`)
|
|
445
474
|
--json Emit the exact single-line MCP response (default pretty-prints)
|
|
446
475
|
```
|
|
447
476
|
|
|
@@ -512,6 +541,31 @@ MAST registers 11 tools with the MCP server. Every read tool includes a `_stats`
|
|
|
512
541
|
}
|
|
513
542
|
```
|
|
514
543
|
|
|
544
|
+
### The signals
|
|
545
|
+
|
|
546
|
+
Beyond the results, a response carries fields describing what MAST **does not know** about
|
|
547
|
+
the answer it just gave. All but the last are omitted entirely when they do not apply, so
|
|
548
|
+
their absence carries meaning and their presence is never noise. `truncated` is the one
|
|
549
|
+
exception — it is a required field on every `type_context` entry and is present-and-`false`
|
|
550
|
+
in the ordinary case, so read it, don't test for it.
|
|
551
|
+
|
|
552
|
+
| signal | carried by | means |
|
|
553
|
+
|---|---|---|
|
|
554
|
+
| `stale` | per result of `mast_search`, `mast_implementors` | this result's file changed on disk since it was indexed. The content and line numbers shown may be out of date; no re-parse was attempted (see [JIT Staleness Checks](#jit-staleness-checks)) |
|
|
555
|
+
| `file_busy_returning_stale_cache` | `mast_signature`, `mast_callers`, `mast_exports`, `mast_dependencies`, `mast_rename_impact` | a re-parse *was* attempted and lost to a concurrent writer, so the previous chunk was returned. Contended, not wrong by design — retry shortly |
|
|
556
|
+
| `index_empty` | the empty answer of any of the eight tools that return a result set | nothing is indexed at all. The answer is empty because there was nothing to answer from, not because nothing matched |
|
|
557
|
+
| `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) | this many files exist on disk and are not in the index. The answer was computed over an incomplete corpus — an empty or thin result may mean "not indexed" rather than "not present". The tools that claim an exhaustive set report it even when they found something, because a short list reads exactly like a complete one; the three that answer about one named thing report it only when that thing was not found |
|
|
558
|
+
| `results_truncated` | `mast_signature`, `mast_implementors` | the list was capped at `limit`; the field carries the real, uncapped total |
|
|
559
|
+
| `exports_truncated` | `mast_exports` | the same, for a module's export list |
|
|
560
|
+
| `potential_truncated` | `mast_callers`, `mast_rename_impact` | the unresolved-candidate fetch was capped at 50; the field carries the real match count |
|
|
561
|
+
| `truncated` | a `type_context` entry of `mast_signature` | that referenced type's declaration was clipped at 50 lines. The one signal that *is* always present, as a boolean |
|
|
562
|
+
|
|
563
|
+
Two distinctions that are not flags but carry the same weight: in `mast_callers` and
|
|
564
|
+
`mast_rename_impact`, a **`verified_caller`** carries a `resolution` naming how the edge was
|
|
565
|
+
statically proven and is safe to act on, while a **`potential_match`** carries a `reason` and
|
|
566
|
+
is a name match with no proven edge — review before editing. And an empty result is never
|
|
567
|
+
proof of absence: MAST indexes TypeScript, JavaScript, and Markdown only.
|
|
568
|
+
|
|
515
569
|
---
|
|
516
570
|
|
|
517
571
|
### `mast_search`
|
|
@@ -529,7 +583,7 @@ Lexical BM25 + declaration-exact search over the indexed codebase.
|
|
|
529
583
|
}
|
|
530
584
|
```
|
|
531
585
|
|
|
532
|
-
**Returns:** `{ results[], suggestions?, _stats }`. Each result includes `file_path`, `start_line`, `end_line`, `content`, `chunk_type`, `symbol_name`, `parent_symbol`, `is_exported`, `match_score` (BM25 score, negative; `null` when the hit came only from ranker D), `rank`, `match_snippet`, and an optional `related` hint when a method and its class shell both matched (only the higher-ranked one is returned). `suggestions` is present, possibly empty, only when `results` is empty — a zero-result "did you mean" assist.
|
|
586
|
+
**Returns:** `{ results[], suggestions?, index_empty?, unindexed_files?, _stats }`. Each result includes `file_path`, `start_line`, `end_line`, `content`, `chunk_type`, `symbol_name`, `parent_symbol`, `is_exported`, `match_score` (BM25 score, negative; `null` when the hit came only from ranker D), `rank`, `match_snippet`, an optional `stale` flag, and an optional `related` hint when a method and its class shell both matched (only the higher-ranked one is returned). `suggestions` is present, possibly empty, only when `results` is empty — a zero-result "did you mean" assist. See [The signals](#the-signals) for `stale`, `index_empty`, and `unindexed_files`.
|
|
533
587
|
|
|
534
588
|
**Why:** `grep` and `glob` find exact strings and require the caller to already know the pattern. `mast_search` ranks by relevance across two signals fused with Reciprocal Rank Fusion:
|
|
535
589
|
|
|
@@ -776,13 +830,34 @@ with default `k = 60`. A chunk appearing at rank 1 in both lists scores twice as
|
|
|
776
830
|
|
|
777
831
|
### JIT Staleness Checks
|
|
778
832
|
|
|
779
|
-
|
|
833
|
+
Read tools handle a file that changed since it was indexed in one of **two** ways. Which one a tool
|
|
834
|
+
uses is fixed per tool, not decided at runtime:
|
|
835
|
+
|
|
836
|
+
**Re-parse inline** — `mast_signature`, `mast_callers`, `mast_exports`, `mast_dependencies`,
|
|
837
|
+
`mast_rename_impact`. Before returning, each calls `jitRefreshFile`, which:
|
|
780
838
|
|
|
781
|
-
1. Reads the stored mtime for the file from the `files` table.
|
|
839
|
+
1. Reads the stored mtime for the file from the `files` table. **If there is no row, it returns
|
|
840
|
+
immediately** — an unindexed file has no stored mtime to compare against, which is the whole of
|
|
841
|
+
why JIT cannot discover new files.
|
|
782
842
|
2. Calls `stat()` on the file on disk.
|
|
783
|
-
3. If the disk mtime is newer, acquires the `structure.lock` and re-parses the file immediately
|
|
843
|
+
3. If the disk mtime is newer, acquires the `structure.lock` and re-parses the file immediately,
|
|
844
|
+
on the request path — the answer waits for it.
|
|
845
|
+
|
|
846
|
+
If the lock is held by a concurrent writer the previous chunk is returned with
|
|
847
|
+
`file_busy_returning_stale_cache` set, so the caller is told rather than quietly served stale data.
|
|
848
|
+
|
|
849
|
+
**Stat and flag** — `mast_search`, `mast_implementors`. These call `findStaleFiles`, which stats the
|
|
850
|
+
files behind the results and sets `stale: true` on the affected ones. **No re-parse is attempted**:
|
|
851
|
+
taking a write lock on a ranked result set would serialise the cheapest tools in the package behind
|
|
852
|
+
the most expensive operation in it. The line coordinates on a flagged result may be off; the caller
|
|
853
|
+
is expected to re-read or call a re-parsing tool.
|
|
854
|
+
|
|
855
|
+
**Neither** — `mast_project_skeleton` (documented exempt) and `mast_efficiency`.
|
|
784
856
|
|
|
785
|
-
|
|
857
|
+
Both mechanisms only ever touch files the index **already knows**. Neither can discover a file that
|
|
858
|
+
was never indexed, so a file created during a session is invisible to every read tool until
|
|
859
|
+
something reindexes it — which is why `mast serve` watches by default, and why every
|
|
860
|
+
primary-result tool reports `unindexed_files` when the index is nonetheless behind.
|
|
786
861
|
|
|
787
862
|
### Startup Ladder
|
|
788
863
|
|