lat.md 0.6.0 → 0.7.1
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 +3 -2
- package/dist/src/cli/check.d.ts +7 -5
- package/dist/src/cli/check.js +186 -65
- package/dist/src/cli/context.d.ts +3 -8
- package/dist/src/cli/context.js +13 -1
- package/dist/src/cli/expand.d.ts +7 -0
- package/dist/src/cli/{prompt.js → expand.js} +44 -13
- package/dist/src/cli/gen.js +11 -4
- package/dist/src/cli/hook.d.ts +1 -0
- package/dist/src/cli/hook.js +147 -0
- package/dist/src/cli/index.js +77 -28
- package/dist/src/cli/init.js +148 -120
- package/dist/src/cli/locate.d.ts +2 -2
- package/dist/src/cli/locate.js +9 -4
- package/dist/src/cli/refs.d.ts +20 -4
- package/dist/src/cli/refs.js +63 -39
- package/dist/src/cli/search.d.ts +25 -3
- package/dist/src/cli/search.js +82 -48
- package/dist/src/cli/section.d.ts +26 -0
- package/dist/src/cli/section.js +138 -0
- package/dist/src/code-refs.js +2 -1
- package/dist/src/config.js +3 -2
- package/dist/src/context.d.ts +21 -0
- package/dist/src/context.js +11 -0
- package/dist/src/format.d.ts +5 -3
- package/dist/src/format.js +24 -19
- package/dist/src/init-version.d.ts +10 -0
- package/dist/src/init-version.js +49 -0
- package/dist/src/lattice.d.ts +1 -2
- package/dist/src/lattice.js +5 -8
- package/dist/src/mcp/server.js +26 -279
- package/dist/src/parser.js +2 -0
- package/dist/src/source-parser.js +389 -2
- package/package.json +2 -1
- package/templates/AGENTS.md +36 -5
- package/templates/cursor-rules.md +9 -4
- package/templates/lat-prompt-hook.sh +2 -2
- package/dist/src/cli/prompt.d.ts +0 -2
|
@@ -28,6 +28,10 @@ async function getLanguage(ext) {
|
|
|
28
28
|
'.js': 'tree-sitter-javascript.wasm',
|
|
29
29
|
'.jsx': 'tree-sitter-javascript.wasm',
|
|
30
30
|
'.py': 'tree-sitter-python.wasm',
|
|
31
|
+
'.rs': 'tree-sitter-rust.wasm',
|
|
32
|
+
'.go': 'tree-sitter-go.wasm',
|
|
33
|
+
'.c': 'tree-sitter-c.wasm',
|
|
34
|
+
'.h': 'tree-sitter-c.wasm',
|
|
31
35
|
};
|
|
32
36
|
const wasmFile = grammarMap[ext];
|
|
33
37
|
if (!wasmFile)
|
|
@@ -228,6 +232,380 @@ function extractPySymbols(tree) {
|
|
|
228
232
|
}
|
|
229
233
|
return symbols;
|
|
230
234
|
}
|
|
235
|
+
function extractRustSymbols(tree) {
|
|
236
|
+
const symbols = [];
|
|
237
|
+
const root = tree.rootNode;
|
|
238
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
239
|
+
const node = root.child(i);
|
|
240
|
+
const startLine = node.startPosition.row + 1;
|
|
241
|
+
const endLine = node.endPosition.row + 1;
|
|
242
|
+
if (node.type === 'function_item') {
|
|
243
|
+
const name = extractName(node);
|
|
244
|
+
if (name) {
|
|
245
|
+
symbols.push({
|
|
246
|
+
name,
|
|
247
|
+
kind: 'function',
|
|
248
|
+
startLine,
|
|
249
|
+
endLine,
|
|
250
|
+
signature: firstLine(node.text),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else if (node.type === 'struct_item') {
|
|
255
|
+
const name = extractName(node);
|
|
256
|
+
if (name) {
|
|
257
|
+
symbols.push({
|
|
258
|
+
name,
|
|
259
|
+
kind: 'class',
|
|
260
|
+
startLine,
|
|
261
|
+
endLine,
|
|
262
|
+
signature: firstLine(node.text),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else if (node.type === 'enum_item') {
|
|
267
|
+
const name = extractName(node);
|
|
268
|
+
if (name) {
|
|
269
|
+
symbols.push({
|
|
270
|
+
name,
|
|
271
|
+
kind: 'class',
|
|
272
|
+
startLine,
|
|
273
|
+
endLine,
|
|
274
|
+
signature: firstLine(node.text),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (node.type === 'trait_item') {
|
|
279
|
+
const name = extractName(node);
|
|
280
|
+
if (name) {
|
|
281
|
+
symbols.push({
|
|
282
|
+
name,
|
|
283
|
+
kind: 'interface',
|
|
284
|
+
startLine,
|
|
285
|
+
endLine,
|
|
286
|
+
signature: firstLine(node.text),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (node.type === 'impl_item') {
|
|
291
|
+
// impl Type { ... } or impl Trait for Type { ... }
|
|
292
|
+
const typeName = node.childForFieldName('type')?.text;
|
|
293
|
+
if (!typeName)
|
|
294
|
+
continue;
|
|
295
|
+
const body = node.childForFieldName('body');
|
|
296
|
+
if (!body)
|
|
297
|
+
continue;
|
|
298
|
+
for (let j = 0; j < body.namedChildCount; j++) {
|
|
299
|
+
const member = body.namedChild(j);
|
|
300
|
+
if (member.type === 'function_item') {
|
|
301
|
+
const name = extractName(member);
|
|
302
|
+
if (name) {
|
|
303
|
+
symbols.push({
|
|
304
|
+
name,
|
|
305
|
+
kind: 'method',
|
|
306
|
+
parent: typeName,
|
|
307
|
+
startLine: member.startPosition.row + 1,
|
|
308
|
+
endLine: member.endPosition.row + 1,
|
|
309
|
+
signature: firstLine(member.text),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else if (node.type === 'const_item') {
|
|
316
|
+
const name = extractName(node);
|
|
317
|
+
if (name) {
|
|
318
|
+
symbols.push({
|
|
319
|
+
name,
|
|
320
|
+
kind: 'const',
|
|
321
|
+
startLine,
|
|
322
|
+
endLine,
|
|
323
|
+
signature: firstLine(node.text),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (node.type === 'static_item') {
|
|
328
|
+
const name = extractName(node);
|
|
329
|
+
if (name) {
|
|
330
|
+
symbols.push({
|
|
331
|
+
name,
|
|
332
|
+
kind: 'variable',
|
|
333
|
+
startLine,
|
|
334
|
+
endLine,
|
|
335
|
+
signature: firstLine(node.text),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (node.type === 'type_item') {
|
|
340
|
+
const name = extractName(node);
|
|
341
|
+
if (name) {
|
|
342
|
+
symbols.push({
|
|
343
|
+
name,
|
|
344
|
+
kind: 'type',
|
|
345
|
+
startLine,
|
|
346
|
+
endLine,
|
|
347
|
+
signature: firstLine(node.text),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return symbols;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Extract the receiver type name from a Go method declaration's receiver node.
|
|
356
|
+
* Handles both value receivers (Greeter) and pointer receivers (*Greeter).
|
|
357
|
+
*/
|
|
358
|
+
function goReceiverType(receiverNode) {
|
|
359
|
+
const param = receiverNode.namedChild(0);
|
|
360
|
+
if (!param)
|
|
361
|
+
return null;
|
|
362
|
+
const typeNode = param.childForFieldName('type');
|
|
363
|
+
if (!typeNode)
|
|
364
|
+
return null;
|
|
365
|
+
// pointer_type -> child is the actual type name
|
|
366
|
+
if (typeNode.type === 'pointer_type') {
|
|
367
|
+
return typeNode.namedChild(0)?.text ?? null;
|
|
368
|
+
}
|
|
369
|
+
return typeNode.text;
|
|
370
|
+
}
|
|
371
|
+
function extractGoSymbols(tree) {
|
|
372
|
+
const symbols = [];
|
|
373
|
+
const root = tree.rootNode;
|
|
374
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
375
|
+
const node = root.child(i);
|
|
376
|
+
const startLine = node.startPosition.row + 1;
|
|
377
|
+
const endLine = node.endPosition.row + 1;
|
|
378
|
+
if (node.type === 'function_declaration') {
|
|
379
|
+
const name = extractName(node);
|
|
380
|
+
if (name) {
|
|
381
|
+
symbols.push({
|
|
382
|
+
name,
|
|
383
|
+
kind: 'function',
|
|
384
|
+
startLine,
|
|
385
|
+
endLine,
|
|
386
|
+
signature: firstLine(node.text),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (node.type === 'method_declaration') {
|
|
391
|
+
const name = extractName(node);
|
|
392
|
+
const receiver = node.childForFieldName('receiver');
|
|
393
|
+
const typeName = receiver ? goReceiverType(receiver) : null;
|
|
394
|
+
if (name && typeName) {
|
|
395
|
+
symbols.push({
|
|
396
|
+
name,
|
|
397
|
+
kind: 'method',
|
|
398
|
+
parent: typeName,
|
|
399
|
+
startLine,
|
|
400
|
+
endLine,
|
|
401
|
+
signature: firstLine(node.text),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (node.type === 'type_declaration') {
|
|
406
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
407
|
+
const spec = node.namedChild(j);
|
|
408
|
+
if (spec.type !== 'type_spec')
|
|
409
|
+
continue;
|
|
410
|
+
const name = spec.childForFieldName('name')?.text;
|
|
411
|
+
if (!name)
|
|
412
|
+
continue;
|
|
413
|
+
const typeNode = spec.childForFieldName('type');
|
|
414
|
+
const kind = typeNode?.type === 'interface_type' ? 'interface' : 'class';
|
|
415
|
+
symbols.push({
|
|
416
|
+
name,
|
|
417
|
+
kind,
|
|
418
|
+
startLine: spec.startPosition.row + 1,
|
|
419
|
+
endLine: spec.endPosition.row + 1,
|
|
420
|
+
signature: firstLine(node.text),
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else if (node.type === 'const_declaration') {
|
|
425
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
426
|
+
const spec = node.namedChild(j);
|
|
427
|
+
if (spec.type !== 'const_spec')
|
|
428
|
+
continue;
|
|
429
|
+
const name = spec.childForFieldName('name')?.text;
|
|
430
|
+
if (name) {
|
|
431
|
+
symbols.push({
|
|
432
|
+
name,
|
|
433
|
+
kind: 'const',
|
|
434
|
+
startLine: spec.startPosition.row + 1,
|
|
435
|
+
endLine: spec.endPosition.row + 1,
|
|
436
|
+
signature: firstLine(node.text),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (node.type === 'var_declaration') {
|
|
442
|
+
for (let j = 0; j < node.namedChildCount; j++) {
|
|
443
|
+
const spec = node.namedChild(j);
|
|
444
|
+
if (spec.type !== 'var_spec')
|
|
445
|
+
continue;
|
|
446
|
+
const name = spec.childForFieldName('name')?.text;
|
|
447
|
+
if (name) {
|
|
448
|
+
symbols.push({
|
|
449
|
+
name,
|
|
450
|
+
kind: 'variable',
|
|
451
|
+
startLine: spec.startPosition.row + 1,
|
|
452
|
+
endLine: spec.endPosition.row + 1,
|
|
453
|
+
signature: firstLine(node.text),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return symbols;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Extract the declarator name from a C function_declarator node.
|
|
463
|
+
* Handles plain identifiers and pointer declarators (*name).
|
|
464
|
+
*/
|
|
465
|
+
function cFuncName(declarator) {
|
|
466
|
+
if (declarator.type === 'function_declarator') {
|
|
467
|
+
const inner = declarator.childForFieldName('declarator');
|
|
468
|
+
if (!inner)
|
|
469
|
+
return null;
|
|
470
|
+
if (inner.type === 'identifier')
|
|
471
|
+
return inner.text;
|
|
472
|
+
if (inner.type === 'pointer_declarator') {
|
|
473
|
+
// *name — dig through pointer layers
|
|
474
|
+
let cur = inner;
|
|
475
|
+
while (cur.type === 'pointer_declarator') {
|
|
476
|
+
const child = cur.childForFieldName('declarator');
|
|
477
|
+
if (!child)
|
|
478
|
+
return null;
|
|
479
|
+
cur = child;
|
|
480
|
+
}
|
|
481
|
+
return cur.type === 'identifier' ? cur.text : null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Extract the variable name from a C init_declarator or plain declarator.
|
|
488
|
+
* Handles pointers like `*DEFAULT_NAME = "World"`.
|
|
489
|
+
*/
|
|
490
|
+
function cVarName(declarator) {
|
|
491
|
+
let node = declarator;
|
|
492
|
+
// Unwrap init_declarator to get the declarator part
|
|
493
|
+
if (node.type === 'init_declarator') {
|
|
494
|
+
const inner = node.childForFieldName('declarator');
|
|
495
|
+
if (!inner)
|
|
496
|
+
return null;
|
|
497
|
+
node = inner;
|
|
498
|
+
}
|
|
499
|
+
if (node.type === 'identifier')
|
|
500
|
+
return node.text;
|
|
501
|
+
if (node.type === 'pointer_declarator') {
|
|
502
|
+
let cur = node;
|
|
503
|
+
while (cur.type === 'pointer_declarator') {
|
|
504
|
+
const child = cur.childForFieldName('declarator');
|
|
505
|
+
if (!child)
|
|
506
|
+
return null;
|
|
507
|
+
cur = child;
|
|
508
|
+
}
|
|
509
|
+
return cur.type === 'identifier' ? cur.text : null;
|
|
510
|
+
}
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
function extractCSymbols(tree) {
|
|
514
|
+
const symbols = [];
|
|
515
|
+
collectCNodes(tree.rootNode, symbols);
|
|
516
|
+
return symbols;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Walk C AST nodes, collecting symbols. Recurses into preproc_ifdef /
|
|
520
|
+
* preproc_ifndef blocks so header include guards don't hide declarations.
|
|
521
|
+
*/
|
|
522
|
+
function collectCNodes(parent, symbols) {
|
|
523
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
524
|
+
const node = parent.child(i);
|
|
525
|
+
const startLine = node.startPosition.row + 1;
|
|
526
|
+
const endLine = node.endPosition.row + 1;
|
|
527
|
+
if (node.type === 'function_definition') {
|
|
528
|
+
const declarator = node.childForFieldName('declarator');
|
|
529
|
+
const name = declarator ? cFuncName(declarator) : null;
|
|
530
|
+
if (name) {
|
|
531
|
+
symbols.push({
|
|
532
|
+
name,
|
|
533
|
+
kind: 'function',
|
|
534
|
+
startLine,
|
|
535
|
+
endLine,
|
|
536
|
+
signature: firstLine(node.text),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else if (node.type === 'struct_specifier') {
|
|
541
|
+
const name = extractName(node);
|
|
542
|
+
if (name) {
|
|
543
|
+
symbols.push({
|
|
544
|
+
name,
|
|
545
|
+
kind: 'class',
|
|
546
|
+
startLine,
|
|
547
|
+
endLine,
|
|
548
|
+
signature: firstLine(node.text),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else if (node.type === 'enum_specifier') {
|
|
553
|
+
const name = extractName(node);
|
|
554
|
+
if (name) {
|
|
555
|
+
symbols.push({
|
|
556
|
+
name,
|
|
557
|
+
kind: 'class',
|
|
558
|
+
startLine,
|
|
559
|
+
endLine,
|
|
560
|
+
signature: firstLine(node.text),
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else if (node.type === 'type_definition') {
|
|
565
|
+
const declarator = node.childForFieldName('declarator');
|
|
566
|
+
const name = declarator?.type === 'type_identifier' ? declarator.text : null;
|
|
567
|
+
if (name) {
|
|
568
|
+
symbols.push({
|
|
569
|
+
name,
|
|
570
|
+
kind: 'type',
|
|
571
|
+
startLine,
|
|
572
|
+
endLine,
|
|
573
|
+
signature: firstLine(node.text),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (node.type === 'declaration') {
|
|
578
|
+
const declarator = node.childForFieldName('declarator');
|
|
579
|
+
const name = declarator ? cVarName(declarator) : null;
|
|
580
|
+
if (name) {
|
|
581
|
+
symbols.push({
|
|
582
|
+
name,
|
|
583
|
+
kind: 'variable',
|
|
584
|
+
startLine,
|
|
585
|
+
endLine,
|
|
586
|
+
signature: firstLine(node.text),
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else if (node.type === 'preproc_def') {
|
|
591
|
+
const name = extractName(node);
|
|
592
|
+
if (name) {
|
|
593
|
+
symbols.push({
|
|
594
|
+
name,
|
|
595
|
+
kind: 'const',
|
|
596
|
+
startLine,
|
|
597
|
+
endLine,
|
|
598
|
+
signature: firstLine(node.text),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else if (node.type === 'preproc_ifdef' ||
|
|
603
|
+
node.type === 'preproc_ifndef') {
|
|
604
|
+
// Recurse into include guard / conditional blocks
|
|
605
|
+
collectCNodes(node, symbols);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
231
609
|
function firstLine(text) {
|
|
232
610
|
const nl = text.indexOf('\n');
|
|
233
611
|
return nl === -1 ? text : text.slice(0, nl);
|
|
@@ -245,6 +623,15 @@ export async function parseSourceSymbols(filePath, content) {
|
|
|
245
623
|
if (ext === '.py') {
|
|
246
624
|
return extractPySymbols(tree);
|
|
247
625
|
}
|
|
626
|
+
if (ext === '.rs') {
|
|
627
|
+
return extractRustSymbols(tree);
|
|
628
|
+
}
|
|
629
|
+
if (ext === '.go') {
|
|
630
|
+
return extractGoSymbols(tree);
|
|
631
|
+
}
|
|
632
|
+
if (ext === '.c' || ext === '.h') {
|
|
633
|
+
return extractCSymbols(tree);
|
|
634
|
+
}
|
|
248
635
|
return extractTsSymbols(tree);
|
|
249
636
|
}
|
|
250
637
|
/**
|
|
@@ -302,7 +689,7 @@ export function sourceSymbolsToSections(symbols, filePath) {
|
|
|
302
689
|
children: [],
|
|
303
690
|
startLine: sym.startLine,
|
|
304
691
|
endLine: sym.endLine,
|
|
305
|
-
|
|
692
|
+
firstParagraph: sym.signature,
|
|
306
693
|
};
|
|
307
694
|
sections.push(section);
|
|
308
695
|
if (sym.kind === 'class') {
|
|
@@ -325,7 +712,7 @@ export function sourceSymbolsToSections(symbols, filePath) {
|
|
|
325
712
|
children: [],
|
|
326
713
|
startLine: sym.startLine,
|
|
327
714
|
endLine: sym.endLine,
|
|
328
|
-
|
|
715
|
+
firstParagraph: sym.signature,
|
|
329
716
|
};
|
|
330
717
|
parentSection.children.push(section);
|
|
331
718
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lat.md",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "A knowledge graph for your codebase, written in markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.30.2",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"commander": "^14.0.3",
|
|
56
56
|
"ignore-walk": "^8.0.0",
|
|
57
57
|
"mdast-util-to-markdown": "^2.1.0",
|
|
58
|
+
"remark-frontmatter": "^5.0.0",
|
|
58
59
|
"remark-parse": "^11.0.0",
|
|
59
60
|
"remark-stringify": "^11.0.0",
|
|
60
61
|
"unified": "^11.0.0",
|
package/templates/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Before starting work
|
|
2
2
|
|
|
3
3
|
- Run `lat search` to find sections relevant to your task. Read them to understand the design intent before writing code.
|
|
4
|
-
- Run `lat
|
|
4
|
+
- Run `lat expand` on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.
|
|
5
5
|
|
|
6
6
|
# Post-task checklist (REQUIRED — do not skip)
|
|
7
7
|
|
|
@@ -23,7 +23,7 @@ This project uses [lat.md](https://www.npmjs.com/package/lat.md) to maintain a s
|
|
|
23
23
|
lat locate "Section Name" # find a section by name (exact, fuzzy)
|
|
24
24
|
lat refs "file#Section" # find what references a section
|
|
25
25
|
lat search "natural language" # semantic search across all sections
|
|
26
|
-
lat
|
|
26
|
+
lat expand "user prompt text" # expand [[refs]] to resolved locations
|
|
27
27
|
lat check # validate all links and code refs
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -35,8 +35,8 @@ If `lat search` fails because no API key is configured, explain to the user that
|
|
|
35
35
|
|
|
36
36
|
- **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`).
|
|
37
37
|
- **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`.
|
|
38
|
-
- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]
|
|
39
|
-
- **Code refs**: `// @lat: [[section-id]]` (JS/TS) or `# @lat: [[section-id]]` (Python) — ties source code to concepts
|
|
38
|
+
- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist.
|
|
39
|
+
- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts
|
|
40
40
|
|
|
41
41
|
# Test specs
|
|
42
42
|
|
|
@@ -49,7 +49,12 @@ lat:
|
|
|
49
49
|
---
|
|
50
50
|
# Tests
|
|
51
51
|
|
|
52
|
+
Authentication and authorization test specifications.
|
|
53
|
+
|
|
52
54
|
## User login
|
|
55
|
+
|
|
56
|
+
Verify credential validation and error handling for the login endpoint.
|
|
57
|
+
|
|
53
58
|
### Rejects expired tokens
|
|
54
59
|
Tokens past their expiry timestamp are rejected with 401, even if otherwise valid.
|
|
55
60
|
|
|
@@ -57,7 +62,7 @@ Tokens past their expiry timestamp are rejected with 401, even if otherwise vali
|
|
|
57
62
|
Login request without a password field returns 400 with a descriptive error.
|
|
58
63
|
```
|
|
59
64
|
|
|
60
|
-
Every section MUST have a description — at least one sentence explaining what the test verifies and why. Empty sections with just a heading are not acceptable.
|
|
65
|
+
Every section MUST have a description — at least one sentence explaining what the test verifies and why. Empty sections with just a heading are not acceptable. (This is a specific case of the general leading paragraph rule below.)
|
|
61
66
|
|
|
62
67
|
Each test in code should reference its spec with exactly one comment placed next to the relevant test — not at the top of the file:
|
|
63
68
|
|
|
@@ -72,3 +77,29 @@ def test_handles_missing_password():
|
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
Do not duplicate refs. One `@lat:` comment per spec section, placed at the test that covers it. `lat check` will flag any spec section not covered by a code reference, and any code reference pointing to a nonexistent section.
|
|
80
|
+
|
|
81
|
+
# Section structure
|
|
82
|
+
|
|
83
|
+
Every section in `lat.md/` **must** have a leading paragraph — at least one sentence immediately after the heading, before any child headings or other block content. The first paragraph must be ≤250 characters (excluding `[[wiki link]]` content). This paragraph serves as the section's overview and is used in search results, command output, and RAG context — keeping it concise guarantees the section's essence is always captured.
|
|
84
|
+
|
|
85
|
+
```markdown
|
|
86
|
+
# Good Section
|
|
87
|
+
|
|
88
|
+
Brief overview of what this section documents and why it matters.
|
|
89
|
+
|
|
90
|
+
More detail can go in subsequent paragraphs, code blocks, or lists.
|
|
91
|
+
|
|
92
|
+
## Child heading
|
|
93
|
+
|
|
94
|
+
Details about this child topic.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
# Bad Section
|
|
99
|
+
|
|
100
|
+
## Child heading
|
|
101
|
+
|
|
102
|
+
Details about this child topic.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The second example is invalid because `Bad Section` has no leading paragraph. `lat check` validates this rule and reports errors for missing or overly long leading paragraphs.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Before starting work
|
|
2
2
|
|
|
3
3
|
- Use the `lat_search` tool to find sections relevant to your task. Read them to understand the design intent before writing code.
|
|
4
|
-
- Use the `
|
|
4
|
+
- Use the `lat_expand` tool on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.
|
|
5
5
|
|
|
6
6
|
# Post-task checklist (REQUIRED — do not skip)
|
|
7
7
|
|
|
@@ -23,7 +23,7 @@ You have access to the following MCP tools from the `lat` server:
|
|
|
23
23
|
|
|
24
24
|
- **lat_locate** — find a section by name (exact, fuzzy)
|
|
25
25
|
- **lat_search** — semantic search across all sections
|
|
26
|
-
- **
|
|
26
|
+
- **lat_expand** — expand `[[refs]]` in text to resolved locations
|
|
27
27
|
- **lat_check** — validate all wiki links and code refs
|
|
28
28
|
- **lat_refs** — find what references a section
|
|
29
29
|
|
|
@@ -33,8 +33,8 @@ If `lat_search` fails because `LAT_LLM_KEY` is not set, explain to the user that
|
|
|
33
33
|
|
|
34
34
|
- **Section ids**: `lat.md/path/to/file#Heading#SubHeading` — full form uses project-root-relative path (e.g. `lat.md/tests/search#RAG Replay Tests`). Short form uses bare file name when unique (e.g. `search#RAG Replay Tests`, `cli#search#Indexing`).
|
|
35
35
|
- **Wiki links**: `[[target]]` or `[[target|alias]]` — cross-references between sections. Can also reference source code: `[[src/foo.ts#myFunction]]`.
|
|
36
|
-
- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]
|
|
37
|
-
- **Code refs**: `// @lat: [[section-id]]` (JS/TS) or `# @lat: [[section-id]]` (Python) — ties source code to concepts
|
|
36
|
+
- **Source code links**: Wiki links in `lat.md/` files can reference functions, classes, constants, and methods in TypeScript/JavaScript/Python/Rust/Go/C files. Use the full path: `[[src/config.ts#getConfigDir]]`, `[[src/server.ts#App#listen]]` (class method), `[[lib/utils.py#parse_args]]`, `[[src/lib.rs#Greeter#greet]]` (Rust impl method), `[[src/app.go#Greeter#Greet]]` (Go method), `[[src/app.h#Greeter]]` (C struct). `lat check` validates these exist.
|
|
37
|
+
- **Code refs**: `// @lat: [[section-id]]` (JS/TS/Rust/Go/C) or `# @lat: [[section-id]]` (Python) — ties source code to concepts
|
|
38
38
|
|
|
39
39
|
# Test specs
|
|
40
40
|
|
|
@@ -47,7 +47,12 @@ lat:
|
|
|
47
47
|
---
|
|
48
48
|
# Tests
|
|
49
49
|
|
|
50
|
+
Authentication and authorization test specifications.
|
|
51
|
+
|
|
50
52
|
## User login
|
|
53
|
+
|
|
54
|
+
Verify credential validation and error handling for the login endpoint.
|
|
55
|
+
|
|
51
56
|
### Rejects expired tokens
|
|
52
57
|
Tokens past their expiry timestamp are rejected with 401, even if otherwise valid.
|
|
53
58
|
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
# Injected by `lat init` — reminds the agent to consult lat.md before working.
|
|
4
4
|
#
|
|
5
5
|
# This hook fires on every user prompt submission. It adds a short instruction
|
|
6
|
-
# telling the agent to run `lat search` and `lat
|
|
6
|
+
# telling the agent to run `lat search` and `lat expand` so that lat.md context
|
|
7
7
|
# is consulted before any code changes.
|
|
8
8
|
|
|
9
9
|
cat <<'HOOK_JSON'
|
|
10
10
|
{
|
|
11
11
|
"hookSpecificOutput": {
|
|
12
12
|
"hookEventName": "UserPromptSubmit",
|
|
13
|
-
"additionalContext": "Before starting work on this task:\n1. Run `lat search` with a query relevant to the task and read the results to understand the design intent.\n2. If the prompt contains [[refs]], run `lat
|
|
13
|
+
"additionalContext": "Before starting work on this task:\n1. Run `lat search` with a query relevant to the task and read the results to understand the design intent.\n2. If the prompt contains [[refs]], run `lat expand` on the full prompt text to resolve them.\n3. After completing work, run `lat check` to validate all links and code refs.\nDo not skip these steps."
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
HOOK_JSON
|
package/dist/src/cli/prompt.d.ts
DELETED