@plebeiantech/lat.md 0.12.3-fork.16 → 0.12.3-fork.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/cli/index.js +12 -6
- package/dist/src/cli/init.js +3 -4
- package/dist/src/cli/reindex.js +4 -8
- package/dist/src/cli/search.d.ts +3 -2
- package/dist/src/cli/search.js +69 -59
- package/dist/src/format.d.ts +5 -0
- package/dist/src/format.js +32 -4
- package/dist/src/lattice-model.d.ts +8 -1
- package/dist/src/lattice-model.js +4 -1
- package/dist/src/lattice.d.ts +2 -2
- package/dist/src/lattice.js +17 -34
- package/dist/src/markdown-analysis.d.ts +11 -0
- package/dist/src/markdown-analysis.js +31 -1
- package/dist/src/mcp/server.js +6 -6
- package/dist/src/parser-cache.d.ts +1 -1
- package/dist/src/parser-cache.js +1 -1
- package/dist/src/search/cache.d.ts +3 -0
- package/dist/src/search/cache.js +71 -0
- package/dist/src/search/chunks.d.ts +23 -0
- package/dist/src/search/chunks.js +197 -0
- package/dist/src/search/db.d.ts +27 -16
- package/dist/src/search/db.js +108 -38
- package/dist/src/search/index.d.ts +5 -2
- package/dist/src/search/index.js +173 -69
- package/dist/src/search/lexical.d.ts +6 -0
- package/dist/src/search/lexical.js +36 -0
- package/dist/src/search/lock.d.ts +2 -0
- package/dist/src/search/lock.js +23 -0
- package/dist/src/search/query.d.ts +2 -2
- package/dist/src/search/query.js +24 -9
- package/dist/src/search/search.d.ts +13 -8
- package/dist/src/search/search.js +155 -23
- package/dist/src/search/types.d.ts +31 -0
- package/dist/src/search/types.js +1 -0
- package/dist/src/source-formats.d.ts +1 -1
- package/dist/src/source-formats.js +2 -0
- package/dist/src/source-parser.js +197 -1
- package/dist/src/view/client/assets/{GraphView-BuZg_wrj.js → GraphView-BWlRdQM4.js} +2 -2
- package/dist/src/view/client/assets/{MarkdownEditor-BKqJtZYv.js → MarkdownEditor-MW-Ck1O0.js} +1 -1
- package/dist/src/view/client/assets/{abnfDiagram-VCTEODGH-CAu3KwfO.js → abnfDiagram-VCTEODGH-Bn3RshMV.js} +1 -1
- package/dist/src/view/client/assets/{arc-CFnPZhKM.js → arc-En9tdFQF.js} +1 -1
- package/dist/src/view/client/assets/{architectureDiagram-5GKGNRK7-Cf9Kd0OA.js → architectureDiagram-5GKGNRK7-B-9J1VAM.js} +1 -1
- package/dist/src/view/client/assets/{blockDiagram-I7D4REHJ-dPn7g_vm.js → blockDiagram-I7D4REHJ-QlznwalP.js} +1 -1
- package/dist/src/view/client/assets/{c4Diagram-7LVT6UL2-DdZrwaPd.js → c4Diagram-7LVT6UL2-Bp2y8B4_.js} +1 -1
- package/dist/src/view/client/assets/channel-uy0fFvcW.js +1 -0
- package/dist/src/view/client/assets/{chunk-2Q5K7J3B-C42JuteH.js → chunk-2Q5K7J3B-CnBKL-lm.js} +1 -1
- package/dist/src/view/client/assets/{chunk-5VM5RSS4-BKK3z8Zy.js → chunk-5VM5RSS4-LLYMi_n6.js} +1 -1
- package/dist/src/view/client/assets/{chunk-F27PBJKO-qZl_uKcW.js → chunk-F27PBJKO-VIeO7VF0.js} +1 -1
- package/dist/src/view/client/assets/{chunk-IMKFNOWR-Bs4Rnoj_.js → chunk-IMKFNOWR-D5i67DVD.js} +1 -1
- package/dist/src/view/client/assets/{chunk-JWPE2WC7-BpxgV2rA.js → chunk-JWPE2WC7-D4qAtoL3.js} +1 -1
- package/dist/src/view/client/assets/{chunk-POPQ4Y6H-hIynofdE.js → chunk-POPQ4Y6H-BMzOyiW_.js} +1 -1
- package/dist/src/view/client/assets/{chunk-SVP7TREG-t3gKMqeu.js → chunk-SVP7TREG-DWy5nbfp.js} +1 -1
- package/dist/src/view/client/assets/{chunk-TICWLB2K-B2x394mh.js → chunk-TICWLB2K-CVqCANFB.js} +1 -1
- package/dist/src/view/client/assets/{chunk-XXDRQBXY-CHaVKDsH.js → chunk-XXDRQBXY-Bp23h6Tf.js} +1 -1
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-DbSEqjA5.js +1 -0
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-DbSEqjA5.js +1 -0
- package/dist/src/view/client/assets/{cose-bilkent-JH36ORCC-DE2-Jgqu.js → cose-bilkent-JH36ORCC-BCw7fCC2.js} +1 -1
- package/dist/src/view/client/assets/{cynefin-OW5HDTMX-BsEtOvRy.js → cynefin-OW5HDTMX--lYN4N9m.js} +1 -1
- package/dist/src/view/client/assets/{cynefinDiagram-5FMLGOSQ-BKYG1Xs8.js → cynefinDiagram-5FMLGOSQ-9sK5EV7v.js} +1 -1
- package/dist/src/view/client/assets/{dagre-GXQ25YYZ-hgrdNjl7.js → dagre-GXQ25YYZ-CoMdEFCq.js} +1 -1
- package/dist/src/view/client/assets/{diagram-S7CK7UJ4-VIx8zAxf.js → diagram-S7CK7UJ4-D13dyWdd.js} +1 -1
- package/dist/src/view/client/assets/{diagram-UQ7AKVKN-yfloehRm.js → diagram-UQ7AKVKN-PdwLPPPA.js} +1 -1
- package/dist/src/view/client/assets/{diagram-VSXAHHWV-BT_dA4Q9.js → diagram-VSXAHHWV-CtWWa_A7.js} +1 -1
- package/dist/src/view/client/assets/{diagram-VX7I27RA-Gfmwxx6u.js → diagram-VX7I27RA-BeCYcSU-.js} +1 -1
- package/dist/src/view/client/assets/{diagram-Z3DM3KII-C2yM7EOo.js → diagram-Z3DM3KII-DiWabOBM.js} +1 -1
- package/dist/src/view/client/assets/{ebnfDiagram-PWID7BFC-Bkb_Lj59.js → ebnfDiagram-PWID7BFC-CZQ3MD_K.js} +1 -1
- package/dist/src/view/client/assets/{erDiagram-RLTQ6QDP-C7wQHPye.js → erDiagram-RLTQ6QDP-B1AhXIrP.js} +1 -1
- package/dist/src/view/client/assets/{flowDiagram-HODETNUW-CXoKr3oI.js → flowDiagram-HODETNUW-Bhr0JM6P.js} +1 -1
- package/dist/src/view/client/assets/{ganttDiagram-EL5Y4UJY-DqqXKgLa.js → ganttDiagram-EL5Y4UJY-CbP8iAkg.js} +1 -1
- package/dist/src/view/client/assets/{gitGraphDiagram-WWUBYQGX-D_DAhSgZ.js → gitGraphDiagram-WWUBYQGX-B9uuGdLF.js} +1 -1
- package/dist/src/view/client/assets/{index-DAxFk0q-.css → index-DG0Ez15D.css} +1 -1
- package/dist/src/view/client/assets/index-nBmbYfun.js +13 -0
- package/dist/src/view/client/assets/{infoDiagram-27XIBGKW-B50EOats.js → infoDiagram-27XIBGKW-BrfwQ_Cq.js} +1 -1
- package/dist/src/view/client/assets/{ishikawaDiagram-5VMMS53U-D1rny9gj.js → ishikawaDiagram-5VMMS53U-CHtJ36re.js} +1 -1
- package/dist/src/view/client/assets/{journeyDiagram-3NMN7TZE-B5UD2Xo4.js → journeyDiagram-3NMN7TZE-BChcgX1s.js} +1 -1
- package/dist/src/view/client/assets/{kanban-definition-UXKFOSKX-CTgey9zw.js → kanban-definition-UXKFOSKX-BnwFsKiB.js} +1 -1
- package/dist/src/view/client/assets/{layout-4X2n4os-.js → layout-Dnd0pLdb.js} +1 -1
- package/dist/src/view/client/assets/{linear-D0RIBXsp.js → linear-CFhW2ozn.js} +1 -1
- package/dist/src/view/client/assets/{mermaid.core-BAnH8y-D.js → mermaid.core--OP6lXtW.js} +5 -5
- package/dist/src/view/client/assets/{mindmap-definition-YA3MSWOX-WiFsh3Jg.js → mindmap-definition-YA3MSWOX-D51y2zbm.js} +1 -1
- package/dist/src/view/client/assets/{pegDiagram-XKGWAZYB-DCJOrckP.js → pegDiagram-XKGWAZYB--zyQJDvI.js} +1 -1
- package/dist/src/view/client/assets/{pieDiagram-E7YTZNPT-C78IkUE2.js → pieDiagram-E7YTZNPT-Cj8mvpJy.js} +1 -1
- package/dist/src/view/client/assets/{quadrantDiagram-AXDQQJYC-xZ6guleB.js → quadrantDiagram-AXDQQJYC-qvhJrnBf.js} +1 -1
- package/dist/src/view/client/assets/{railroadDiagram-O6MQD6OU-D3PwmgX0.js → railroadDiagram-O6MQD6OU-CtWvxpSo.js} +1 -1
- package/dist/src/view/client/assets/{requirementDiagram-BXWQKSXE-v-I8wLGH.js → requirementDiagram-BXWQKSXE-CBxen_KG.js} +1 -1
- package/dist/src/view/client/assets/{sankeyDiagram-P5KCCOFB-DBY-sdO7.js → sankeyDiagram-P5KCCOFB-B_fUkmfc.js} +1 -1
- package/dist/src/view/client/assets/{sequenceDiagram-WJ2MYXX4-D9HApBFi.js → sequenceDiagram-WJ2MYXX4-Dclv4p68.js} +1 -1
- package/dist/src/view/client/assets/{sizeCapture-INFHLROL-ERAytLnQ.js → sizeCapture-INFHLROL-GfG0sMTZ.js} +1 -1
- package/dist/src/view/client/assets/{stateDiagram-D77RDMKH-BKef_Qwv.js → stateDiagram-D77RDMKH-YQWPnzxU.js} +1 -1
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-gUj6Hzgl.js +1 -0
- package/dist/src/view/client/assets/{swimlanes-42K2YHIH-D7dS_tnF.js → swimlanes-42K2YHIH-5yiCveVS.js} +1 -1
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-DR7JhrxL.js +8 -0
- package/dist/src/view/client/assets/{timeline-definition-24CTP7MA-Di1UCseG.js → timeline-definition-24CTP7MA-BvAbB5Zs.js} +1 -1
- package/dist/src/view/client/assets/{vennDiagram-4TSXK5OY-g7Je3H2H.js → vennDiagram-4TSXK5OY-guC-r60n.js} +1 -1
- package/dist/src/view/client/assets/{wardleyDiagram-VM6X3IG4-C_diPEka.js → wardleyDiagram-VM6X3IG4-C0yXcU2x.js} +1 -1
- package/dist/src/view/client/assets/{xychartDiagram-S5SC5T6Z-C8uIQy_I.js → xychartDiagram-S5SC5T6Z-RPmkPNfK.js} +1 -1
- package/dist/src/view/client/index.html +2 -2
- package/dist/src/view/highlight.js +2 -0
- package/dist/src/view/markdown.js +15 -0
- package/dist/src/view/protocol.d.ts +7 -1
- package/dist/src/view/references.js +3 -3
- package/dist/src/view/repository.js +10 -2
- package/dist/src/view/search-result.js +7 -2
- package/dist/src/view/server-build.js +1 -1
- package/dist/src/view/server-deployment.js +2 -1
- package/dist/src/view/server-index-worker.js +1 -1
- package/dist/src/view/source-target.d.ts +2 -0
- package/dist/src/view/source-target.js +24 -0
- package/dist/src/view/store.js +3 -3
- package/dist/src/view/vercel-build.js +7 -0
- package/package.json +6 -3
- package/templates/AGENTS.md +1 -1
- package/templates/cursor-rules.md +1 -1
- package/templates/skill/SKILL.md +1 -1
- package/dist/src/view/client/assets/channel-C-byJRR7.js +0 -1
- package/dist/src/view/client/assets/classDiagram-ZZMXUADV-FlqGrEwv.js +0 -1
- package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-FlqGrEwv.js +0 -1
- package/dist/src/view/client/assets/index-DPuOWFj2.js +0 -10
- package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-BePfayWI.js +0 -1
- package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-DaqCEguT.js +0 -8
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ Alternatively, install `@plebeiantech/lat.md` via npm/mise, run `lat init` in yo
|
|
|
103
103
|
|
|
104
104
|
## How it works
|
|
105
105
|
|
|
106
|
-
Run `lat init` to scaffold a `lat.md/` directory, then write markdown files describing your architecture, business logic, test specs — whatever matters. Link between sections using `[[file#Section#Subsection]]` syntax. Link to source code symbols with `[[src/auth.ts#validateToken]]`. Annotate source code with `// @lat: [[section-id]]` (or `# @lat: [[section-id]]` in Python) comments to tie implementation back to concepts.
|
|
106
|
+
Run `lat init` to scaffold a `lat.md/` directory, then write markdown files describing your architecture, business logic, test specs — whatever matters. Link between sections using `[[file#Section#Subsection]]` syntax. Link to source code symbols with `[[src/auth.ts#validateToken]]`. Annotate source code with `// @lat: [[section-id]]` (or `# @lat: [[section-id]]` in Python and PHP) comments to tie implementation back to concepts.
|
|
107
107
|
|
|
108
108
|
```
|
|
109
109
|
my-project/
|
package/dist/src/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { dirname, join } from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { Command, InvalidArgumentError } from 'commander';
|
|
10
10
|
import { resolveCheckContext, resolveContext } from './context.js';
|
|
11
|
-
import { DEFAULT_SEARCH_LIMIT,
|
|
11
|
+
import { DEFAULT_SEARCH_LIMIT, DEFAULT_MIN_SIMILARITY, } from '../search/search.js';
|
|
12
12
|
function parsePort(value) {
|
|
13
13
|
if (!/^\d+$/.test(value)) {
|
|
14
14
|
throw new InvalidArgumentError('port must be an integer from 1 to 65535');
|
|
@@ -34,7 +34,7 @@ function configureUiRun(command) {
|
|
|
34
34
|
function parseSimilarityThreshold(value) {
|
|
35
35
|
const threshold = Number(value);
|
|
36
36
|
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
37
|
-
throw new InvalidArgumentError('
|
|
37
|
+
throw new InvalidArgumentError('min-similarity must be a number from 0 to 1');
|
|
38
38
|
}
|
|
39
39
|
return threshold;
|
|
40
40
|
}
|
|
@@ -339,11 +339,16 @@ program
|
|
|
339
339
|
});
|
|
340
340
|
program
|
|
341
341
|
.command('search')
|
|
342
|
-
.description('
|
|
342
|
+
.description('Hybrid search across lat.md sections')
|
|
343
343
|
.argument('[query]', 'search query in plain English')
|
|
344
344
|
.option('--limit <n>', `max results (default: ${DEFAULT_SEARCH_LIMIT})`, String(DEFAULT_SEARCH_LIMIT))
|
|
345
|
-
.option('--
|
|
346
|
-
|
|
345
|
+
.option('--preview <variant>', 'preview passage, intro, or both', (value) => {
|
|
346
|
+
if (!['passage', 'intro', 'both'].includes(value))
|
|
347
|
+
throw new InvalidArgumentError('preview must be passage, intro, or both');
|
|
348
|
+
return value;
|
|
349
|
+
}, 'passage')
|
|
350
|
+
.option('--debug', 'show retrieval scores and candidate diagnostics')
|
|
351
|
+
.option('--min-similarity <score>', `minimum cosine similarity score (default: ${DEFAULT_MIN_SIMILARITY})`, parseSimilarityThreshold)
|
|
347
352
|
.action(async (query, opts) => {
|
|
348
353
|
const ctx = resolveContext(program.opts());
|
|
349
354
|
const { searchCommand, cliProgress } = await import('./search.js');
|
|
@@ -351,7 +356,8 @@ program
|
|
|
351
356
|
const result = await searchCommand(ctx, query, {
|
|
352
357
|
limit: parseInt(opts.limit),
|
|
353
358
|
debug: opts.debug,
|
|
354
|
-
|
|
359
|
+
minSimilarity: opts.minSimilarity,
|
|
360
|
+
preview: opts.preview,
|
|
355
361
|
}, progress);
|
|
356
362
|
handleResult(result);
|
|
357
363
|
});
|
package/dist/src/cli/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import { findTemplatesDir } from './templates.js';
|
|
|
8
8
|
import { readAgentsTemplate, readCursorRulesTemplate, readPiExtensionTemplate, readOpenCodePluginTemplate, readSkillTemplate, } from './gen.js';
|
|
9
9
|
import { getLlmKey, getRepoEmbedding, setRepoEmbedding } from '../config.js';
|
|
10
10
|
import { makeStyler } from './context.js';
|
|
11
|
-
import { closeDb,
|
|
11
|
+
import { closeDb, getStoredModel, openDb } from '../search/db.js';
|
|
12
12
|
import { modelKey } from '../search/embedder.js';
|
|
13
13
|
import { reindexCommand } from './reindex.js';
|
|
14
14
|
import { INIT_VERSION, writeInitMeta, readInitVersion, readFileHash, contentHash, } from '../init-version.js';
|
|
@@ -668,11 +668,10 @@ async function setupCodex(root, latDir, hashes, ask, style) {
|
|
|
668
668
|
' Run /hooks in Codex to review and trust the project hooks.');
|
|
669
669
|
}
|
|
670
670
|
async function readStoredEmbeddingModel(latDir) {
|
|
671
|
-
if (!existsSync(join(latDir, '.cache', '
|
|
671
|
+
if (!existsSync(join(latDir, '.cache', 'search.db')))
|
|
672
672
|
return null;
|
|
673
|
-
const db = openDb(latDir);
|
|
673
|
+
const db = openDb(latDir, undefined, true);
|
|
674
674
|
try {
|
|
675
|
-
await ensureMeta(db);
|
|
676
675
|
return await getStoredModel(db);
|
|
677
676
|
}
|
|
678
677
|
finally {
|
package/dist/src/cli/reindex.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { writeIndex } from '../search/cache.js';
|
|
1
2
|
import readline from 'node:readline/promises';
|
|
2
|
-
import {
|
|
3
|
+
import { ensureMeta, setStoredModel, ensureSectionsSchema, dropSections, } from '../search/db.js';
|
|
3
4
|
import { embedderFromEnv, localEmbedder, modelKey, EmbeddingAuthError, } from '../search/embedder.js';
|
|
4
5
|
import { getLlmKey, getRepoEmbedding, setRepoEmbedding } from '../config.js';
|
|
5
6
|
import { indexSections } from '../search/index.js';
|
|
@@ -83,11 +84,9 @@ export async function reindexCommand(ctx, opts) {
|
|
|
83
84
|
embedder = await localEmbedder();
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
|
-
const db = openDb(ctx.latDir);
|
|
87
87
|
const interactive = ctx.mode === 'cli' && !!process.stderr.isTTY;
|
|
88
|
-
|
|
88
|
+
return writeIndex(ctx.latDir, undefined, true, async (db) => {
|
|
89
89
|
await ensureMeta(db);
|
|
90
|
-
await dropSections(db);
|
|
91
90
|
await ensureSectionsSchema(db, embedder.dimensions);
|
|
92
91
|
const label = `Reindexing with ${embedder.name}`;
|
|
93
92
|
// Interactive terminals get a live progress line driven per embed-chunk
|
|
@@ -130,8 +129,5 @@ export async function reindexCommand(ctx, opts) {
|
|
|
130
129
|
output: s.green(`Reindexed ${stats.added} sections`) +
|
|
131
130
|
` using ${s.cyan(embedder.name)}.`,
|
|
132
131
|
};
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
await closeDb(db);
|
|
136
|
-
}
|
|
132
|
+
});
|
|
137
133
|
}
|
package/dist/src/cli/search.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare function runSearch(latDir: string, query: string, limit: number,
|
|
|
27
27
|
buildIndex?: boolean;
|
|
28
28
|
project?: MarkdownProjectAnalysis;
|
|
29
29
|
sectionById?: ReadonlyMap<string, Section>;
|
|
30
|
-
|
|
30
|
+
minSimilarity?: number;
|
|
31
31
|
cacheDir?: string;
|
|
32
32
|
}): Promise<SearchResult>;
|
|
33
33
|
/**
|
|
@@ -41,5 +41,6 @@ export declare function cliProgress(s: Styler): IndexProgress;
|
|
|
41
41
|
export declare function searchCommand(ctx: CmdContext, query: string | undefined, opts: {
|
|
42
42
|
limit: number;
|
|
43
43
|
debug?: boolean;
|
|
44
|
-
|
|
44
|
+
minSimilarity?: number;
|
|
45
|
+
preview?: 'passage' | 'intro' | 'both';
|
|
45
46
|
}, progress?: IndexProgress): Promise<CmdResult>;
|
package/dist/src/cli/search.js
CHANGED
|
@@ -1,71 +1,55 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { LEXICAL_VERSION } from '../search/lexical.js';
|
|
3
|
+
import { hasIndex } from '../search/db.js';
|
|
4
|
+
import { embeddingFingerprint } from '../search/chunks.js';
|
|
5
|
+
import { writeIndex } from '../search/cache.js';
|
|
2
6
|
import { dirname, join } from 'node:path';
|
|
3
|
-
import { openDb,
|
|
4
|
-
import { getLlmKey } from '../config.js';
|
|
7
|
+
import { openDb, getStoredModel, setStoredModel, ensureSectionsSchema, closeDb, } from '../search/db.js';
|
|
5
8
|
import { embedderForIndex, modelKey, ReindexRequiredError, EmbeddingAuthError, } from '../search/embedder.js';
|
|
6
|
-
import { indexSections } from '../search/index.js';
|
|
9
|
+
import { indexSections, projectFingerprint, } from '../search/index.js';
|
|
7
10
|
import { searchSections } from '../search/search.js';
|
|
8
11
|
import { resolveSearchMatches, searchIndexedSections, } from '../search/query.js';
|
|
9
12
|
import { analyzeMarkdownProject, commandProjectAnalysis, } from '../project-analysis.js';
|
|
10
13
|
import { formatSectionPreview, formatNavHints } from '../format.js';
|
|
11
14
|
import { provenanceNote, formatProvenanceNote } from './check-status.js';
|
|
12
15
|
async function withDb(latDir, progress, project, cacheDir, fn) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
await ensureMeta(db);
|
|
16
|
-
const stored = await getStoredModel(db);
|
|
17
|
-
// The stored model is authoritative — no silent backend switch. Throws
|
|
18
|
-
// ReindexRequiredError if the environment can't serve the stored index.
|
|
19
|
-
// Rebuilding / switching backends is `lat reindex`, never `lat search`.
|
|
20
|
-
const embedder = await embedderForIndex(stored, latDir);
|
|
21
|
-
await ensureSectionsSchema(db, embedder.dimensions);
|
|
22
|
-
const countResult = await db.execute('SELECT COUNT(*) as n FROM sections');
|
|
23
|
-
let isEmpty = countResult.rows[0].n === 0;
|
|
24
|
-
// Legacy cache: a version before the model was recorded left rows behind
|
|
25
|
-
// with no `meta.embedding_model`. Those vectors may be a different backend
|
|
26
|
-
// (and dimension) than the resolved embedder, and `CREATE TABLE IF NOT
|
|
27
|
-
// EXISTS` won't migrate the column width — so drop and rebuild from scratch
|
|
28
|
-
// under the resolved backend rather than querying a mismatched table.
|
|
29
|
-
if (!stored && !isEmpty) {
|
|
30
|
-
await dropSections(db);
|
|
31
|
-
await ensureSectionsSchema(db, embedder.dimensions);
|
|
32
|
-
isEmpty = true;
|
|
33
|
-
}
|
|
34
|
-
// If the repo is pinned to local but a key is set, say so — otherwise it
|
|
35
|
-
// looks like the key is being silently ignored.
|
|
36
|
-
if (isEmpty && embedder.name.startsWith('local:') && process.stderr.isTTY) {
|
|
37
|
-
let hasKey = false;
|
|
38
|
-
try {
|
|
39
|
-
hasKey = !!getLlmKey();
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
/* key source misconfigured — irrelevant, we're local */
|
|
43
|
-
}
|
|
44
|
-
if (hasKey) {
|
|
45
|
-
process.stderr.write('This repo is configured for local embeddings; ignoring LAT_LLM_KEY.\n');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
progress?.beforeIndex?.(isEmpty);
|
|
16
|
+
if (hasIndex(cacheDir ?? join(latDir, '.cache'))) {
|
|
17
|
+
const db = openDb(latDir, cacheDir, true);
|
|
49
18
|
try {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
19
|
+
const stored = await getStoredModel(db);
|
|
20
|
+
const embedder = await embedderForIndex(stored, latDir);
|
|
21
|
+
const metadata = new Map((await db.execute('SELECT key,value FROM meta')).rows.map((row) => [
|
|
22
|
+
row.key,
|
|
23
|
+
row.value,
|
|
24
|
+
]));
|
|
25
|
+
if (metadata.get('fingerprint') !== embeddingFingerprint(embedder))
|
|
26
|
+
throw new ReindexRequiredError('Search chunking or embedding model changed; run lat reindex.');
|
|
27
|
+
if (stored &&
|
|
28
|
+
metadata.get('lexical_version') === LEXICAL_VERSION &&
|
|
29
|
+
metadata.get('project_hash') === projectFingerprint(project)) {
|
|
30
|
+
progress?.afterIndex?.({
|
|
31
|
+
added: 0,
|
|
32
|
+
updated: 0,
|
|
33
|
+
removed: 0,
|
|
34
|
+
unchanged: project.sections.length,
|
|
35
|
+
}, false);
|
|
36
|
+
return await fn(db, embedder, project);
|
|
37
|
+
}
|
|
56
38
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// truly fresh (re-resolves the backend cleanly) rather than stuck.
|
|
60
|
-
if (!stored)
|
|
61
|
-
await dropSections(db);
|
|
62
|
-
throw err;
|
|
39
|
+
finally {
|
|
40
|
+
await closeDb(db);
|
|
63
41
|
}
|
|
64
|
-
return await fn(db, embedder, project);
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
await closeDb(db);
|
|
68
42
|
}
|
|
43
|
+
return writeIndex(latDir, cacheDir, false, async (db, stored) => {
|
|
44
|
+
const embedder = await embedderForIndex(stored, latDir);
|
|
45
|
+
await ensureSectionsSchema(db, embedder.dimensions);
|
|
46
|
+
const isEmpty = (await db.execute('SELECT COUNT(*) AS n FROM sections')).rows[0].n === 0;
|
|
47
|
+
progress?.beforeIndex?.(isEmpty);
|
|
48
|
+
const stats = await indexSections(latDir, db, embedder, undefined, project);
|
|
49
|
+
await setStoredModel(db, modelKey(embedder));
|
|
50
|
+
progress?.afterIndex?.(stats, isEmpty);
|
|
51
|
+
return fn(db, embedder, project);
|
|
52
|
+
});
|
|
69
53
|
}
|
|
70
54
|
/**
|
|
71
55
|
* Run a semantic search across lat.md sections.
|
|
@@ -86,7 +70,7 @@ export async function runSearch(latDir, query, limit, progress, opts) {
|
|
|
86
70
|
})).sectionById;
|
|
87
71
|
return searchIndexedSections(latDir, query, limit, sectionById, {
|
|
88
72
|
cacheDir: opts.cacheDir,
|
|
89
|
-
|
|
73
|
+
minSimilarity: opts.minSimilarity,
|
|
90
74
|
});
|
|
91
75
|
}
|
|
92
76
|
const project = opts?.project ??
|
|
@@ -94,7 +78,7 @@ export async function runSearch(latDir, query, limit, progress, opts) {
|
|
|
94
78
|
executor: 'auto',
|
|
95
79
|
}));
|
|
96
80
|
return withDb(latDir, progress, project, opts?.cacheDir, async (db, embedder, analyzed) => {
|
|
97
|
-
const results = await searchSections(db, query, embedder, limit, opts?.
|
|
81
|
+
const results = await searchSections(db, query, embedder, limit, opts?.minSimilarity);
|
|
98
82
|
return {
|
|
99
83
|
query,
|
|
100
84
|
matches: resolveSearchMatches(results, analyzed.sectionById),
|
|
@@ -158,7 +142,32 @@ async function formatSearchMatches(ctx, query, matches, opts) {
|
|
|
158
142
|
lines.push('');
|
|
159
143
|
lines.push(formatSectionPreview(ctx, matches[i].section, {
|
|
160
144
|
reason: matches[i].reason,
|
|
161
|
-
|
|
145
|
+
spans: opts?.preview === 'intro'
|
|
146
|
+
? undefined
|
|
147
|
+
: matches[i].evidence?.[0]?.spans,
|
|
148
|
+
score: opts?.showScores ? matches[i].rankScore : undefined,
|
|
149
|
+
text: opts?.preview === 'intro'
|
|
150
|
+
? undefined
|
|
151
|
+
: matches[i].evidence?.length
|
|
152
|
+
? (opts?.preview === 'both'
|
|
153
|
+
? matches[i].section.firstParagraph + '\n\n'
|
|
154
|
+
: '') + matches[i].evidence[0].text
|
|
155
|
+
: undefined,
|
|
156
|
+
debug: opts?.showScores && matches[i].rankScore !== undefined
|
|
157
|
+
? JSON.stringify({
|
|
158
|
+
lexicalRank: matches[i].lexicalRank,
|
|
159
|
+
semanticRank: matches[i].semanticRank,
|
|
160
|
+
lexicalScore: matches[i].lexicalScore,
|
|
161
|
+
semanticSimilarity: matches[i].semanticSimilarity,
|
|
162
|
+
lexicalContribution: matches[i].lexicalRank
|
|
163
|
+
? 1 / (60 + matches[i].lexicalRank)
|
|
164
|
+
: 0,
|
|
165
|
+
semanticContribution: matches[i].semanticRank
|
|
166
|
+
? 1 / (60 + matches[i].semanticRank)
|
|
167
|
+
: 0,
|
|
168
|
+
...matches[i].diagnostics,
|
|
169
|
+
})
|
|
170
|
+
: undefined,
|
|
162
171
|
}));
|
|
163
172
|
const note = notes.get(matches[i].section.filePath);
|
|
164
173
|
if (note)
|
|
@@ -177,7 +186,7 @@ export async function searchCommand(ctx, query, opts, progress) {
|
|
|
177
186
|
const project = await commandProjectAnalysis(ctx);
|
|
178
187
|
const result = await runSearch(ctx.latDir, query, opts.limit, progress, {
|
|
179
188
|
project,
|
|
180
|
-
|
|
189
|
+
minSimilarity: opts.minSimilarity,
|
|
181
190
|
});
|
|
182
191
|
if (result.matches.length === 0) {
|
|
183
192
|
return { output: 'No results found.' };
|
|
@@ -185,6 +194,7 @@ export async function searchCommand(ctx, query, opts, progress) {
|
|
|
185
194
|
return {
|
|
186
195
|
output: (await formatSearchMatches(ctx, query, result.matches, {
|
|
187
196
|
showScores: opts.debug,
|
|
197
|
+
preview: opts.preview ?? 'passage',
|
|
188
198
|
})) + formatNavHints(ctx),
|
|
189
199
|
};
|
|
190
200
|
}
|
package/dist/src/format.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import type { SourceSpan } from './search/types.js';
|
|
1
2
|
import type { Section, SectionMatch } from './lattice-model.js';
|
|
2
3
|
import type { CmdContext, Styler } from './context.js';
|
|
3
4
|
export declare function formatSectionId(id: string, s: Styler): string;
|
|
4
5
|
export declare function formatSectionPreview(ctx: CmdContext, section: Section, opts?: {
|
|
5
6
|
reason?: string;
|
|
6
7
|
score?: number;
|
|
8
|
+
text?: string;
|
|
9
|
+
debug?: string;
|
|
10
|
+
spans?: SourceSpan[];
|
|
7
11
|
}): string;
|
|
8
12
|
export declare function formatResultList(ctx: CmdContext, header: string, matches: SectionMatch[], opts?: {
|
|
9
13
|
showScores?: boolean;
|
|
14
|
+
preview?: 'passage' | 'intro' | 'both';
|
|
10
15
|
}): string;
|
|
11
16
|
export declare function formatNavHints(ctx: CmdContext): string;
|
package/dist/src/format.js
CHANGED
|
@@ -12,6 +12,7 @@ export function formatSectionPreview(ctx, section, opts) {
|
|
|
12
12
|
const kind = section.id.includes('#') ? 'Section' : 'File';
|
|
13
13
|
const details = [
|
|
14
14
|
opts?.reason,
|
|
15
|
+
opts?.debug,
|
|
15
16
|
opts?.score === undefined ? undefined : `score: ${opts.score.toFixed(6)}`,
|
|
16
17
|
].filter((detail) => detail !== undefined);
|
|
17
18
|
const detailsSuffix = details.length > 0 ? ' ' + s.dim(`(${details.join(', ')})`) : '';
|
|
@@ -19,9 +20,11 @@ export function formatSectionPreview(ctx, section, opts) {
|
|
|
19
20
|
`${s.dim('*')} ${s.dim(kind + ':')} [[${formatSectionId(section.id, s)}]]${detailsSuffix}`,
|
|
20
21
|
` ${s.dim('Defined in')} ${s.cyan(relPath)}${s.dim(`:${section.startLine}-${section.endLine}`)}`,
|
|
21
22
|
];
|
|
22
|
-
if (
|
|
23
|
-
lines.push(
|
|
24
|
-
|
|
23
|
+
if (opts?.spans?.length)
|
|
24
|
+
lines.push(` ${s.dim('Matched lines:')} ${opts.spans.map((span) => `${span.startLine}-${span.endLine}`).join(', ')}`);
|
|
25
|
+
const text = opts?.text ?? section.firstParagraph;
|
|
26
|
+
if (text)
|
|
27
|
+
lines.push('', ...text.split('\n').map((line) => ` ${s.dim('>')} ${line}`));
|
|
25
28
|
return lines.join('\n');
|
|
26
29
|
}
|
|
27
30
|
export function formatResultList(ctx, header, matches, opts) {
|
|
@@ -31,7 +34,32 @@ export function formatResultList(ctx, header, matches, opts) {
|
|
|
31
34
|
lines.push('');
|
|
32
35
|
lines.push(formatSectionPreview(ctx, matches[i].section, {
|
|
33
36
|
reason: matches[i].reason,
|
|
34
|
-
|
|
37
|
+
spans: opts?.preview === 'intro'
|
|
38
|
+
? undefined
|
|
39
|
+
: matches[i].evidence?.[0]?.spans,
|
|
40
|
+
score: opts?.showScores ? matches[i].rankScore : undefined,
|
|
41
|
+
text: opts?.preview === 'intro'
|
|
42
|
+
? undefined
|
|
43
|
+
: matches[i].evidence?.length
|
|
44
|
+
? (opts?.preview === 'both'
|
|
45
|
+
? matches[i].section.firstParagraph + '\n\n'
|
|
46
|
+
: '') + matches[i].evidence[0].text
|
|
47
|
+
: undefined,
|
|
48
|
+
debug: opts?.showScores && matches[i].rankScore !== undefined
|
|
49
|
+
? JSON.stringify({
|
|
50
|
+
lexicalRank: matches[i].lexicalRank,
|
|
51
|
+
semanticRank: matches[i].semanticRank,
|
|
52
|
+
lexicalScore: matches[i].lexicalScore,
|
|
53
|
+
semanticSimilarity: matches[i].semanticSimilarity,
|
|
54
|
+
lexicalContribution: matches[i].lexicalRank
|
|
55
|
+
? 1 / (60 + matches[i].lexicalRank)
|
|
56
|
+
: 0,
|
|
57
|
+
semanticContribution: matches[i].semanticRank
|
|
58
|
+
? 1 / (60 + matches[i].semanticRank)
|
|
59
|
+
: 0,
|
|
60
|
+
...matches[i].diagnostics,
|
|
61
|
+
})
|
|
62
|
+
: undefined,
|
|
35
63
|
}));
|
|
36
64
|
}
|
|
37
65
|
lines.push('');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SearchEvidence, SearchDiagnostics } from './search/types.js';
|
|
1
2
|
import type { ForkFrontmatter } from './fork/frontmatter-fields.js';
|
|
2
3
|
export type Section = {
|
|
3
4
|
id: string;
|
|
@@ -72,6 +73,12 @@ export declare function resolveRef(target: string, sectionIds: Set<string>, file
|
|
|
72
73
|
export type SectionMatch = {
|
|
73
74
|
section: Section;
|
|
74
75
|
reason: string;
|
|
75
|
-
|
|
76
|
+
rankScore?: number;
|
|
77
|
+
semanticSimilarity?: number;
|
|
78
|
+
lexicalScore?: number;
|
|
79
|
+
semanticRank?: number;
|
|
80
|
+
lexicalRank?: number;
|
|
81
|
+
evidence?: SearchEvidence[];
|
|
82
|
+
diagnostics?: SearchDiagnostics;
|
|
76
83
|
};
|
|
77
84
|
export declare function findSections(sections: Section[], query: string): SectionMatch[];
|
|
@@ -94,8 +94,11 @@ export function buildSectionSlugIndex(sections) {
|
|
|
94
94
|
const levels = [
|
|
95
95
|
...stack,
|
|
96
96
|
{
|
|
97
|
+
id: section.id,
|
|
97
98
|
depth: section.depth,
|
|
98
|
-
literal: section.
|
|
99
|
+
literal: section.id
|
|
100
|
+
.slice((stack.at(-1)?.id ?? section.file).length + 1)
|
|
101
|
+
.toLowerCase(),
|
|
99
102
|
slug: headingSlug.toLowerCase(),
|
|
100
103
|
},
|
|
101
104
|
];
|
package/dist/src/lattice.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LatFrontmatter, type MdLink, type Ref, type Section } from './lattice-model.js';
|
|
2
2
|
import type { Root } from 'mdast';
|
|
3
3
|
import type { Profiler } from './profiler.js';
|
|
4
4
|
export { buildFileIndex, buildSectionSlugIndex, findSections, flattenSections, resolveRef, } from './lattice-model.js';
|
|
@@ -9,6 +9,6 @@ export type { ForkFrontmatter, FrontmatterProblem, } from './fork/frontmatter-fi
|
|
|
9
9
|
export declare function parseFrontmatter(content: string): LatFrontmatter;
|
|
10
10
|
export declare function parseSections(filePath: string, content: string, projectRoot?: string, tree?: Root): Section[];
|
|
11
11
|
export declare function loadAllSections(latticeDir: string, projectRoot?: string, profile?: Profiler): Promise<Section[]>;
|
|
12
|
-
export declare function extractRefs(filePath: string, content: string, projectRoot?: string, tree?: Root): Ref[];
|
|
12
|
+
export declare function extractRefs(filePath: string, content: string, projectRoot?: string, tree?: Root, sections?: Section[]): Ref[];
|
|
13
13
|
/** Extract link destinations and undefined reference syntax. */
|
|
14
14
|
export declare function extractLinks(content: string, tree?: Root): MdLink[];
|
package/dist/src/lattice.js
CHANGED
|
@@ -6,6 +6,7 @@ import { parse } from './parser.js';
|
|
|
6
6
|
import { toPosix } from './path.js';
|
|
7
7
|
import { visit } from 'unist-util-visit';
|
|
8
8
|
import { listLatticeFiles } from './project-discovery.js';
|
|
9
|
+
import { flattenSections, } from './lattice-model.js';
|
|
9
10
|
import { LAT_FIELDS, } from './fork/frontmatter-fields.js';
|
|
10
11
|
export { buildFileIndex, buildSectionSlugIndex, findSections, flattenSections, resolveRef, } from './lattice-model.js';
|
|
11
12
|
export { findLatticeDir, findProjectRoot, listLatticeFiles, } from './project-discovery.js';
|
|
@@ -56,14 +57,8 @@ export function parseFrontmatter(content) {
|
|
|
56
57
|
result.problems = problems;
|
|
57
58
|
return result;
|
|
58
59
|
}
|
|
59
|
-
function headingText(node) {
|
|
60
|
-
return node.children
|
|
61
|
-
.filter((c) => c.type === 'text')
|
|
62
|
-
.map((c) => c.value)
|
|
63
|
-
.join('');
|
|
64
|
-
}
|
|
65
60
|
/** Extract the rendered text GitHub uses as input to its heading slugger. */
|
|
66
|
-
function
|
|
61
|
+
function headingText(node) {
|
|
67
62
|
if (!node || typeof node !== 'object')
|
|
68
63
|
return '';
|
|
69
64
|
const value = node;
|
|
@@ -77,7 +72,7 @@ function githubHeadingText(node) {
|
|
|
77
72
|
return value.data?.alias ?? value.value ?? '';
|
|
78
73
|
}
|
|
79
74
|
if (value.children) {
|
|
80
|
-
return value.children.map(
|
|
75
|
+
return value.children.map(headingText).join('');
|
|
81
76
|
}
|
|
82
77
|
return '';
|
|
83
78
|
}
|
|
@@ -112,6 +107,8 @@ export function parseSections(filePath, content, projectRoot, tree = parse(conte
|
|
|
112
107
|
const stack = [];
|
|
113
108
|
const flat = [];
|
|
114
109
|
const slugger = new GithubSlugger();
|
|
110
|
+
const usedIds = new Set();
|
|
111
|
+
const nextSuffix = new Map();
|
|
115
112
|
visit(tree, 'heading', (node) => {
|
|
116
113
|
const heading = headingText(node);
|
|
117
114
|
const depth = node.depth;
|
|
@@ -121,7 +118,14 @@ export function parseSections(filePath, content, projectRoot, tree = parse(conte
|
|
|
121
118
|
stack.pop();
|
|
122
119
|
}
|
|
123
120
|
const parent = stack.length > 0 ? stack[stack.length - 1] : null;
|
|
124
|
-
const
|
|
121
|
+
const baseId = parent ? `${parent.id}#${heading}` : `${file}#${heading}`;
|
|
122
|
+
let id = baseId;
|
|
123
|
+
const key = baseId.toLowerCase();
|
|
124
|
+
let suffix = nextSuffix.get(key) ?? 1;
|
|
125
|
+
while (usedIds.has(id.toLowerCase()))
|
|
126
|
+
id = `${baseId}-${suffix++}`;
|
|
127
|
+
nextSuffix.set(key, suffix);
|
|
128
|
+
usedIds.add(id.toLowerCase());
|
|
125
129
|
const section = {
|
|
126
130
|
id,
|
|
127
131
|
heading,
|
|
@@ -132,7 +136,7 @@ export function parseSections(filePath, content, projectRoot, tree = parse(conte
|
|
|
132
136
|
startLine,
|
|
133
137
|
endLine: 0,
|
|
134
138
|
firstParagraph: '',
|
|
135
|
-
githubSlug: slugger.slug(
|
|
139
|
+
githubSlug: slugger.slug(headingText(node)),
|
|
136
140
|
};
|
|
137
141
|
if (parent) {
|
|
138
142
|
parent.children.push(section);
|
|
@@ -190,34 +194,13 @@ export async function loadAllSections(latticeDir, projectRoot = dirname(latticeD
|
|
|
190
194
|
}
|
|
191
195
|
return all;
|
|
192
196
|
}
|
|
193
|
-
export function extractRefs(filePath, content, projectRoot, tree = parse(content)) {
|
|
197
|
+
export function extractRefs(filePath, content, projectRoot, tree = parse(content), sections = parseSections(filePath, content, projectRoot, tree)) {
|
|
194
198
|
const file = projectRoot
|
|
195
199
|
? toPosix(relative(projectRoot, filePath)).replace(/\.md$/, '')
|
|
196
200
|
: basename(filePath, '.md');
|
|
197
201
|
const refs = [];
|
|
198
|
-
//
|
|
199
|
-
const flat =
|
|
200
|
-
visit(tree, 'heading', (node) => {
|
|
201
|
-
flat.push({
|
|
202
|
-
id: '', // filled below
|
|
203
|
-
startLine: node.position.start.line,
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
// Re-derive ids using the same logic as parseSections
|
|
207
|
-
const stack = [];
|
|
208
|
-
let idx = 0;
|
|
209
|
-
visit(tree, 'heading', (node) => {
|
|
210
|
-
const heading = headingText(node);
|
|
211
|
-
const depth = node.depth;
|
|
212
|
-
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
213
|
-
stack.pop();
|
|
214
|
-
}
|
|
215
|
-
const parent = stack.length > 0 ? stack[stack.length - 1] : null;
|
|
216
|
-
const id = parent ? `${parent.id}#${heading}` : `${file}#${heading}`;
|
|
217
|
-
flat[idx].id = id;
|
|
218
|
-
stack.push({ id, depth });
|
|
219
|
-
idx++;
|
|
220
|
-
});
|
|
202
|
+
// Reuse canonical identities, including duplicate suffixes and formatted text.
|
|
203
|
+
const flat = flattenSections(sections);
|
|
221
204
|
visit(tree, 'wikiLink', (node) => {
|
|
222
205
|
const line = node.position.start.line;
|
|
223
206
|
// Find enclosing section: last heading with startLine <= link line
|
|
@@ -6,6 +6,16 @@ export type MarkdownParagraph = {
|
|
|
6
6
|
endLine: number;
|
|
7
7
|
text: string;
|
|
8
8
|
};
|
|
9
|
+
/** Source-positioned block facts; never retains the parser AST. */
|
|
10
|
+
export type MarkdownBlock = {
|
|
11
|
+
type: string;
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
startLine: number;
|
|
15
|
+
endLine: number;
|
|
16
|
+
language?: string;
|
|
17
|
+
children: MarkdownBlock[];
|
|
18
|
+
};
|
|
9
19
|
export type MarkdownDestinationLink = {
|
|
10
20
|
kind: 'image' | 'link';
|
|
11
21
|
line: number;
|
|
@@ -37,6 +47,7 @@ export type MarkdownFileAnalysis = {
|
|
|
37
47
|
headingTitles: string[];
|
|
38
48
|
wikiRefs: Ref[];
|
|
39
49
|
paragraphs: MarkdownParagraph[];
|
|
50
|
+
blocks: MarkdownBlock[];
|
|
40
51
|
markdownLinks: MarkdownDestinationLink[];
|
|
41
52
|
validationLinks: MdLink[];
|
|
42
53
|
indexEntries: string[];
|
|
@@ -5,6 +5,35 @@ import { extractLinks, extractRefs, parseFrontmatter, parseSections, } from './l
|
|
|
5
5
|
import { parse } from './parser.js';
|
|
6
6
|
import { toPosix } from './path.js';
|
|
7
7
|
import { analyzeLocalMarkdownDiagnostics, } from './markdown-validation.js';
|
|
8
|
+
function extractBlocks(tree) {
|
|
9
|
+
const block = (node) => {
|
|
10
|
+
if (node.position?.start.offset === undefined ||
|
|
11
|
+
node.position?.end.offset === undefined)
|
|
12
|
+
return;
|
|
13
|
+
return {
|
|
14
|
+
type: node.type,
|
|
15
|
+
start: node.position.start.offset,
|
|
16
|
+
end: node.position.end.offset,
|
|
17
|
+
startLine: node.position.start.line,
|
|
18
|
+
endLine: node.position.end.line,
|
|
19
|
+
...('lang' in node && node.lang ? { language: node.lang } : {}),
|
|
20
|
+
children: [
|
|
21
|
+
'list',
|
|
22
|
+
'listItem',
|
|
23
|
+
'blockquote',
|
|
24
|
+
'table',
|
|
25
|
+
'tableRow',
|
|
26
|
+
].includes(node.type)
|
|
27
|
+
? ('children' in node ? node.children : [])
|
|
28
|
+
.map(block)
|
|
29
|
+
.filter((item) => !!item)
|
|
30
|
+
: [],
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
return tree.children
|
|
34
|
+
.map(block)
|
|
35
|
+
.filter((item) => !!item);
|
|
36
|
+
}
|
|
8
37
|
function elapsed(work) {
|
|
9
38
|
const start = performance.now();
|
|
10
39
|
return [work(), performance.now() - start];
|
|
@@ -96,7 +125,7 @@ function extractIndexEntries(content) {
|
|
|
96
125
|
export function analyzeMarkdownFile(absolutePath, content, latDir, projectRoot) {
|
|
97
126
|
const [tree, parseMs] = elapsed(() => parse(content));
|
|
98
127
|
const [sections, sectionsMs] = elapsed(() => parseSections(absolutePath, content, projectRoot, tree));
|
|
99
|
-
const [wikiRefs, refsMs] = elapsed(() => extractRefs(absolutePath, content, projectRoot, tree));
|
|
128
|
+
const [wikiRefs, refsMs] = elapsed(() => extractRefs(absolutePath, content, projectRoot, tree, sections));
|
|
100
129
|
const [validationLinks, linksMs] = elapsed(() => extractLinks(content, tree));
|
|
101
130
|
const [[paragraphs, markdownLinks, headingTitles], paragraphsMs] = elapsed(() => [
|
|
102
131
|
extractParagraphs(content, tree),
|
|
@@ -116,6 +145,7 @@ export function analyzeMarkdownFile(absolutePath, content, latDir, projectRoot)
|
|
|
116
145
|
headingTitles,
|
|
117
146
|
wikiRefs,
|
|
118
147
|
paragraphs,
|
|
148
|
+
blocks: extractBlocks(tree),
|
|
119
149
|
markdownLinks,
|
|
120
150
|
validationLinks,
|
|
121
151
|
indexEntries,
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -7,7 +7,7 @@ import { plainStyler } from '../context.js';
|
|
|
7
7
|
import { locateCommand } from '../cli/locate.js';
|
|
8
8
|
import { sectionCommand } from '../cli/section.js';
|
|
9
9
|
import { searchCommand } from '../cli/search.js';
|
|
10
|
-
import { DEFAULT_SEARCH_LIMIT,
|
|
10
|
+
import { DEFAULT_SEARCH_LIMIT, DEFAULT_MIN_SIMILARITY, } from '../search/search.js';
|
|
11
11
|
import { expandCommand } from '../cli/expand.js';
|
|
12
12
|
import { checkAllCommand } from '../cli/check.js';
|
|
13
13
|
import { refsCommand } from '../cli/refs.js';
|
|
@@ -40,21 +40,21 @@ export async function startMcpServer() {
|
|
|
40
40
|
.string()
|
|
41
41
|
.describe('Local section id or exact handle:path#fragment target'),
|
|
42
42
|
}, async ({ query }) => toMcp(await sectionCommand(requestContext(), query)));
|
|
43
|
-
server.tool('lat_search', '
|
|
43
|
+
server.tool('lat_search', 'Hybrid lexical and semantic search across lat.md sections', {
|
|
44
44
|
query: z.string().describe('Search query in natural language'),
|
|
45
45
|
limit: z
|
|
46
46
|
.number()
|
|
47
47
|
.optional()
|
|
48
48
|
.default(DEFAULT_SEARCH_LIMIT)
|
|
49
49
|
.describe(`Max results (default ${DEFAULT_SEARCH_LIMIT})`),
|
|
50
|
-
|
|
50
|
+
minSimilarity: z
|
|
51
51
|
.number()
|
|
52
52
|
.min(0)
|
|
53
53
|
.max(1)
|
|
54
54
|
.optional()
|
|
55
|
-
.default(
|
|
56
|
-
.describe('Minimum cosine similarity score (default 0.
|
|
57
|
-
}, async ({ query, limit,
|
|
55
|
+
.default(DEFAULT_MIN_SIMILARITY)
|
|
56
|
+
.describe('Minimum cosine similarity score (default 0.20)'),
|
|
57
|
+
}, async ({ query, limit, minSimilarity }) => toMcp(await searchCommand(requestContext(), query, { limit, minSimilarity })));
|
|
58
58
|
server.tool('lat_expand', 'Expand [[refs]] in text to resolved lat.md section paths with context', { text: z.string().describe('Text containing [[refs]] to expand') }, async ({ text: input }) => toMcp(await expandCommand(requestContext(), input)));
|
|
59
59
|
server.tool('lat_check', 'Run full lat.md validation: links, code references, indexes, and section structure', {}, async () => toMcp(await checkAllCommand(requestContext())));
|
|
60
60
|
server.tool('lat_refs', 'Find sections that reference a given section via wiki links or @lat code comments', {
|