@spikedpunch/mast 0.1.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/LICENSE +21 -0
- package/MAST_SPEC.md +2926 -0
- package/README.md +842 -0
- package/assets/skill.md +45 -0
- package/dist/ast/extract.d.ts +65 -0
- package/dist/ast/extract.d.ts.map +1 -0
- package/dist/ast/extract.js +147 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/extractor.d.ts +85 -0
- package/dist/ast/extractor.d.ts.map +1 -0
- package/dist/ast/extractor.js +2 -0
- package/dist/ast/extractor.js.map +1 -0
- package/dist/ast/extractors/markdown.d.ts +40 -0
- package/dist/ast/extractors/markdown.d.ts.map +1 -0
- package/dist/ast/extractors/markdown.js +175 -0
- package/dist/ast/extractors/markdown.js.map +1 -0
- package/dist/ast/extractors/typescript.d.ts +179 -0
- package/dist/ast/extractors/typescript.d.ts.map +1 -0
- package/dist/ast/extractors/typescript.js +1439 -0
- package/dist/ast/extractors/typescript.js.map +1 -0
- package/dist/ast/parser.d.ts +5 -0
- package/dist/ast/parser.d.ts.map +1 -0
- package/dist/ast/parser.js +21 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/ast/types.d.ts +586 -0
- package/dist/ast/types.d.ts.map +1 -0
- package/dist/ast/types.js +3 -0
- package/dist/ast/types.js.map +1 -0
- package/dist/cli/docs-cmd.d.ts +22 -0
- package/dist/cli/docs-cmd.d.ts.map +1 -0
- package/dist/cli/docs-cmd.js +100 -0
- package/dist/cli/docs-cmd.js.map +1 -0
- package/dist/cli/index-cmd.d.ts +72 -0
- package/dist/cli/index-cmd.d.ts.map +1 -0
- package/dist/cli/index-cmd.js +263 -0
- package/dist/cli/index-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +64 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/install-hooks.d.ts +3 -0
- package/dist/cli/install-hooks.d.ts.map +1 -0
- package/dist/cli/install-hooks.js +34 -0
- package/dist/cli/install-hooks.js.map +1 -0
- package/dist/cli/metrics-cmd.d.ts +3 -0
- package/dist/cli/metrics-cmd.d.ts.map +1 -0
- package/dist/cli/metrics-cmd.js +164 -0
- package/dist/cli/metrics-cmd.js.map +1 -0
- package/dist/cli/program.d.ts +14 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +44 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/query.d.ts +46 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +153 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/search-cmd.d.ts +33 -0
- package/dist/cli/search-cmd.d.ts.map +1 -0
- package/dist/cli/search-cmd.js +114 -0
- package/dist/cli/search-cmd.js.map +1 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +19 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/skill-install.d.ts +39 -0
- package/dist/cli/skill-install.d.ts.map +1 -0
- package/dist/cli/skill-install.js +57 -0
- package/dist/cli/skill-install.js.map +1 -0
- package/dist/cli/status.d.ts +35 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +117 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/upgrade-cmd.d.ts +48 -0
- package/dist/cli/upgrade-cmd.d.ts.map +1 -0
- package/dist/cli/upgrade-cmd.js +129 -0
- package/dist/cli/upgrade-cmd.js.map +1 -0
- package/dist/cli/version.d.ts +3 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +19 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/env.d.ts +17 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +17 -0
- package/dist/env.js.map +1 -0
- package/dist/graph/checker-resolver.d.ts +156 -0
- package/dist/graph/checker-resolver.d.ts.map +1 -0
- package/dist/graph/checker-resolver.js +458 -0
- package/dist/graph/checker-resolver.js.map +1 -0
- package/dist/graph/db.d.ts +318 -0
- package/dist/graph/db.d.ts.map +1 -0
- package/dist/graph/db.js +266 -0
- package/dist/graph/db.js.map +1 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/local-type-env.d.ts +45 -0
- package/dist/graph/local-type-env.d.ts.map +1 -0
- package/dist/graph/local-type-env.js +59 -0
- package/dist/graph/local-type-env.js.map +1 -0
- package/dist/graph/path-range.d.ts +40 -0
- package/dist/graph/path-range.d.ts.map +1 -0
- package/dist/graph/path-range.js +42 -0
- package/dist/graph/path-range.js.map +1 -0
- package/dist/graph/populate.d.ts +263 -0
- package/dist/graph/populate.d.ts.map +1 -0
- package/dist/graph/populate.js +984 -0
- package/dist/graph/populate.js.map +1 -0
- package/dist/graph/queries.d.ts +114 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +415 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/sqliteBatch.d.ts +48 -0
- package/dist/graph/sqliteBatch.d.ts.map +1 -0
- package/dist/graph/sqliteBatch.js +93 -0
- package/dist/graph/sqliteBatch.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +72 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/import-resolver.d.ts +50 -0
- package/dist/indexer/import-resolver.d.ts.map +1 -0
- package/dist/indexer/import-resolver.js +318 -0
- package/dist/indexer/import-resolver.js.map +1 -0
- package/dist/indexer/index.d.ts +249 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +560 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/walker.d.ts +60 -0
- package/dist/indexer/walker.d.ts.map +1 -0
- package/dist/indexer/walker.js +128 -0
- package/dist/indexer/walker.js.map +1 -0
- package/dist/indexer/watcher.d.ts +80 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +151 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/context.d.ts +23 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +2 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +21 -0
- package/dist/mcp/register-tools.d.ts.map +1 -0
- package/dist/mcp/register-tools.js +42 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/staleness.d.ts +72 -0
- package/dist/mcp/staleness.d.ts.map +1 -0
- package/dist/mcp/staleness.js +213 -0
- package/dist/mcp/staleness.js.map +1 -0
- package/dist/mcp/startup.d.ts +40 -0
- package/dist/mcp/startup.d.ts.map +1 -0
- package/dist/mcp/startup.js +114 -0
- package/dist/mcp/startup.js.map +1 -0
- package/dist/mcp/tools/_helpers.d.ts +26 -0
- package/dist/mcp/tools/_helpers.d.ts.map +1 -0
- package/dist/mcp/tools/_helpers.js +54 -0
- package/dist/mcp/tools/_helpers.js.map +1 -0
- package/dist/mcp/tools/callers.d.ts +4 -0
- package/dist/mcp/tools/callers.d.ts.map +1 -0
- package/dist/mcp/tools/callers.js +112 -0
- package/dist/mcp/tools/callers.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +4 -0
- package/dist/mcp/tools/dependencies.d.ts.map +1 -0
- package/dist/mcp/tools/dependencies.js +36 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/efficiency.d.ts +4 -0
- package/dist/mcp/tools/efficiency.d.ts.map +1 -0
- package/dist/mcp/tools/efficiency.js +39 -0
- package/dist/mcp/tools/efficiency.js.map +1 -0
- package/dist/mcp/tools/exports.d.ts +4 -0
- package/dist/mcp/tools/exports.d.ts.map +1 -0
- package/dist/mcp/tools/exports.js +56 -0
- package/dist/mcp/tools/exports.js.map +1 -0
- package/dist/mcp/tools/implementors.d.ts +4 -0
- package/dist/mcp/tools/implementors.d.ts.map +1 -0
- package/dist/mcp/tools/implementors.js +47 -0
- package/dist/mcp/tools/implementors.js.map +1 -0
- package/dist/mcp/tools/project-skeleton.d.ts +4 -0
- package/dist/mcp/tools/project-skeleton.d.ts.map +1 -0
- package/dist/mcp/tools/project-skeleton.js +61 -0
- package/dist/mcp/tools/project-skeleton.js.map +1 -0
- package/dist/mcp/tools/reindex.d.ts +14 -0
- package/dist/mcp/tools/reindex.d.ts.map +1 -0
- package/dist/mcp/tools/reindex.js +32 -0
- package/dist/mcp/tools/reindex.js.map +1 -0
- package/dist/mcp/tools/rename-impact.d.ts +4 -0
- package/dist/mcp/tools/rename-impact.d.ts.map +1 -0
- package/dist/mcp/tools/rename-impact.js +124 -0
- package/dist/mcp/tools/rename-impact.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +4 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +69 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/signature.d.ts +4 -0
- package/dist/mcp/tools/signature.d.ts.map +1 -0
- package/dist/mcp/tools/signature.js +127 -0
- package/dist/mcp/tools/signature.js.map +1 -0
- package/dist/mcp/tools/status.d.ts +4 -0
- package/dist/mcp/tools/status.d.ts.map +1 -0
- package/dist/mcp/tools/status.js +30 -0
- package/dist/mcp/tools/status.js.map +1 -0
- package/dist/search/declex.d.ts +109 -0
- package/dist/search/declex.d.ts.map +1 -0
- package/dist/search/declex.js +242 -0
- package/dist/search/declex.js.map +1 -0
- package/dist/search/fts.d.ts +77 -0
- package/dist/search/fts.d.ts.map +1 -0
- package/dist/search/fts.js +223 -0
- package/dist/search/fts.js.map +1 -0
- package/dist/search/fused.d.ts +110 -0
- package/dist/search/fused.d.ts.map +1 -0
- package/dist/search/fused.js +323 -0
- package/dist/search/fused.js.map +1 -0
- package/dist/search/potential-matches.d.ts +88 -0
- package/dist/search/potential-matches.d.ts.map +1 -0
- package/dist/search/potential-matches.js +83 -0
- package/dist/search/potential-matches.js.map +1 -0
- package/dist/search/scope.d.ts +66 -0
- package/dist/search/scope.d.ts.map +1 -0
- package/dist/search/scope.js +71 -0
- package/dist/search/scope.js.map +1 -0
- package/dist/store/config.d.ts +57 -0
- package/dist/store/config.d.ts.map +1 -0
- package/dist/store/config.js +208 -0
- package/dist/store/config.js.map +1 -0
- package/dist/store/lock.d.ts +101 -0
- package/dist/store/lock.d.ts.map +1 -0
- package/dist/store/lock.js +182 -0
- package/dist/store/lock.js.map +1 -0
- package/dist/store/lockMetrics.d.ts +52 -0
- package/dist/store/lockMetrics.d.ts.map +1 -0
- package/dist/store/lockMetrics.js +29 -0
- package/dist/store/lockMetrics.js.map +1 -0
- package/dist/store/sqliteChunkStore.d.ts +44 -0
- package/dist/store/sqliteChunkStore.d.ts.map +1 -0
- package/dist/store/sqliteChunkStore.js +135 -0
- package/dist/store/sqliteChunkStore.js.map +1 -0
- package/dist/telemetry/lockMetricsSummary.d.ts +42 -0
- package/dist/telemetry/lockMetricsSummary.d.ts.map +1 -0
- package/dist/telemetry/lockMetricsSummary.js +139 -0
- package/dist/telemetry/lockMetricsSummary.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +140 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +312 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tokenizer.d.ts +125 -0
- package/dist/telemetry/tokenizer.d.ts.map +1 -0
- package/dist/telemetry/tokenizer.js +174 -0
- package/dist/telemetry/tokenizer.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { sql } from 'kysely';
|
|
4
|
+
import { CURRENT_SCHEMA_VERSION } from '../store/config.js';
|
|
5
|
+
import { initLockMarkers, withLock } from '../store/lock.js';
|
|
6
|
+
import { SqliteChunkStore } from '../store/sqliteChunkStore.js';
|
|
7
|
+
import { openDatabase, readPragmaValue } from '../graph/db.js';
|
|
8
|
+
import { populateFile, insertEdges, insertReExportFiles, removeDeletedFiles } from '../graph/populate.js';
|
|
9
|
+
import { extractFile } from '../ast/extract.js';
|
|
10
|
+
import { walkProject, buildManifest, diffManifest } from './walker.js';
|
|
11
|
+
import { getImportResolver } from './import-resolver.js';
|
|
12
|
+
/**
|
|
13
|
+
* Fresh best-effort mtime read (seconds), used wherever a value written to
|
|
14
|
+
* `files.mtime` or `file_manifest.json` must reflect disk state as observed
|
|
15
|
+
* at READ time rather than at walk time (F1/F12 — see the WHY-comments in
|
|
16
|
+
* {@link runIndex} for why the two can diverge once `structure.lock` no
|
|
17
|
+
* longer spans the whole run). Two call sites, two different reasons: the
|
|
18
|
+
* parse loop calls this IMMEDIATELY BEFORE `doExtract` so the stamp can never
|
|
19
|
+
* be newer than the content it accompanies (F12 invariant 1); the final
|
|
20
|
+
* manifest phase calls this with no accompanying content read at all, purely
|
|
21
|
+
* for `file_manifest.json` bookkeeping (see invariant 1's note on why that
|
|
22
|
+
* one doesn't need the same fix). Falls back to `fallbackSeconds` if the file
|
|
23
|
+
* has vanished since it was walked — mirrors `walkProject`'s own
|
|
24
|
+
* best-effort disappearance handling (walker.ts:63-65).
|
|
25
|
+
*/
|
|
26
|
+
function statMtimeSecondsOrFallback(absPath, fallbackSeconds) {
|
|
27
|
+
try {
|
|
28
|
+
return statSync(absPath).mtimeMs / 1_000;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return fallbackSeconds;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Walk the project, parse changed files into chunks, populate the graph
|
|
36
|
+
* database and FTS indexes.
|
|
37
|
+
*
|
|
38
|
+
* F1 (GITNEXUS_COMPARISON.md §13.2, MAST_SPEC.md:822-826): `structure.lock`
|
|
39
|
+
* is acquired and released separately for each write phase — deleted-file
|
|
40
|
+
* cleanup, each `LANCE_BATCH`-sized write batch, each edge-insertion batch,
|
|
41
|
+
* and the final manifest/index.json write — instead of once for the whole
|
|
42
|
+
* run. The read-only walk and the CPU-bound parse phase never hold the lock
|
|
43
|
+
* at all. Before this change the lock was held for the run's full duration
|
|
44
|
+
* (measured max: 280,782 ms on a 1,335-file corpus — eval/baseline-locks.json),
|
|
45
|
+
* which starved the JIT re-parse path (mcp/staleness.ts:56, 3 retries x
|
|
46
|
+
* 100 ms) for minutes at a time — directly contradicting MAST_SPEC.md:822-826
|
|
47
|
+
* ("lock holding is per-file-parse (10-50ms), not per-tool-call").
|
|
48
|
+
*
|
|
49
|
+
* Narrowing the lock's scope means a JIT re-parse can now interleave BETWEEN
|
|
50
|
+
* phases of an in-flight run. Two invariants keep that safe:
|
|
51
|
+
*
|
|
52
|
+
* 1. **[F12, `GITNEXUS_COMPARISON.md` Stage 1]** The row's stamped mtime must
|
|
53
|
+
* never be NEWER than the content it is stamped against — i.e.
|
|
54
|
+
* `stamp <= mtime-at-content-read`. This is an ORDERING property, not a
|
|
55
|
+
* mutual-exclusion one: no lock can enforce it, because a file's editor
|
|
56
|
+
* never takes `structure.lock`. An earlier version of this invariant
|
|
57
|
+
* re-stat'd each file AFTER `doExtract` (inside the locked write phase) to
|
|
58
|
+
* stop `files.mtime` *regressing* below `entry.mtime` — but that opened
|
|
59
|
+
* the real hazard: an edit landing in the (unlocked, seconds-wide under
|
|
60
|
+
* §15.2's FTS growth) parse-to-write window would be stamped with a mtime
|
|
61
|
+
* NEWER than the content that was actually parsed, permanently disarming
|
|
62
|
+
* staleness detection for that file (`diskMtime <= storedMtime` reads
|
|
63
|
+
* "fresh" forever — the exact P0 class this stage exists to eliminate).
|
|
64
|
+
* The fix stats each file IMMEDIATELY BEFORE `doExtract`, in the parse
|
|
65
|
+
* loop below (unlocked, same as `mcp/staleness.ts`'s JIT path, which has
|
|
66
|
+
* always done this correctly), and carries that pre-parse stamp through to
|
|
67
|
+
* the write untouched — no re-stat at write time. A post-stat edit then
|
|
68
|
+
* leaves stamp < content, so staleness re-fires on the next check and the
|
|
69
|
+
* system self-heals, the same property the JIT path already had.
|
|
70
|
+
* 2. **Monotonic write-guard, `graph/populate.ts`'s `populateFile`**: even
|
|
71
|
+
* with (1), two writers (e.g. this run's batch write and a concurrent JIT
|
|
72
|
+
* refresh of the same file) can still race to write the SAME row with
|
|
73
|
+
* DIFFERENT stamps, and `structure.lock` only serializes the two writes —
|
|
74
|
+
* it does not guarantee the one with the fresher stamp wins. `populateFile`
|
|
75
|
+
* refuses to replace a row whose stored mtime already exceeds the incoming
|
|
76
|
+
* stamp, so whichever write carries the NEWER stamp wins regardless of
|
|
77
|
+
* which one reaches the lock first — fixing the reindex-vs-JIT race
|
|
78
|
+
* *better* than the lock alone does (the lock only serializes; the guard
|
|
79
|
+
* orders).
|
|
80
|
+
*
|
|
81
|
+
* Known limitation (not solved here): mtime-granularity blindness. An edit
|
|
82
|
+
* landing in the SAME mtime tick as the stamp compares equal, not greater,
|
|
83
|
+
* and reads as fresh (the git racy-lstat problem). Near-moot on APFS's
|
|
84
|
+
* nanosecond-resolution mtimes; real on coarse-mtime volumes (e.g. some
|
|
85
|
+
* shared/network filesystems), which is exactly the container-deployment
|
|
86
|
+
* case MAST_SPEC.md §7.6 targets. Solving it properly means content hashing,
|
|
87
|
+
* not mtime comparison, and is out of scope for F12.
|
|
88
|
+
*
|
|
89
|
+
* `file_manifest.json` (which drives the NEXT run's incremental diff, see
|
|
90
|
+
* `diffManifest`) is rebuilt from a fresh re-stat of every file under the
|
|
91
|
+
* FINAL lock acquisition (below), not from the run-start walk snapshot or
|
|
92
|
+
* from the parse-phase stamps above. This one does NOT need invariant 1's
|
|
93
|
+
* fix: it is a stat with no accompanying content read in this final phase, so
|
|
94
|
+
* there is no stamp/content pair to invert. If it ever disagrees with
|
|
95
|
+
* `files.mtime` for a file JIT touched concurrently, that disagreement
|
|
96
|
+
* self-heals on the next JIT check (`checkAndRefreshIfStale` compares disk
|
|
97
|
+
* mtime, not the manifest, against `files.mtime`) — the manifest only feeds
|
|
98
|
+
* the NEXT run's `diffManifest`, and a stale manifest entry there causes an
|
|
99
|
+
* extra, harmless re-parse, never a missed one.
|
|
100
|
+
*
|
|
101
|
+
* The two-pass edge-insertion structure (`insertEdges` needs every file's
|
|
102
|
+
* symbols already present) is preserved: ALL pass-1 batches — across the
|
|
103
|
+
* whole run, not just the current batch — complete before pass 2 starts, so
|
|
104
|
+
* cross-file edge targets are always resolvable regardless of how finely
|
|
105
|
+
* pass 1's own lock acquisitions are chunked.
|
|
106
|
+
*/
|
|
107
|
+
export async function runIndex(config, options) {
|
|
108
|
+
const startMs = Date.now();
|
|
109
|
+
// initLockMarkers is idempotent — safe to call on every run so that
|
|
110
|
+
// `mast index` works even without a prior `mast init`.
|
|
111
|
+
initLockMarkers(config.resolved_state_dir);
|
|
112
|
+
const lockOptions = {
|
|
113
|
+
maxRetries: 5,
|
|
114
|
+
retryIntervalMs: 1_000,
|
|
115
|
+
caller: 'index-run',
|
|
116
|
+
sink: options.lockMetricsSink,
|
|
117
|
+
};
|
|
118
|
+
const doExtract = options.extractFileFn ?? extractFile;
|
|
119
|
+
// Opening the db handle is connection setup, not a graph/chunk-store
|
|
120
|
+
// mutation — no lock needed.
|
|
121
|
+
const db = openDatabase(config.resolved_state_dir, options.dbOptions ?? {});
|
|
122
|
+
// M1 (eval/GITNEXUS_COMPARISON.md §15.1): chunks live in graph.db's
|
|
123
|
+
// `chunks` table by default — `SqliteChunkStore` wraps the same `db`
|
|
124
|
+
// connection every write below already uses. `chunkStoreOverride` (test
|
|
125
|
+
// DI, §4.4) substitutes a fake for write-failure tests; see its docstring
|
|
126
|
+
// on `IndexOptions` and `graph/populate.ts`'s `ChunkWriter`.
|
|
127
|
+
const chunkStore = options.chunkStoreOverride ?? new SqliteChunkStore(db);
|
|
128
|
+
const chunkWriter = options.chunkStoreOverride
|
|
129
|
+
? options.chunkStoreOverride.replaceChunksForFile.bind(options.chunkStoreOverride)
|
|
130
|
+
: undefined;
|
|
131
|
+
// Built with conditional spreads for the same reason `index-cmd.ts` builds
|
|
132
|
+
// `dbOptions` that way: `exactOptionalPropertyTypes` distinguishes an absent
|
|
133
|
+
// property from one explicitly set to undefined, and the production path
|
|
134
|
+
// depends on `spans` being genuinely absent so `populateFile` never takes a
|
|
135
|
+
// clock reading at all.
|
|
136
|
+
const populateOptions = {
|
|
137
|
+
...(chunkWriter !== undefined ? { chunkWriter } : {}),
|
|
138
|
+
...(options.writeSpans !== undefined ? { spans: options.writeSpans } : {}),
|
|
139
|
+
...(options.unsafeSkipFtsDeletes === true ? { skipFtsDeletes: true } : {}),
|
|
140
|
+
};
|
|
141
|
+
// Load previous manifest for incremental comparison.
|
|
142
|
+
const manifestPath = join(config.resolved_state_dir, 'file_manifest.json');
|
|
143
|
+
const prevManifest = existsSync(manifestPath)
|
|
144
|
+
? JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
145
|
+
: {};
|
|
146
|
+
const currentFiles = await walkProject(config);
|
|
147
|
+
const { stale, added, deleted } = diffManifest(currentFiles, prevManifest);
|
|
148
|
+
let chunksRemoved = 0;
|
|
149
|
+
// Deleted-file cleanup — cascade removes symbols/edges/imports/chunks from
|
|
150
|
+
// the graph (one transaction, `removeDeletedFiles`). Its own short lock
|
|
151
|
+
// acquisition, separate from pass 1's per-batch locks.
|
|
152
|
+
await withLock(config.resolved_state_dir, 'structure', lockOptions, async () => {
|
|
153
|
+
chunksRemoved += await removeDeletedFiles(db, deleted);
|
|
154
|
+
// Full reindex: also purge DB entries for files no longer in the current
|
|
155
|
+
// walk (e.g. dist/ files left over from a previous indexing configuration
|
|
156
|
+
// that predates the current exclude_patterns). These are invisible to the
|
|
157
|
+
// manifest-diff above because they were never written to file_manifest.json
|
|
158
|
+
// under the current config.
|
|
159
|
+
if (!options.incremental) {
|
|
160
|
+
const currentPaths = new Set(currentFiles.map((e) => e.relativePath));
|
|
161
|
+
const dbRows = await db.selectFrom('files').select('path').execute();
|
|
162
|
+
const orphans = dbRows.map((r) => r.path).filter((p) => !currentPaths.has(p));
|
|
163
|
+
if (orphans.length > 0) {
|
|
164
|
+
chunksRemoved += await removeDeletedFiles(db, orphans);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// An incremental run's work set is the manifest diff PLUS any walked file
|
|
169
|
+
// with no `files` row. The manifest alone is not sufficient: a run that
|
|
170
|
+
// failed on a file before 2026-08-20 recorded it as up to date anyway
|
|
171
|
+
// (D034), so the diff reports it unchanged forever and the file never comes
|
|
172
|
+
// back. Without this, `mast_status` would correctly report such an index
|
|
173
|
+
// stale (`measureFreshness` reads the `files` stamps too) and every
|
|
174
|
+
// incremental run would do nothing about it — a state that never converges.
|
|
175
|
+
// On a full run every file is reindexed regardless, so this is scoped to the
|
|
176
|
+
// incremental path and costs one indexed `path` scan.
|
|
177
|
+
let toIndex;
|
|
178
|
+
if (options.incremental) {
|
|
179
|
+
const queued = new Set([...stale, ...added].map((e) => e.relativePath));
|
|
180
|
+
const indexedPaths = new Set((await db.selectFrom('files').select('path').execute()).map((r) => r.path));
|
|
181
|
+
const neverWritten = currentFiles.filter((e) => !queued.has(e.relativePath) && !indexedPaths.has(e.relativePath));
|
|
182
|
+
toIndex = [...stale, ...added, ...neverWritten];
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
toIndex = currentFiles;
|
|
186
|
+
}
|
|
187
|
+
let filesIndexed = 0;
|
|
188
|
+
/**
|
|
189
|
+
* Files parsed and queued for a write. NOT `filesIndexed` — a queued file
|
|
190
|
+
* whose write then fails never lands, and conflating the two is what made
|
|
191
|
+
* `files: N indexed` and `files_indexed: N` overstate the index (D038).
|
|
192
|
+
* Kept solely so `onProgress` still advances during the parse phase, which
|
|
193
|
+
* is where the wall-clock is spent on a large batch.
|
|
194
|
+
*/
|
|
195
|
+
let filesQueued = 0;
|
|
196
|
+
let parseErrors = 0;
|
|
197
|
+
let writeErrors = 0;
|
|
198
|
+
let staleWriteRejections = 0;
|
|
199
|
+
let chunksAdded = 0;
|
|
200
|
+
let filesStable = 0;
|
|
201
|
+
/**
|
|
202
|
+
* Relative paths this run attempted and did not get into the index — a parse
|
|
203
|
+
* that threw, or a write that failed. They are withheld from the manifest at
|
|
204
|
+
* finalise, so the next run's `diffManifest` sees them as new and retries
|
|
205
|
+
* them. Recording them as up-to-date is what made one transient failure a
|
|
206
|
+
* permanent hole (`docs/defects/LEDGER.md` D034).
|
|
207
|
+
*/
|
|
208
|
+
const failedPaths = new Set();
|
|
209
|
+
// Pass 1: parse, then write (chunks + graph + FTS together via
|
|
210
|
+
// populateFile), processed in batches so a batch's worth of parse results
|
|
211
|
+
// doesn't sit in memory indefinitely. SQLite writes are sequential —
|
|
212
|
+
// better-sqlite3 serialises internally, and M1 (§15.1) removed the
|
|
213
|
+
// separate concurrent chunk-store write phase that existed only because
|
|
214
|
+
// the spike's chunk store was a different file/connection from graph.db.
|
|
215
|
+
// F1: `structure.lock` now wraps only the write half of each batch (below),
|
|
216
|
+
// not the parse phase and not the whole loop — see the runIndex WHY-comment.
|
|
217
|
+
const LANCE_BATCH = 16;
|
|
218
|
+
const edgeDataByFile = new Map();
|
|
219
|
+
// Phase accumulators. Summed across batches rather than sampled, because the parse/write
|
|
220
|
+
// interleave runs once per 16-file batch and a single stamp would capture only one of them.
|
|
221
|
+
const phase = { walk: 0, parse: 0, write: 0, edges: 0, finalise: 0 };
|
|
222
|
+
// Everything from `startMs` to here is the walk and manifest diff.
|
|
223
|
+
phase.walk = Date.now() - startMs;
|
|
224
|
+
for (let i = 0; i < toIndex.length; i += LANCE_BATCH) {
|
|
225
|
+
const batch = toIndex.slice(i, i + LANCE_BATCH);
|
|
226
|
+
const batchParseStart = Date.now();
|
|
227
|
+
// Parse phase — synchronous tree-sitter, cannot be parallelised without
|
|
228
|
+
// workers, and does not touch the graph db / chunk store, so it runs
|
|
229
|
+
// UNLOCKED. This is exactly the window in which a concurrent JIT
|
|
230
|
+
// re-parse of a not-yet-reached file can acquire structure.lock.
|
|
231
|
+
const parsed = [];
|
|
232
|
+
for (const entry of batch) {
|
|
233
|
+
try {
|
|
234
|
+
// F12: stat IMMEDIATELY BEFORE doExtract, not after — mirrors
|
|
235
|
+
// mcp/staleness.ts's JIT path (which has always stat'd before
|
|
236
|
+
// parsing). This is invariant 1 from the runIndex WHY-comment:
|
|
237
|
+
// `stamp <= mtime-at-content-read`. Stat'ing AFTER doExtract (the
|
|
238
|
+
// prior ordering) let a concurrent edit land in between, stamping
|
|
239
|
+
// this row with a mtime NEWER than the content that was actually
|
|
240
|
+
// parsed — permanently disarming staleness detection for the file
|
|
241
|
+
// (F12), and no lock can fix that ordering because editors never
|
|
242
|
+
// take structure.lock.
|
|
243
|
+
const preParseMtime = statMtimeSecondsOrFallback(entry.path, entry.mtime);
|
|
244
|
+
const result = doExtract(entry.path, config.resolved_project_root, config.context_lines, config.chunk_split_threshold, config.markdown_heading_depth);
|
|
245
|
+
// §7.1 stability skip: a file whose mtime changed but whose chunked
|
|
246
|
+
// content is identical (e.g. `git checkout` rewriting the same bytes)
|
|
247
|
+
// is re-parsed but need not be re-written. Incremental only; the
|
|
248
|
+
// check is conservative and bails to a full rewrite on any doubt.
|
|
249
|
+
if (options.incremental && await isFileUnchanged(db, chunkStore, entry.relativePath, result)) {
|
|
250
|
+
filesStable++;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
parsed.push({ entry, result, mtime: preParseMtime });
|
|
254
|
+
// `filesIndexed` / `chunksAdded` are NOT incremented here. Both are
|
|
255
|
+
// counted in the write loop below, once `populateFile` reports the
|
|
256
|
+
// write actually landed — see D038.
|
|
257
|
+
filesQueued++;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
process.stderr.write(`[mast] WARN: parse error in ${entry.path}: ${String(err)}\n`);
|
|
262
|
+
parseErrors++;
|
|
263
|
+
failedPaths.add(entry.relativePath);
|
|
264
|
+
}
|
|
265
|
+
options.onProgress?.(filesQueued + filesStable + parseErrors, toIndex.length);
|
|
266
|
+
}
|
|
267
|
+
phase.parse += Date.now() - batchParseStart;
|
|
268
|
+
const batchWriteStart = Date.now();
|
|
269
|
+
// E1-FTS `lock` span — see WriteSpansMs.lock. Acquisition is timed from
|
|
270
|
+
// here to the first statement of the callback, release from the callback's
|
|
271
|
+
// last statement to `withLock`'s return; both directly, neither derived.
|
|
272
|
+
const lockAcquireStart = options.writeSpans === undefined ? 0 : performance.now();
|
|
273
|
+
let lockBodyEnd = 0;
|
|
274
|
+
// Write phase — LOCKED, scoped to this batch only (F1). Bounds how long
|
|
275
|
+
// any caller (including a concurrent JIT re-parse) can be starved to
|
|
276
|
+
// roughly one batch's writes, not the whole run.
|
|
277
|
+
await withLock(config.resolved_state_dir, 'structure', lockOptions, async () => {
|
|
278
|
+
if (options.writeSpans !== undefined) {
|
|
279
|
+
options.writeSpans.lock += performance.now() - lockAcquireStart;
|
|
280
|
+
}
|
|
281
|
+
// populateFile now writes chunks + symbols + edges + imports + FTS in
|
|
282
|
+
// ONE per-file transaction (M1, §15.1) — a failure anywhere in it rolls
|
|
283
|
+
// back everything for that file, so there is no separate "chunk store
|
|
284
|
+
// write" step to run before this loop any more.
|
|
285
|
+
//
|
|
286
|
+
// A write failure is counted as `writeErrors`, NEVER `parseErrors` — the
|
|
287
|
+
// file parsed correctly; only its storage failed. Conflating the two
|
|
288
|
+
// (the original defect, GITNEXUS_COMPARISON.md §15.3 item 3) hid a
|
|
289
|
+
// storage failure behind a "parse error" label, which is the wrong
|
|
290
|
+
// diagnosis for anyone debugging it. Logged at ERROR (not WARN): the
|
|
291
|
+
// file's chunks/symbols/edges/imports/FTS rows are skipped entirely —
|
|
292
|
+
// this is not a soft warning.
|
|
293
|
+
//
|
|
294
|
+
// Deliberately NOT aborted and NOT quarantined per-chunk: one
|
|
295
|
+
// pathological file must not turn a monorepo index into a total outage,
|
|
296
|
+
// and partial-chunk recovery is out of scope for this fix. The file
|
|
297
|
+
// still fails to index — it just fails loudly and is correctly
|
|
298
|
+
// classified, instead of silently amputating the file under a WARN and
|
|
299
|
+
// a miscounted `parseErrors`.
|
|
300
|
+
for (const { entry, result, mtime } of parsed) {
|
|
301
|
+
// `mtime` is the PRE-parse stamp captured in the parse loop above
|
|
302
|
+
// (F12 invariant 1) — carried through untouched, NOT re-stat'd here.
|
|
303
|
+
// Re-statting at this point (the prior ordering) is exactly the bug:
|
|
304
|
+
// it reads disk state AFTER doExtract already read the content, so a
|
|
305
|
+
// concurrent edit landing in between gets stamped as newer-than-content.
|
|
306
|
+
try {
|
|
307
|
+
const { chunksRemoved: removed, written } = await populateFile(db, {
|
|
308
|
+
filePath: entry.relativePath,
|
|
309
|
+
language: result.language,
|
|
310
|
+
mtime,
|
|
311
|
+
chunks: result.chunks,
|
|
312
|
+
imports: result.imports,
|
|
313
|
+
symbols: result.symbols,
|
|
314
|
+
identifierRows: result.identifierRows,
|
|
315
|
+
}, populateOptions);
|
|
316
|
+
if (!written) {
|
|
317
|
+
// Monotonic write-guard (F12 invariant 2, graph/populate.ts)
|
|
318
|
+
// rejected this write: some other writer — a concurrent JIT
|
|
319
|
+
// refresh, or a later batch in this same run reprocessing the
|
|
320
|
+
// file — already stamped a NEWER mtime. The DB row is already at
|
|
321
|
+
// least as fresh as what this parse would have written, so this
|
|
322
|
+
// is correct behaviour, not a failure, but it must stay visible
|
|
323
|
+
// rather than look like an ordinary successful write (the
|
|
324
|
+
// `writeErrors` precedent).
|
|
325
|
+
staleWriteRejections++;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
chunksRemoved += removed;
|
|
329
|
+
// Counted HERE, not in the parse loop: this is the first point at
|
|
330
|
+
// which the file is known to be in the index (D038). A stale-write
|
|
331
|
+
// rejection `continue`s above and a write failure `continue`s below,
|
|
332
|
+
// so neither reaches this line.
|
|
333
|
+
filesIndexed++;
|
|
334
|
+
chunksAdded += result.chunks.length;
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
process.stderr.write(`[mast] ERROR: chunk store write failed for ${entry.path} — file will be absent from the index: ${String(err)}\n`);
|
|
338
|
+
writeErrors++;
|
|
339
|
+
failedPaths.add(entry.relativePath);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
edgeDataByFile.set(entry.relativePath, result);
|
|
343
|
+
}
|
|
344
|
+
lockBodyEnd = performance.now();
|
|
345
|
+
});
|
|
346
|
+
if (options.writeSpans !== undefined) {
|
|
347
|
+
options.writeSpans.lock += performance.now() - lockBodyEnd;
|
|
348
|
+
}
|
|
349
|
+
phase.write += Date.now() - batchWriteStart;
|
|
350
|
+
}
|
|
351
|
+
// Pass 2: insert edges and star re-export rows now that all files' symbols
|
|
352
|
+
// and file rows exist (both may reference files indexed later in pass 1).
|
|
353
|
+
// Batched the same way as pass 1 and for the same reason (F1) — each batch
|
|
354
|
+
// gets its own short lock instead of one lock spanning every file's edges.
|
|
355
|
+
// Safe because ALL of pass 1 (every batch above) has already completed by
|
|
356
|
+
// the time this loop starts, so every cross-file edge target this run
|
|
357
|
+
// could produce already exists, independent of how pass 2's own lock
|
|
358
|
+
// acquisitions are chunked.
|
|
359
|
+
const edgeEntries = [...edgeDataByFile];
|
|
360
|
+
const edgesStart = Date.now();
|
|
361
|
+
for (let i = 0; i < edgeEntries.length; i += LANCE_BATCH) {
|
|
362
|
+
const batch = edgeEntries.slice(i, i + LANCE_BATCH);
|
|
363
|
+
await withLock(config.resolved_state_dir, 'structure', lockOptions, async () => {
|
|
364
|
+
for (const [filePath, data] of batch) {
|
|
365
|
+
await insertEdges(db, filePath, data.edges);
|
|
366
|
+
await insertReExportFiles(db, filePath, data.starReExports);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
phase.edges = Date.now() - edgesStart;
|
|
371
|
+
const finaliseStart = Date.now();
|
|
372
|
+
// Final phase — manifest + index.json, under one more short lock. See
|
|
373
|
+
// invariant 2 in the runIndex WHY-comment for why this re-stats every file
|
|
374
|
+
// instead of reusing `currentFiles`' run-start walk snapshot, and why it
|
|
375
|
+
// needs the lock at all (to not race a JIT write that is already in flight).
|
|
376
|
+
await withLock(config.resolved_state_dir, 'structure', lockOptions, async () => {
|
|
377
|
+
// Files this run failed on are withheld: a manifest entry asserts "the
|
|
378
|
+
// index reflects this file at this mtime", which is false for them. Leaving
|
|
379
|
+
// the entry out makes the next run see the file as new and retry it, and —
|
|
380
|
+
// because a file with no manifest entry is also what `measureFreshness`
|
|
381
|
+
// counts as unindexed — keeps both status surfaces honest in the meantime.
|
|
382
|
+
// A file that was never attempted (unchanged on an incremental run) is not
|
|
383
|
+
// in `failedPaths` and keeps its entry, as it should.
|
|
384
|
+
const freshEntries = currentFiles
|
|
385
|
+
.filter((entry) => !failedPaths.has(entry.relativePath))
|
|
386
|
+
.map((entry) => ({
|
|
387
|
+
...entry,
|
|
388
|
+
mtime: statMtimeSecondsOrFallback(entry.path, entry.mtime),
|
|
389
|
+
}));
|
|
390
|
+
const newManifest = buildManifest(freshEntries);
|
|
391
|
+
writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2));
|
|
392
|
+
const chunkCount = await chunkStore.chunkCount();
|
|
393
|
+
const meta = {
|
|
394
|
+
schema_version: CURRENT_SCHEMA_VERSION,
|
|
395
|
+
last_indexed: new Date().toISOString(),
|
|
396
|
+
file_count: currentFiles.length,
|
|
397
|
+
chunk_count: chunkCount,
|
|
398
|
+
parse_errors: parseErrors > 0 ? parseErrors : undefined,
|
|
399
|
+
write_errors: writeErrors > 0 ? writeErrors : undefined,
|
|
400
|
+
};
|
|
401
|
+
writeFileSync(join(config.resolved_state_dir, 'index.json'), JSON.stringify(meta, null, 2));
|
|
402
|
+
});
|
|
403
|
+
phase.finalise = Date.now() - finaliseStart;
|
|
404
|
+
// Read the tuning pragmas back BEFORE the connection closes — they are
|
|
405
|
+
// connection-scoped state, not file state, and are unrecoverable afterwards.
|
|
406
|
+
const appliedPragmas = {
|
|
407
|
+
cache_size: readPragmaValue('cache_size', (await sql `PRAGMA cache_size`.execute(db)).rows),
|
|
408
|
+
mmap_size: readPragmaValue('mmap_size', (await sql `PRAGMA mmap_size`.execute(db)).rows),
|
|
409
|
+
};
|
|
410
|
+
await db.destroy();
|
|
411
|
+
// SqliteChunkStore (default path) wraps this same `db` connection (§15.1) —
|
|
412
|
+
// no separate handle to close. `chunkStoreOverride`, when supplied, is
|
|
413
|
+
// owned by the caller regardless.
|
|
414
|
+
return {
|
|
415
|
+
// Files skipped = never-parsed (mtime unchanged) + parsed-but-stable.
|
|
416
|
+
filesIndexed,
|
|
417
|
+
filesSkipped: currentFiles.length - toIndex.length + filesStable,
|
|
418
|
+
chunksAdded,
|
|
419
|
+
chunksRemoved,
|
|
420
|
+
parseErrors,
|
|
421
|
+
writeErrors,
|
|
422
|
+
staleWriteRejections,
|
|
423
|
+
// Drained, not read: the resolver is cached for the process lifetime and
|
|
424
|
+
// serves every run, so each run must clear what it reported.
|
|
425
|
+
miscasedImports: getImportResolver(config.resolved_project_root).drainMiscased(),
|
|
426
|
+
durationMs: Date.now() - startMs,
|
|
427
|
+
phaseMs: phase,
|
|
428
|
+
appliedPragmas,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* True when `result` is equivalent to what is already stored for `filePath`, so
|
|
433
|
+
* re-writing it would be redundant (§7.1). Conservative:
|
|
434
|
+
*
|
|
435
|
+
* - bails (returns false) if the file has any `block` chunk, whose content is
|
|
436
|
+
* not captured by a symbol hash and so cannot be verified here;
|
|
437
|
+
* - requires the exact same set of chunk ids (catches additions, removals, and
|
|
438
|
+
* declarations that moved to a different line);
|
|
439
|
+
* - requires every stored chunk's CONTENT to match byte-for-byte;
|
|
440
|
+
* - requires every symbol's declaration_hash AND body_hash to match the stored
|
|
441
|
+
* symbol of the same name+line;
|
|
442
|
+
* - requires the stored `imports` rows to match module-for-module.
|
|
443
|
+
*
|
|
444
|
+
* Any mismatch — or any uncertainty — falls through to a full rewrite.
|
|
445
|
+
*
|
|
446
|
+
* D030 — WHY THE LAST TWO CHECKS EXIST. This function decides that a write can
|
|
447
|
+
* be skipped, so its equivalence check must cover everything the write covers.
|
|
448
|
+
* It originally compared chunk IDs and symbol hashes only, and neither moves
|
|
449
|
+
* when an edit lands OUTSIDE every symbol body. An `import` statement always
|
|
450
|
+
* does. `populateFile` writes the `imports` table, and a skipped file is also
|
|
451
|
+
* never added to `edgeDataByFile`, so pass 2 never re-resolves its edges —
|
|
452
|
+
* which made an import-only edit invisible in both places at once. It never
|
|
453
|
+
* self-healed: the finalise phase stamps the manifest from a fresh re-stat of
|
|
454
|
+
* EVERY walked file, written or not, so the next run did not see the file as
|
|
455
|
+
* stale either. Measured: after moving a declaration and repointing its
|
|
456
|
+
* importer, `mast index --incremental` reported `0 indexed, 2 skipped`, the
|
|
457
|
+
* `imports` row still read `./alpha.js` after that path had stopped existing,
|
|
458
|
+
* and `mast_callers` returned `verified_callers: []` for a function with a
|
|
459
|
+
* live caller.
|
|
460
|
+
*
|
|
461
|
+
* Chunk IDs deliberately do NOT change on a content edit (`dedupeChunkIds`,
|
|
462
|
+
* ast/extract.ts — `vectorKey` depends on that), so ID equality can never
|
|
463
|
+
* stand in for content equality here.
|
|
464
|
+
*/
|
|
465
|
+
async function isFileUnchanged(db, chunkStore, filePath, result) {
|
|
466
|
+
// `block` chunks carry no symbol hash, and `doc` chunks carry no symbols at
|
|
467
|
+
// all — neither can be content-verified here, so both bail to a full rewrite.
|
|
468
|
+
if (result.chunks.some((c) => c.chunk_type === 'block' || c.chunk_type === 'doc'))
|
|
469
|
+
return false;
|
|
470
|
+
// Structure: identical set of chunk ids, each carrying identical content.
|
|
471
|
+
// The content comparison is what catches an edit outside every symbol body
|
|
472
|
+
// — the import line, a top-level comment, a re-export (D030).
|
|
473
|
+
const storedChunks = await chunkStore.getChunksByFilePath(filePath);
|
|
474
|
+
if (storedChunks.length !== result.chunks.length)
|
|
475
|
+
return false;
|
|
476
|
+
const storedContentById = new Map(storedChunks.map((c) => [c.chunk_id, c.content]));
|
|
477
|
+
if (!result.chunks.every((c) => storedContentById.get(c.chunk_id) === c.content))
|
|
478
|
+
return false;
|
|
479
|
+
// Content: identical symbol hash signature.
|
|
480
|
+
const storedSymbols = await db
|
|
481
|
+
.selectFrom('symbols as s')
|
|
482
|
+
.innerJoin('files as f', 'f.id', 's.file_id')
|
|
483
|
+
.select(['s.name', 's.line', 's.declaration_hash', 's.body_hash'])
|
|
484
|
+
.where('f.path', '=', filePath)
|
|
485
|
+
.execute();
|
|
486
|
+
if (symbolSignature(result.symbols) !== symbolSignature(storedSymbols))
|
|
487
|
+
return false;
|
|
488
|
+
// Imports: an import statement can sit outside every chunk entirely (a file
|
|
489
|
+
// whose only chunks are its declarations, with `context_lines: 0`), so the
|
|
490
|
+
// chunk-content check above does not subsume this one.
|
|
491
|
+
const storedImports = await db
|
|
492
|
+
.selectFrom('imports as i')
|
|
493
|
+
.innerJoin('files as f', 'f.id', 'i.file_id')
|
|
494
|
+
.select(['i.module', 'i.symbols', 'i.is_external', 'i.resolved_path'])
|
|
495
|
+
.where('f.path', '=', filePath)
|
|
496
|
+
.execute();
|
|
497
|
+
return importSignature(result.imports) === importSignature(storedImports);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Order-independent signature of a file's import set, over exactly the columns
|
|
501
|
+
* `populateFile` writes to `imports`. The extracted and stored shapes differ
|
|
502
|
+
* (camelCase vs snake_case, boolean vs 0/1, array vs JSON text), so both are
|
|
503
|
+
* normalised to one canonical string rather than compared field by field.
|
|
504
|
+
*/
|
|
505
|
+
function importSignature(imports) {
|
|
506
|
+
return imports
|
|
507
|
+
.map((i) => {
|
|
508
|
+
const symbols = typeof i.symbols === 'string' ? i.symbols : JSON.stringify(i.symbols ?? []);
|
|
509
|
+
const external = i.isExternal ?? i.is_external === 1;
|
|
510
|
+
const resolved = i.resolvedPath ?? i.resolved_path ?? '';
|
|
511
|
+
return `${i.module}|${symbols}|${external ? 1 : 0}|${resolved}`;
|
|
512
|
+
})
|
|
513
|
+
.sort()
|
|
514
|
+
.join('\n');
|
|
515
|
+
}
|
|
516
|
+
/** Order-independent signature of a symbol set's identity + stability hashes. */
|
|
517
|
+
function symbolSignature(symbols) {
|
|
518
|
+
return symbols
|
|
519
|
+
.map((s) => {
|
|
520
|
+
const decl = s.declarationHash ?? s.declaration_hash ?? '';
|
|
521
|
+
const body = s.bodyHash ?? s.body_hash ?? '';
|
|
522
|
+
return `${s.name}|${s.line}|${decl}|${body}`;
|
|
523
|
+
})
|
|
524
|
+
.sort()
|
|
525
|
+
.join('\n');
|
|
526
|
+
}
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
// Freshness diagnostics (mast_status, `mast status`)
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
/**
|
|
531
|
+
* Derive the `freshness_cause` discriminator (§9 mast_status) from the stale
|
|
532
|
+
* file count. Stage 7.2 (IMPLEMENTATION_PLAN.md "Stage 7: Vector-store
|
|
533
|
+
* deletion") dropped the second parameter this used to take
|
|
534
|
+
* (`pendingEmbeddings`) along with the `'embedding_backlog'`/`'both'` cases —
|
|
535
|
+
* the Phase 2 embedder that could produce a backlog distinct from Phase 1
|
|
536
|
+
* staleness was excised in Stage 7.1, so a two-cause signature asserted a
|
|
537
|
+
* distinction the code could no longer draw.
|
|
538
|
+
*/
|
|
539
|
+
export function freshnessCause(staleFiles) {
|
|
540
|
+
return staleFiles > 0 ? 'phase1_stale' : null;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Load `index.json` from the state directory, or return null if absent.
|
|
544
|
+
*
|
|
545
|
+
* Unvalidated cast, not a zod parse — an `index.json` written before Stage
|
|
546
|
+
* 7.2 (IMPLEMENTATION_PLAN.md "Stage 7: Vector-store deletion") may still
|
|
547
|
+
* carry the removed `model` field; it rides along as an untyped extra key
|
|
548
|
+
* and is simply never read, so old files load without error.
|
|
549
|
+
*/
|
|
550
|
+
export function loadIndexMeta(stateDir) {
|
|
551
|
+
const metaPath = join(stateDir, 'index.json');
|
|
552
|
+
if (!existsSync(metaPath))
|
|
553
|
+
return null;
|
|
554
|
+
return JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
555
|
+
}
|
|
556
|
+
/** Write `index.json` to the state directory. */
|
|
557
|
+
export function writeIndexMeta(stateDir, meta) {
|
|
558
|
+
writeFileSync(join(stateDir, 'index.json'), JSON.stringify(meta, null, 2));
|
|
559
|
+
}
|
|
560
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/indexer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { MastConfig } from '../ast/types.js';
|
|
2
|
+
export interface FileEntry {
|
|
3
|
+
/** Absolute path to the file. */
|
|
4
|
+
readonly path: string;
|
|
5
|
+
/** Relative path from project root (used as the stored `file_path`). */
|
|
6
|
+
readonly relativePath: string;
|
|
7
|
+
/** File mtime in unix seconds. */
|
|
8
|
+
readonly mtime: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Convert a glob pattern to a RegExp.
|
|
12
|
+
* `**\/` — matches zero or more leading directories
|
|
13
|
+
* `**` — matches any sequence including `/`
|
|
14
|
+
* `*` — matches any sequence not containing `/`
|
|
15
|
+
* `?` — matches a single non-`/` character
|
|
16
|
+
*
|
|
17
|
+
* Lives in the walker (file-discovery domain) because both watch mode and the
|
|
18
|
+
* MCP tools' `file_pattern` filters need the same glob semantics — and because
|
|
19
|
+
* `walkProject` below hands the same patterns to fast-glob, so this function's
|
|
20
|
+
* output has to agree with fast-glob's for the same pattern. It is checked
|
|
21
|
+
* against fast-glob directly by `__tests__/glob-to-regex.test.ts`.
|
|
22
|
+
*
|
|
23
|
+
* Written as a single left-to-right scan rather than a chain of `.replace`
|
|
24
|
+
* calls. The chain was re-entrant: each rule rewrote the output of the ones
|
|
25
|
+
* before it, so `**` → `.*` left a `*` for the `*` rule to turn into
|
|
26
|
+
* `.[^/]*`, and `**\/` → `(.+/)?` left a `?` for the `?` rule to turn into
|
|
27
|
+
* `(.+/)[^/]`. Every shipped default in `exclude_patterns` (`store/config.ts`)
|
|
28
|
+
* contains one of those two sequences, so every one of them compiled to a
|
|
29
|
+
* regex that matched neither the directory it named nor its contents.
|
|
30
|
+
*/
|
|
31
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
32
|
+
/**
|
|
33
|
+
* Walk the project and return all indexable files.
|
|
34
|
+
*
|
|
35
|
+
* Applies `file_extensions` allowlist and `exclude_patterns` denylist from
|
|
36
|
+
* config. Results are sorted lexicographically by `relativePath` (D1,
|
|
37
|
+
* IMPLEMENTATION_PLAN.md Stage 4): fast-glob returns filesystem order, which
|
|
38
|
+
* varies between identical runs — and because edge insertion order feeds the
|
|
39
|
+
* bare-name fallback in `insertEdges`' name resolution, two identical index
|
|
40
|
+
* runs produced edge sets differing by ±4/3,940 (§15.5). Sorting here makes
|
|
41
|
+
* every downstream consumer (index order, manifest, edge insertion)
|
|
42
|
+
* deterministic at the source. Callers still must not attach SEMANTIC meaning
|
|
43
|
+
* to the order — the guarantee is reproducibility, not priority.
|
|
44
|
+
*/
|
|
45
|
+
export declare function walkProject(config: MastConfig): Promise<FileEntry[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Build a `{ relativePath → mtime }` snapshot map from `walkProject` results.
|
|
48
|
+
* Stored as `file_manifest.json` at the end of each index run.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildManifest(entries: readonly FileEntry[]): Record<string, number>;
|
|
51
|
+
/**
|
|
52
|
+
* Compare the current filesystem scan against the previous manifest.
|
|
53
|
+
* Returns sets of paths for changed, new, and deleted files.
|
|
54
|
+
*/
|
|
55
|
+
export declare function diffManifest(current: readonly FileEntry[], previousManifest: Record<string, number>): {
|
|
56
|
+
stale: FileEntry[];
|
|
57
|
+
added: FileEntry[];
|
|
58
|
+
deleted: string[];
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=walker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walker.d.ts","sourceRoot":"","sources":["../../src/indexer/walker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwBnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAgC1E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMnF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACvC;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAoBA"}
|