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.
@@ -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
- body: sym.signature,
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
- body: sym.signature,
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.6.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",
@@ -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 prompt` on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.
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 prompt "user prompt text" # expand [[refs]] to resolved locations
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]]`. `lat check` validates these exist.
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 `lat_prompt` tool on user prompts to expand any `[[refs]]` — this resolves section names to file locations and provides context.
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
- - **lat_prompt** — expand `[[refs]]` in text to resolved locations
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]]`. `lat check` validates these exist.
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 prompt` so that lat.md context
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 prompt` 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."
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
@@ -1,2 +0,0 @@
1
- import type { CliContext } from './context.js';
2
- export declare function promptCmd(ctx: CliContext, text: string): Promise<void>;