@spikedpunch/mast 0.2.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.
Files changed (150) hide show
  1. package/MAST_SPEC.md +169 -44
  2. package/README.md +266 -20
  3. package/assets/prime.md +18 -0
  4. package/assets/signals.md +51 -0
  5. package/assets/skill.md +13 -15
  6. package/dist/ast/types.d.ts +137 -1
  7. package/dist/ast/types.d.ts.map +1 -1
  8. package/dist/cli/docs-cmd.d.ts +1 -0
  9. package/dist/cli/docs-cmd.d.ts.map +1 -1
  10. package/dist/cli/docs-cmd.js +2 -1
  11. package/dist/cli/docs-cmd.js.map +1 -1
  12. package/dist/cli/hook-cmd.d.ts +3 -0
  13. package/dist/cli/hook-cmd.d.ts.map +1 -0
  14. package/dist/cli/hook-cmd.js +13 -0
  15. package/dist/cli/hook-cmd.js.map +1 -0
  16. package/dist/cli/hook-state-dir.d.ts +24 -0
  17. package/dist/cli/hook-state-dir.d.ts.map +1 -0
  18. package/dist/cli/hook-state-dir.js +57 -0
  19. package/dist/cli/hook-state-dir.js.map +1 -0
  20. package/dist/cli/hook.d.ts +44 -0
  21. package/dist/cli/hook.d.ts.map +1 -0
  22. package/dist/cli/hook.js +200 -0
  23. package/dist/cli/hook.js.map +1 -0
  24. package/dist/cli/index.js +12 -2
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/installed-artifacts.d.ts +23 -0
  27. package/dist/cli/installed-artifacts.d.ts.map +1 -0
  28. package/dist/cli/installed-artifacts.js +39 -0
  29. package/dist/cli/installed-artifacts.js.map +1 -0
  30. package/dist/cli/prime-cmd.d.ts +17 -0
  31. package/dist/cli/prime-cmd.d.ts.map +1 -0
  32. package/dist/cli/prime-cmd.js +70 -0
  33. package/dist/cli/prime-cmd.js.map +1 -0
  34. package/dist/cli/program.d.ts.map +1 -1
  35. package/dist/cli/program.js +6 -0
  36. package/dist/cli/program.js.map +1 -1
  37. package/dist/cli/query.d.ts +40 -1
  38. package/dist/cli/query.d.ts.map +1 -1
  39. package/dist/cli/query.js +36 -2
  40. package/dist/cli/query.js.map +1 -1
  41. package/dist/cli/search-cmd.d.ts +6 -1
  42. package/dist/cli/search-cmd.d.ts.map +1 -1
  43. package/dist/cli/search-cmd.js +9 -2
  44. package/dist/cli/search-cmd.js.map +1 -1
  45. package/dist/cli/serve.d.ts +6 -1
  46. package/dist/cli/serve.d.ts.map +1 -1
  47. package/dist/cli/serve.js +14 -4
  48. package/dist/cli/serve.js.map +1 -1
  49. package/dist/cli/setup-cmd.d.ts +34 -0
  50. package/dist/cli/setup-cmd.d.ts.map +1 -0
  51. package/dist/cli/setup-cmd.js +205 -0
  52. package/dist/cli/setup-cmd.js.map +1 -0
  53. package/dist/cli/setup-command.d.ts +32 -0
  54. package/dist/cli/setup-command.d.ts.map +1 -0
  55. package/dist/cli/setup-command.js +43 -0
  56. package/dist/cli/setup-command.js.map +1 -0
  57. package/dist/cli/setup-plan.d.ts +52 -0
  58. package/dist/cli/setup-plan.d.ts.map +1 -0
  59. package/dist/cli/setup-plan.js +221 -0
  60. package/dist/cli/setup-plan.js.map +1 -0
  61. package/dist/cli/setup-rules.d.ts +41 -0
  62. package/dist/cli/setup-rules.d.ts.map +1 -0
  63. package/dist/cli/setup-rules.js +76 -0
  64. package/dist/cli/setup-rules.js.map +1 -0
  65. package/dist/cli/setup-static.d.ts +13 -0
  66. package/dist/cli/setup-static.d.ts.map +1 -0
  67. package/dist/cli/setup-static.js +116 -0
  68. package/dist/cli/setup-static.js.map +1 -0
  69. package/dist/cli/skill-install.d.ts +2 -0
  70. package/dist/cli/skill-install.d.ts.map +1 -1
  71. package/dist/cli/skill-install.js +2 -0
  72. package/dist/cli/skill-install.js.map +1 -1
  73. package/dist/cli/status.d.ts +15 -0
  74. package/dist/cli/status.d.ts.map +1 -1
  75. package/dist/cli/status.js +32 -6
  76. package/dist/cli/status.js.map +1 -1
  77. package/dist/cli/upgrade-cmd.d.ts +5 -1
  78. package/dist/cli/upgrade-cmd.d.ts.map +1 -1
  79. package/dist/cli/upgrade-cmd.js +28 -3
  80. package/dist/cli/upgrade-cmd.js.map +1 -1
  81. package/dist/indexer/freshness.d.ts +10 -0
  82. package/dist/indexer/freshness.d.ts.map +1 -1
  83. package/dist/indexer/freshness.js +7 -1
  84. package/dist/indexer/freshness.js.map +1 -1
  85. package/dist/indexer/import-resolver.js +28 -16
  86. package/dist/indexer/import-resolver.js.map +1 -1
  87. package/dist/indexer/index.d.ts +38 -3
  88. package/dist/indexer/index.d.ts.map +1 -1
  89. package/dist/indexer/index.js +47 -4
  90. package/dist/indexer/index.js.map +1 -1
  91. package/dist/indexer/watcher.d.ts +94 -0
  92. package/dist/indexer/watcher.d.ts.map +1 -1
  93. package/dist/indexer/watcher.js +172 -19
  94. package/dist/indexer/watcher.js.map +1 -1
  95. package/dist/mcp/context.d.ts +16 -0
  96. package/dist/mcp/context.d.ts.map +1 -1
  97. package/dist/mcp/freshness-probe.d.ts +82 -0
  98. package/dist/mcp/freshness-probe.d.ts.map +1 -0
  99. package/dist/mcp/freshness-probe.js +111 -0
  100. package/dist/mcp/freshness-probe.js.map +1 -0
  101. package/dist/mcp/instructions.d.ts +7 -0
  102. package/dist/mcp/instructions.d.ts.map +1 -0
  103. package/dist/mcp/instructions.js +10 -0
  104. package/dist/mcp/instructions.js.map +1 -0
  105. package/dist/mcp/server.d.ts +54 -3
  106. package/dist/mcp/server.d.ts.map +1 -1
  107. package/dist/mcp/server.js +79 -4
  108. package/dist/mcp/server.js.map +1 -1
  109. package/dist/mcp/tools/_helpers.d.ts +40 -0
  110. package/dist/mcp/tools/_helpers.d.ts.map +1 -1
  111. package/dist/mcp/tools/_helpers.js +27 -0
  112. package/dist/mcp/tools/_helpers.js.map +1 -1
  113. package/dist/mcp/tools/callers.d.ts.map +1 -1
  114. package/dist/mcp/tools/callers.js +6 -1
  115. package/dist/mcp/tools/callers.js.map +1 -1
  116. package/dist/mcp/tools/dependencies.d.ts.map +1 -1
  117. package/dist/mcp/tools/dependencies.js +2 -1
  118. package/dist/mcp/tools/dependencies.js.map +1 -1
  119. package/dist/mcp/tools/exports.d.ts.map +1 -1
  120. package/dist/mcp/tools/exports.js +2 -1
  121. package/dist/mcp/tools/exports.js.map +1 -1
  122. package/dist/mcp/tools/implementors.d.ts.map +1 -1
  123. package/dist/mcp/tools/implementors.js +2 -1
  124. package/dist/mcp/tools/implementors.js.map +1 -1
  125. package/dist/mcp/tools/project-skeleton.d.ts.map +1 -1
  126. package/dist/mcp/tools/project-skeleton.js +2 -1
  127. package/dist/mcp/tools/project-skeleton.js.map +1 -1
  128. package/dist/mcp/tools/reindex.d.ts.map +1 -1
  129. package/dist/mcp/tools/reindex.js +3 -0
  130. package/dist/mcp/tools/reindex.js.map +1 -1
  131. package/dist/mcp/tools/rename-impact.d.ts.map +1 -1
  132. package/dist/mcp/tools/rename-impact.js +4 -1
  133. package/dist/mcp/tools/rename-impact.js.map +1 -1
  134. package/dist/mcp/tools/search.d.ts.map +1 -1
  135. package/dist/mcp/tools/search.js +13 -2
  136. package/dist/mcp/tools/search.js.map +1 -1
  137. package/dist/mcp/tools/signature.d.ts.map +1 -1
  138. package/dist/mcp/tools/signature.js +2 -1
  139. package/dist/mcp/tools/signature.js.map +1 -1
  140. package/dist/mcp/tools/status.d.ts.map +1 -1
  141. package/dist/mcp/tools/status.js +8 -2
  142. package/dist/mcp/tools/status.js.map +1 -1
  143. package/dist/store/config.d.ts.map +1 -1
  144. package/dist/store/config.js +4 -5
  145. package/dist/store/config.js.map +1 -1
  146. package/dist/store/defaults.d.ts +9 -0
  147. package/dist/store/defaults.d.ts.map +1 -0
  148. package/dist/store/defaults.js +12 -0
  149. package/dist/store/defaults.js.map +1 -0
  150. package/package.json +5 -2
@@ -60,6 +60,62 @@ export interface WatchPathFilter {
60
60
  * set of files a manual `mast index` run would.
61
61
  */
62
62
  export declare function shouldWatchPath(filter: WatchPathFilter, absPath: string): boolean;
63
+ /** One directory entry, reduced to what reconciliation needs. */
64
+ export interface ListedEntry {
65
+ readonly name: string;
66
+ readonly isDirectory: boolean;
67
+ }
68
+ export interface UnwatchedEntries {
69
+ /** Every unknown file, including those inside unknown directories. */
70
+ readonly files: readonly string[];
71
+ /** Every unknown directory, at any depth. */
72
+ readonly directories: readonly string[];
73
+ /**
74
+ * The unknown entries whose parent the watcher already knows: handing these to
75
+ * the watcher is enough, because it descends into a directory by itself.
76
+ */
77
+ readonly roots: readonly string[];
78
+ /** Directories that could not be listed (vanished, EACCES); the rest was still scanned. */
79
+ readonly failures: ReadonlyArray<{
80
+ readonly directory: string;
81
+ readonly error: unknown;
82
+ }>;
83
+ }
84
+ export interface FindUnwatchedInput {
85
+ /** chokidar's `getWatched()`: absolute directory -> names of its children, files and directories alike. */
86
+ readonly watched: Readonly<Record<string, readonly string[]>>;
87
+ readonly projectRoot: string;
88
+ readonly listDirectory: (directory: string) => Promise<readonly ListedEntry[]>;
89
+ /** The same predicate the watcher was constructed with, applied to files and directories. */
90
+ readonly isIgnored: (absPath: string) => boolean;
91
+ /** Checked between directories, so a closed watcher stops walking a large tree. */
92
+ readonly signal?: AbortSignal;
93
+ }
94
+ /**
95
+ * Finds what is on disk but unknown to the watcher (D067). Pure over its inputs:
96
+ * `watched` is a snapshot and `listDirectory` is the only I/O.
97
+ *
98
+ * Only directories at or under `projectRoot` are listed: chokidar records the root
99
+ * itself as a child of the root's parent, and that parent is not ours to scan.
100
+ * A file the watcher learns about between the snapshot and the listing is reported
101
+ * too; that is a redundant notification, which the incremental reindex absorbs.
102
+ * Symbolic links are listed as files and never followed, so a link cycle cannot loop.
103
+ */
104
+ export declare function findUnwatchedEntries(input: FindUnwatchedInput): Promise<UnwatchedEntries>;
105
+ /** The members of chokidar's `FSWatcher` that `startWatchMode` uses. */
106
+ export interface FsWatcher {
107
+ onFileEvent(event: 'add' | 'change' | 'unlink', listener: (path: string) => void): void;
108
+ onReady(listener: () => void): void;
109
+ onError(listener: (err: unknown) => void): void;
110
+ getWatched(): Record<string, string[]>;
111
+ add(paths: readonly string[]): unknown;
112
+ close(): Promise<void>;
113
+ }
114
+ export interface FsWatcherOptions {
115
+ readonly ignoreInitial: true;
116
+ readonly ignored: (path: string) => boolean;
117
+ }
118
+ export type FsWatcherFactory = (root: string, options: FsWatcherOptions) => FsWatcher;
63
119
  export interface WatchHandle {
64
120
  close(): Promise<void>;
65
121
  }
@@ -68,8 +124,46 @@ export interface StartWatchModeOptions {
68
124
  /** One incremental Phase 1 + embed run; supplied by the serve lifecycle. */
69
125
  readonly runBatch: (paths: readonly string[]) => Promise<void>;
70
126
  readonly onWarn: (message: string) => void;
127
+ /**
128
+ * Called once, after chokidar's initial scan, a settle period of `settleMs`, and
129
+ * one reconciliation pass over the directories chokidar is watching (D067).
130
+ * It means: every file that existed when the pass ran has been accounted for
131
+ * (queued for reindex if chokidar had not seen it), and the OS watch has had
132
+ * `settleMs` to go live. It is never called after `close()`.
133
+ *
134
+ * What it does not mean: chokidar's own `ready` fires before the OS watch is
135
+ * delivering, and a file created in that gap produces no event at all. The
136
+ * reconciliation pass recovers files created up to the moment it ran; a watch
137
+ * that goes live later than `settleMs` after `ready` is still a gap, and nothing
138
+ * here proves liveness. Each directory has its own OS watch, so a sentinel file
139
+ * would prove one directory only.
140
+ *
141
+ * Before D067 this fired straight from chokidar's `ready`, and under CPU load 12
142
+ * of 550 files written immediately after it were lost, against 0 of 400 written
143
+ * 300 ms after it.
144
+ *
145
+ * The watcher starts AFTER `serve` accepts MCP connections, and it is
146
+ * constructed with `ignoreInitial: true` — correct, since the startup ladder
147
+ * has already indexed the tree, but it means a file created before the scan
148
+ * finishes is treated as pre-existing and fires no event at all. Without this
149
+ * callback nothing outside the process could tell that window apart from
150
+ * "watching, nothing has changed" or from "the watcher failed to start": the
151
+ * EMFILE path warns, and the success path was silent, so silence meant three
152
+ * different things (D061).
153
+ *
154
+ * Optional. A caller that does not want the signal still gets a watcher.
155
+ */
156
+ readonly onReady?: () => void;
71
157
  /** Override for tests; production default 500ms. */
72
158
  readonly debounceMs?: number;
159
+ /**
160
+ * Wait between chokidar's `ready` and the reconciliation pass; production
161
+ * default 1000 ms. Measured under load (D067): 0 of 400 files lost when written
162
+ * 300 ms after `ready`; 1000 ms is a margin over that, not a measured bound.
163
+ */
164
+ readonly settleMs?: number;
165
+ /** Test seam; production uses chokidar. */
166
+ readonly watcherFactory?: FsWatcherFactory;
73
167
  }
74
168
  /**
75
169
  * Start `--watch` mode: a chokidar watcher over the project feeding the
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOzD,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IAQb,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;gBAEnB,OAAO,EAAE,qBAAqB;IAI3D,4DAA4D;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM1B,8EAA8E;IAC9E,KAAK,IAAI,IAAI;IAQb,OAAO,CAAC,QAAQ;YAQF,GAAG;CA4BlB;AAMD,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMjF;AAMD,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,WAAW,CAgD1E"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { extname, relative, resolve } from 'node:path';
1
+ import { readdir } from 'node:fs/promises';
2
+ import { extname, join, relative, resolve } from 'node:path';
2
3
  import { watch as chokidarWatch } from 'chokidar';
3
4
  import { globToRegex } from './walker.js';
4
5
  /**
@@ -93,6 +94,102 @@ export function shouldWatchPath(filter, absPath) {
93
94
  return false;
94
95
  return !filter.excludeRegexes.some((rx) => rx.test(rel));
95
96
  }
97
+ function isInsideRoot(projectRoot, directory) {
98
+ const rel = relative(projectRoot, directory);
99
+ return rel === '' || (!rel.startsWith('..') && !rel.startsWith('/'));
100
+ }
101
+ /**
102
+ * Finds what is on disk but unknown to the watcher (D067). Pure over its inputs:
103
+ * `watched` is a snapshot and `listDirectory` is the only I/O.
104
+ *
105
+ * Only directories at or under `projectRoot` are listed: chokidar records the root
106
+ * itself as a child of the root's parent, and that parent is not ours to scan.
107
+ * A file the watcher learns about between the snapshot and the listing is reported
108
+ * too; that is a redundant notification, which the incremental reindex absorbs.
109
+ * Symbolic links are listed as files and never followed, so a link cycle cannot loop.
110
+ */
111
+ export async function findUnwatchedEntries(input) {
112
+ const files = [];
113
+ const directories = [];
114
+ const roots = [];
115
+ const failures = [];
116
+ const list = async (directory) => {
117
+ try {
118
+ return await input.listDirectory(directory);
119
+ }
120
+ catch (error) {
121
+ failures.push({ directory, error });
122
+ return [];
123
+ }
124
+ };
125
+ // Everything below an unknown directory is unknown too, so it is walked in full.
126
+ const walkUnknown = async (directory) => {
127
+ if (input.signal?.aborted === true)
128
+ return;
129
+ for (const entry of await list(directory)) {
130
+ const abs = join(directory, entry.name);
131
+ if (input.isIgnored(abs))
132
+ continue;
133
+ if (entry.isDirectory) {
134
+ directories.push(abs);
135
+ await walkUnknown(abs);
136
+ }
137
+ else {
138
+ files.push(abs);
139
+ }
140
+ }
141
+ };
142
+ for (const [directory, children] of Object.entries(input.watched)) {
143
+ if (input.signal?.aborted === true)
144
+ break;
145
+ if (!isInsideRoot(input.projectRoot, directory))
146
+ continue;
147
+ const known = new Set(children);
148
+ for (const entry of await list(directory)) {
149
+ if (known.has(entry.name))
150
+ continue;
151
+ const abs = join(directory, entry.name);
152
+ if (input.isIgnored(abs))
153
+ continue;
154
+ roots.push(abs);
155
+ if (entry.isDirectory) {
156
+ directories.push(abs);
157
+ await walkUnknown(abs);
158
+ }
159
+ else {
160
+ files.push(abs);
161
+ }
162
+ }
163
+ }
164
+ return { files, directories, roots, failures };
165
+ }
166
+ async function listDirectoryOnDisk(directory) {
167
+ const entries = await readdir(directory, { withFileTypes: true });
168
+ return entries.map((e) => ({ name: e.name, isDirectory: e.isDirectory() }));
169
+ }
170
+ /** Resolves true after `ms`, or false at once if `signal` aborts first. */
171
+ function delay(ms, signal) {
172
+ return new Promise((resolve) => {
173
+ if (signal.aborted) {
174
+ resolve(false);
175
+ return;
176
+ }
177
+ const timer = setTimeout(() => { signal.removeEventListener('abort', onAbort); resolve(true); }, ms);
178
+ const onAbort = () => { clearTimeout(timer); resolve(false); };
179
+ signal.addEventListener('abort', onAbort, { once: true });
180
+ });
181
+ }
182
+ const defaultWatcherFactory = (root, opts) => {
183
+ const fsw = chokidarWatch(root, opts);
184
+ return {
185
+ onFileEvent: (event, listener) => { fsw.on(event, listener); },
186
+ onReady: (listener) => { fsw.on('ready', listener); },
187
+ onError: (listener) => { fsw.on('error', listener); },
188
+ getWatched: () => fsw.getWatched(),
189
+ add: (paths) => fsw.add([...paths]),
190
+ close: () => fsw.close(),
191
+ };
192
+ };
96
193
  /**
97
194
  * Start `--watch` mode: a chokidar watcher over the project feeding the
98
195
  * debounced scheduler. May throw on construction (e.g. EMFILE) — callers must
@@ -111,38 +208,94 @@ export function startWatchMode(options) {
111
208
  onBatch: options.runBatch,
112
209
  onWarn: options.onWarn,
113
210
  });
114
- const watcher = chokidarWatch(config.resolved_project_root, {
211
+ // Prune ignored subtrees at the directory level so chokidar never descends
212
+ // into node_modules/ or the state dir. Files are re-checked (with the
213
+ // extension allowlist) in shouldWatchPath; `rel + '/'` lets patterns like
214
+ // `**/node_modules/**` match the directory itself, not just its contents.
215
+ // Shared with reconciliation so both agree on what is out of scope.
216
+ const isIgnored = (path) => {
217
+ const abs = resolve(path);
218
+ if (abs === filter.stateDir || abs.startsWith(`${filter.stateDir}/`))
219
+ return true;
220
+ const rel = relative(filter.projectRoot, abs);
221
+ if (rel === '' || rel.startsWith('..'))
222
+ return false;
223
+ return filter.excludeRegexes.some((rx) => rx.test(rel) || rx.test(`${rel}/`));
224
+ };
225
+ const watcher = (options.watcherFactory ?? defaultWatcherFactory)(config.resolved_project_root, {
115
226
  // The startup ladder already reindexed — only future changes matter.
116
227
  ignoreInitial: true,
117
- // Prune ignored subtrees at the directory level so chokidar never descends
118
- // into node_modules/ or the state dir. Files are re-checked (with the
119
- // extension allowlist) in shouldWatchPath; `rel + '/'` lets patterns like
120
- // `**/node_modules/**` match the directory itself, not just its contents.
121
- ignored: (path) => {
122
- const abs = resolve(path);
123
- if (abs === filter.stateDir || abs.startsWith(`${filter.stateDir}/`))
124
- return true;
125
- const rel = relative(filter.projectRoot, abs);
126
- if (rel === '' || rel.startsWith('..'))
127
- return false;
128
- return filter.excludeRegexes.some((rx) => rx.test(rel) || rx.test(`${rel}/`));
129
- },
228
+ ignored: isIgnored,
130
229
  });
230
+ // Aborted by `close()`; cancels the settle wait and the reconciliation walk.
231
+ const closing = new AbortController();
232
+ const reconcile = async () => {
233
+ let found;
234
+ try {
235
+ found = await findUnwatchedEntries({
236
+ watched: watcher.getWatched(),
237
+ projectRoot: filter.projectRoot,
238
+ listDirectory: listDirectoryOnDisk,
239
+ isIgnored,
240
+ signal: closing.signal,
241
+ });
242
+ }
243
+ catch (err) {
244
+ options.onWarn(`[mast] watch: reconciliation failed (continuing): ${String(err)}`);
245
+ return;
246
+ }
247
+ if (closing.signal.aborted)
248
+ return;
249
+ for (const failure of found.failures) {
250
+ options.onWarn(`[mast] watch: reconciliation could not list ${failure.directory} (continuing): ${String(failure.error)}`);
251
+ }
252
+ for (const file of found.files) {
253
+ if (shouldWatchPath(filter, file))
254
+ scheduler.notify(file);
255
+ }
256
+ // `add` under `ignoreInitial: true` emits no `add` events (chokidar handler.js:
257
+ // `_addToNodeFs(path, !_internal, ...)` makes this an initial add), so the files
258
+ // notified above are not delivered twice. It is what makes later changes inside
259
+ // a missed directory visible.
260
+ if (found.roots.length > 0)
261
+ watcher.add(found.roots);
262
+ };
263
+ const announceReadiness = async () => {
264
+ if (!(await delay(options.settleMs ?? 1000, closing.signal)))
265
+ return;
266
+ await reconcile();
267
+ if (closing.signal.aborted)
268
+ return;
269
+ options.onReady?.();
270
+ };
131
271
  // `unlink` feeds the same incremental run: deleted files are cleaned up by
132
272
  // the index run's manifest diff (removeDeletedFiles), not reimplemented here.
133
- watcher.on('add', (path) => { if (shouldWatchPath(filter, resolve(path)))
273
+ watcher.onFileEvent('add', (path) => { if (shouldWatchPath(filter, resolve(path)))
134
274
  scheduler.notify(path); });
135
- watcher.on('change', (path) => { if (shouldWatchPath(filter, resolve(path)))
275
+ watcher.onFileEvent('change', (path) => { if (shouldWatchPath(filter, resolve(path)))
136
276
  scheduler.notify(path); });
137
- watcher.on('unlink', (path) => { if (shouldWatchPath(filter, resolve(path)))
277
+ watcher.onFileEvent('unlink', (path) => { if (shouldWatchPath(filter, resolve(path)))
138
278
  scheduler.notify(path); });
139
- watcher.on('error', (err) => {
279
+ // chokidar's `ready` only says its initial scan finished; the OS watch may not be
280
+ // delivering yet, and a file created in that gap fires no event (D067). So
281
+ // readiness is announced after a settle period and a reconciliation pass, see
282
+ // `onReady`. A construction failure (EMFILE, permissions) throws out of the
283
+ // factory above and never reaches this line, so `onReady` cannot announce a
284
+ // watcher that failed to start. A runtime `error` after a successful scan does
285
+ // not un-fire it: the signal is not "the watcher is healthy".
286
+ watcher.onReady(() => {
287
+ announceReadiness().catch((err) => {
288
+ options.onWarn(`[mast] watch: readiness announcement failed: ${String(err)}`);
289
+ });
290
+ });
291
+ watcher.onError((err) => {
140
292
  // Watcher errors (EMFILE, EPERM, …) degrade to no-watch; JIT staleness
141
293
  // handling keeps reads correct, so serving continues.
142
294
  options.onWarn(`[mast] watch: watcher error (continuing without event): ${String(err)}`);
143
295
  });
144
296
  return {
145
297
  close: async () => {
298
+ closing.abort();
146
299
  scheduler.close();
147
300
  await watcher.close();
148
301
  },
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/indexer/watcher.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import type { Db } from '../graph/db.js';
2
2
  import type { ChunkStore } from '../store/sqliteChunkStore.js';
3
3
  import type { ResolvedConfig } from '../store/config.js';
4
+ import type { FreshnessProbe } from './freshness-probe.js';
4
5
  /**
5
6
  * Shared state injected into every MCP tool handler at registration time.
6
7
  *
@@ -19,5 +20,20 @@ export interface AppContext {
19
20
  readonly config: ResolvedConfig;
20
21
  /** Stable identifier for this MCP server session (used in metrics). */
21
22
  readonly sessionId: string;
23
+ /**
24
+ * Cached "is the index missing files?" signal, read by `mast_search` to warn
25
+ * that a result set may be incomplete.
26
+ *
27
+ * Optional, and absent is a real configuration rather than an oversight: the
28
+ * probe amortises one project walk across a TTL, which only pays off in a
29
+ * long-lived process. `mast serve` wires one (mcp/server.ts); the one-shot
30
+ * CLI query path (cli/query.ts) deliberately does not, because a single
31
+ * search there would pay the whole walk to answer a question it can answer
32
+ * outright with `mast search --reindex`.
33
+ *
34
+ * Absent, or reporting `null`, means *unknown* — surfaces render that as
35
+ * silence, never as zero.
36
+ */
37
+ readonly freshness?: FreshnessProbe;
22
38
  }
23
39
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/mcp/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMzD;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/mcp/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAM3D;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;CACrC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * A cached, non-blocking answer to "is the index missing files?".
3
+ *
4
+ * `mast_search` needs this to warn a caller that a zero-hit result may mean
5
+ * "not indexed" rather than "not present" — the severity-zero failure this
6
+ * package is built around. What it cannot do is pay for it *at scale*, and the
7
+ * cost is worth stating with its corpus attached, because it inverts:
8
+ *
9
+ * - This repository, 213 files walked (medians, measured 2026-09-03):
10
+ * `fusedSearch` 15.3 ms, `measureFreshness` 7.6 ms. The walk is CHEAPER
11
+ * than the search. On a tree this size the probe buys nothing.
12
+ * - A 14k-file monorepo: `measureFreshness` ~183 ms (measured 2026-09-02).
13
+ *
14
+ * That is the whole argument, and it is about scaling, not about a ratio.
15
+ * `measureFreshness` is O(files in the tree) and a search is O(matches), so the
16
+ * walk overtakes the query as the tree grows and then keeps going. The probe
17
+ * exists for the trees where that has already happened.
18
+ *
19
+ * So the probe inverts it. `peekUnindexed` returns the last-known count
20
+ * synchronously and schedules a refresh behind the caller when the value has
21
+ * aged past the TTL. The first search of a session gets `null` — unknown, no
22
+ * warning — and every later one gets an answer that is at most `ttlMs` old.
23
+ * `server.ts` primes it at startup so that window is usually already closed.
24
+ *
25
+ * The cost is amortised, NOT eliminated, and it is worth being exact about
26
+ * which: `measureFreshness` -> `walkProject` globs asynchronously but then
27
+ * `statSync`s every hit in a synchronous loop, so a refresh occupies the event
28
+ * loop for part of its duration. A search that lands mid-refresh can be
29
+ * delayed by that much. One such window per `ttlMs`, rather than one walk per
30
+ * search, is the whole of the improvement — do not read "background" as
31
+ * "free".
32
+ *
33
+ * Deliberately NOT a reindex: no `structure.lock` is taken and nothing is
34
+ * written, so the probe cannot contend with a concurrent `runIndex` or with
35
+ * another reader (E7 measured 35-88.5% JIT failure under reader-vs-reader
36
+ * contention). It reads, it counts, it reports.
37
+ */
38
+ import type { Db } from '../graph/db.js';
39
+ import type { ResolvedConfig } from '../store/config.js';
40
+ import { type IndexFreshness } from '../indexer/freshness.js';
41
+ /** How long a measurement stays authoritative before the next peek refreshes it. */
42
+ export declare const DEFAULT_FRESHNESS_TTL_MS = 30000;
43
+ export interface FreshnessProbe {
44
+ /**
45
+ * Files on disk that the index has never seen, as of the last completed
46
+ * measurement — or `null` when none has completed yet.
47
+ *
48
+ * Never blocks, never throws, never awaits. May schedule a background
49
+ * refresh as a side effect. `null` means *unknown*, which callers must
50
+ * render as silence rather than as zero.
51
+ */
52
+ peekUnindexed(): number | null;
53
+ /**
54
+ * Discard the cached value and re-measure on the next peek.
55
+ *
56
+ * Called after this process reindexes: the previous count is not merely old,
57
+ * it is wrong in the direction that produces a false warning about files
58
+ * that were just indexed. A measurement already in flight is *superseded* —
59
+ * it is allowed to finish, and its result is discarded rather than cached,
60
+ * because it was reading the pre-reindex tree.
61
+ */
62
+ invalidate(): void;
63
+ /** Start a measurement now if none is in flight. Used to prime at startup. */
64
+ refresh(): void;
65
+ /**
66
+ * Resolves when the in-flight measurement settles — success or failure.
67
+ *
68
+ * Settling is not the same as caching a value: a measurement superseded by
69
+ * `invalidate()` settles with its result discarded, leaving `peekUnindexed`
70
+ * at `null` until the next peek starts a clean one.
71
+ */
72
+ settled(): Promise<void>;
73
+ }
74
+ export interface FreshnessProbeOptions {
75
+ readonly ttlMs?: number;
76
+ /** Injected for tests (§4.4); production reads the real clock. */
77
+ readonly now?: () => number;
78
+ /** Injected for tests (§4.4); production walks the project. */
79
+ readonly measure?: (config: ResolvedConfig, db: Db) => Promise<IndexFreshness>;
80
+ }
81
+ export declare function createFreshnessProbe(config: ResolvedConfig, db: Db, options?: FreshnessProbeOptions): FreshnessProbe;
82
+ //# sourceMappingURL=freshness-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness-probe.d.ts","sourceRoot":"","sources":["../../src/mcp/freshness-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEhF,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B;;;;;;;OAOG;IACH,aAAa,IAAI,MAAM,GAAG,IAAI,CAAC;IAE/B;;;;;;;;OAQG;IACH,UAAU,IAAI,IAAI,CAAC;IAEnB,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAEhB;;;;;;OAMG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CAChF;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EACtB,EAAE,EAAE,EAAE,EACN,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAuEhB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * A cached, non-blocking answer to "is the index missing files?".
3
+ *
4
+ * `mast_search` needs this to warn a caller that a zero-hit result may mean
5
+ * "not indexed" rather than "not present" — the severity-zero failure this
6
+ * package is built around. What it cannot do is pay for it *at scale*, and the
7
+ * cost is worth stating with its corpus attached, because it inverts:
8
+ *
9
+ * - This repository, 213 files walked (medians, measured 2026-09-03):
10
+ * `fusedSearch` 15.3 ms, `measureFreshness` 7.6 ms. The walk is CHEAPER
11
+ * than the search. On a tree this size the probe buys nothing.
12
+ * - A 14k-file monorepo: `measureFreshness` ~183 ms (measured 2026-09-02).
13
+ *
14
+ * That is the whole argument, and it is about scaling, not about a ratio.
15
+ * `measureFreshness` is O(files in the tree) and a search is O(matches), so the
16
+ * walk overtakes the query as the tree grows and then keeps going. The probe
17
+ * exists for the trees where that has already happened.
18
+ *
19
+ * So the probe inverts it. `peekUnindexed` returns the last-known count
20
+ * synchronously and schedules a refresh behind the caller when the value has
21
+ * aged past the TTL. The first search of a session gets `null` — unknown, no
22
+ * warning — and every later one gets an answer that is at most `ttlMs` old.
23
+ * `server.ts` primes it at startup so that window is usually already closed.
24
+ *
25
+ * The cost is amortised, NOT eliminated, and it is worth being exact about
26
+ * which: `measureFreshness` -> `walkProject` globs asynchronously but then
27
+ * `statSync`s every hit in a synchronous loop, so a refresh occupies the event
28
+ * loop for part of its duration. A search that lands mid-refresh can be
29
+ * delayed by that much. One such window per `ttlMs`, rather than one walk per
30
+ * search, is the whole of the improvement — do not read "background" as
31
+ * "free".
32
+ *
33
+ * Deliberately NOT a reindex: no `structure.lock` is taken and nothing is
34
+ * written, so the probe cannot contend with a concurrent `runIndex` or with
35
+ * another reader (E7 measured 35-88.5% JIT failure under reader-vs-reader
36
+ * contention). It reads, it counts, it reports.
37
+ */
38
+ import { measureFreshness } from '../indexer/freshness.js';
39
+ /** How long a measurement stays authoritative before the next peek refreshes it. */
40
+ export const DEFAULT_FRESHNESS_TTL_MS = 30_000;
41
+ export function createFreshnessProbe(config, db, options = {}) {
42
+ const ttlMs = options.ttlMs ?? DEFAULT_FRESHNESS_TTL_MS;
43
+ const now = options.now ?? Date.now;
44
+ const measure = options.measure ?? measureFreshness;
45
+ let unindexed = null;
46
+ /** `null` = never measured or invalidated; otherwise the clock at settlement. */
47
+ let settledAt = null;
48
+ let inFlight = null;
49
+ /**
50
+ * Bumped by `invalidate()`. A measurement that started under an older
51
+ * generation was reading a tree this process has since reindexed, so its
52
+ * count is not merely old — it is wrong in the exact direction that warns
53
+ * about files that now ARE indexed. Gating on the generation is what makes
54
+ * `invalidate()` mean "discard", rather than "discard unless a walk happens
55
+ * to be in flight, in which case adopt its stale answer and stamp it fresh
56
+ * for a full TTL".
57
+ */
58
+ let generation = 0;
59
+ function refresh() {
60
+ // Still coalesced across generations: a superseded walk is left to finish
61
+ // and its result dropped, rather than disowned and replaced immediately.
62
+ // Disowning would let one walk per `invalidate()` run concurrently, and
63
+ // `measureFreshness` holds the event loop in a synchronous `statSync` loop
64
+ // (see the header) — under a watch-driven edit loop that is worse than the
65
+ // laziness. The cache is left `null` (unknown -> silence, never a wrong
66
+ // warning) and the next peek starts a clean measurement.
67
+ if (inFlight !== null)
68
+ return;
69
+ const startedAt = generation;
70
+ inFlight = measure(config, db)
71
+ .then((result) => {
72
+ if (startedAt !== generation)
73
+ return;
74
+ unindexed = result.unindexed;
75
+ })
76
+ .catch(() => {
77
+ // Swallowed by contract: a failed walk (a directory removed mid-walk, a
78
+ // permission error) must not reject into a search handler or replace a
79
+ // good answer with a wrong one. The last value stands and the stamp
80
+ // below backs the retry off by a full TTL rather than re-walking on
81
+ // every peek.
82
+ })
83
+ .finally(() => {
84
+ inFlight = null;
85
+ // A superseded walk must NOT stamp the cache fresh. Leaving `settledAt`
86
+ // null keeps the value expired, so the next peek re-measures instead of
87
+ // serving `null` for a full TTL.
88
+ if (startedAt !== generation)
89
+ return;
90
+ settledAt = now();
91
+ });
92
+ }
93
+ return {
94
+ peekUnindexed() {
95
+ const isExpired = settledAt === null || now() - settledAt >= ttlMs;
96
+ if (isExpired)
97
+ refresh();
98
+ return unindexed;
99
+ },
100
+ invalidate() {
101
+ unindexed = null;
102
+ settledAt = null;
103
+ generation += 1;
104
+ },
105
+ refresh,
106
+ async settled() {
107
+ await inFlight;
108
+ },
109
+ };
110
+ }
111
+ //# sourceMappingURL=freshness-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness-probe.js","sourceRoot":"","sources":["../../src/mcp/freshness-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH,OAAO,EAAE,gBAAgB,EAAuB,MAAM,yBAAyB,CAAC;AAEhF,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AA6C/C,MAAM,UAAU,oBAAoB,CAClC,MAAsB,EACtB,EAAM,EACN,UAAiC,EAAE;IAEnC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,wBAAwB,CAAC;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,gBAAgB,CAAC;IAEpD,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,iFAAiF;IACjF,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,QAAQ,GAAyB,IAAI,CAAC;IAC1C;;;;;;;;OAQG;IACH,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,SAAS,OAAO;QACd,0EAA0E;QAC1E,yEAAyE;QACzE,wEAAwE;QACxE,2EAA2E;QAC3E,2EAA2E;QAC3E,wEAAwE;QACxE,yDAAyD;QACzD,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO;QAC9B,MAAM,SAAS,GAAG,UAAU,CAAC;QAC7B,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;aAC3B,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,IAAI,SAAS,KAAK,UAAU;gBAAE,OAAO;YACrC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAC/B,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,wEAAwE;YACxE,uEAAuE;YACvE,oEAAoE;YACpE,oEAAoE;YACpE,cAAc;QAChB,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,QAAQ,GAAG,IAAI,CAAC;YAChB,wEAAwE;YACxE,wEAAwE;YACxE,iCAAiC;YACjC,IAAI,SAAS,KAAK,UAAU;gBAAE,OAAO;YACrC,SAAS,GAAG,GAAG,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACP,CAAC;IAED,OAAO;QACL,aAAa;YACX,MAAM,SAAS,GAAG,SAAS,KAAK,IAAI,IAAI,GAAG,EAAE,GAAG,SAAS,IAAI,KAAK,CAAC;YACnE,IAAI,SAAS;gBAAE,OAAO,EAAE,CAAC;YACzB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,UAAU;YACR,SAAS,GAAG,IAAI,CAAC;YACjB,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,IAAI,CAAC,CAAC;QAClB,CAAC;QAED,OAAO;QAEP,KAAK,CAAC,OAAO;YACX,MAAM,QAAQ,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The MCP `instructions` string sent in the `initialize` result (ADR 017 §6).
3
+ * One paragraph: a client may place it in the model's context verbatim, so every
4
+ * sentence has to earn its tokens.
5
+ */
6
+ export declare const SERVER_INSTRUCTIONS: string;
7
+ //# sourceMappingURL=instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/mcp/instructions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAI2H,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The MCP `instructions` string sent in the `initialize` result (ADR 017 §6).
3
+ * One paragraph: a client may place it in the model's context verbatim, so every
4
+ * sentence has to earn its tokens.
5
+ */
6
+ export const SERVER_INSTRUCTIONS = 'mast is an AST-level index of this repository\'s TypeScript, JavaScript and Markdown. ' +
7
+ 'To find code — a symbol, its callers, a module\'s exports — call mast_search (or mast_signature, mast_callers, mast_exports) before using grep, glob or reading whole files: it returns ranked declarations, not line matches. ' +
8
+ 'Use grep for other languages, non-code files, or an exact regex over text. ' +
9
+ 'An empty result is not proof of absence: check index_empty and unindexed_files on the response, and call mast_reindex after creating or renaming files.';
10
+ //# sourceMappingURL=instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../src/mcp/instructions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC9B,wFAAwF;IACxF,iOAAiO;IACjO,6EAA6E;IAC7E,yJAAyJ,CAAC"}