lat.md 0.8.2 → 0.9.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.
@@ -1,4 +1,5 @@
1
1
  export declare function readAgentsTemplate(): string;
2
2
  export declare function readCursorRulesTemplate(): string;
3
3
  export declare function readPiExtensionTemplate(): string;
4
+ export declare function readSkillTemplate(): string;
4
5
  export declare function genCmd(target: string): Promise<void>;
@@ -10,6 +10,9 @@ export function readCursorRulesTemplate() {
10
10
  export function readPiExtensionTemplate() {
11
11
  return readFileSync(join(findTemplatesDir(), 'pi-extension.ts'), 'utf-8');
12
12
  }
13
+ export function readSkillTemplate() {
14
+ return readFileSync(join(findTemplatesDir(), 'skill', 'SKILL.md'), 'utf-8');
15
+ }
13
16
  export async function genCmd(target) {
14
17
  const normalized = target.toLowerCase();
15
18
  switch (normalized) {
@@ -23,8 +26,11 @@ export async function genCmd(target) {
23
26
  case 'pi-extension.ts':
24
27
  process.stdout.write(readPiExtensionTemplate());
25
28
  break;
29
+ case 'skill.md':
30
+ process.stdout.write(readSkillTemplate());
31
+ break;
26
32
  default:
27
- console.error(`Unknown target: ${target}. Supported: agents.md, claude.md, cursor-rules.md, pi-extension.ts`);
33
+ console.error(`Unknown target: ${target}. Supported: agents.md, claude.md, cursor-rules.md, pi-extension.ts, skill.md`);
28
34
  process.exit(1);
29
35
  }
30
36
  }
@@ -1 +1,2 @@
1
+ export declare function readLogo(): string;
1
2
  export declare function initCmd(targetDir?: string): Promise<void>;
@@ -4,7 +4,7 @@ import { execSync } from 'node:child_process';
4
4
  import { createInterface } from 'node:readline/promises';
5
5
  import chalk from 'chalk';
6
6
  import { findTemplatesDir } from './templates.js';
7
- import { readAgentsTemplate, readCursorRulesTemplate, readPiExtensionTemplate, } from './gen.js';
7
+ import { readAgentsTemplate, readCursorRulesTemplate, readPiExtensionTemplate, readSkillTemplate, } from './gen.js';
8
8
  import { getLlmKey, getConfigPath, readConfig, writeConfig, } from '../config.js';
9
9
  import { writeInitMeta, readFileHash, contentHash } from '../init-version.js';
10
10
  import { getLocalVersion, fetchLatestVersion } from '../version.js';
@@ -314,6 +314,15 @@ async function writeTemplateFile(root, latDir, relPath, template, genTarget, lab
314
314
  ' to see the latest template.');
315
315
  return null;
316
316
  }
317
+ // ── Shared skill setup ───────────────────────────────────────────────
318
+ async function writeAgentsSkill(root, latDir, hashes, ask) {
319
+ console.log('');
320
+ console.log(chalk.dim(' The lat-md skill teaches the agent how to write and maintain lat.md/ files.'));
321
+ const skillTemplate = readSkillTemplate();
322
+ const skillHash = await writeTemplateFile(root, latDir, '.agents/skills/lat-md/SKILL.md', skillTemplate, 'skill.md', 'Skill (.agents/skills/lat-md/SKILL.md)', ' ', ask);
323
+ if (skillHash)
324
+ hashes['.agents/skills/lat-md/SKILL.md'] = skillHash;
325
+ }
317
326
  // ── Per-agent setup ──────────────────────────────────────────────────
318
327
  async function setupAgentsMd(root, latDir, template, hashes, ask) {
319
328
  const hash = await writeTemplateFile(root, latDir, 'AGENTS.md', template, 'agents.md', 'AGENTS.md', '', ask);
@@ -334,6 +343,13 @@ async function setupClaudeCode(root, latDir, template, hashes, ask, style) {
334
343
  mkdirSync(claudeDir, { recursive: true });
335
344
  syncLatHooks(settingsPath, style);
336
345
  console.log(chalk.green(' Hooks') + ' synced (UserPromptSubmit + Stop)');
346
+ // .claude/skills/lat-md/SKILL.md — skill for authoring lat.md files
347
+ console.log('');
348
+ console.log(chalk.dim(' The lat-md skill teaches the agent how to write and maintain lat.md/ files.'));
349
+ const skillTemplate = readSkillTemplate();
350
+ const skillHash = await writeTemplateFile(root, latDir, '.claude/skills/lat-md/SKILL.md', skillTemplate, 'skill.md', 'Skill (.claude/skills/lat-md/SKILL.md)', ' ', ask);
351
+ if (skillHash)
352
+ hashes['.claude/skills/lat-md/SKILL.md'] = skillHash;
337
353
  // Ensure .claude is gitignored (settings contain local absolute paths)
338
354
  ensureGitignored(root, '.claude');
339
355
  // MCP server → .mcp.json at project root
@@ -370,6 +386,8 @@ async function setupCursor(root, latDir, hashes, ask, style) {
370
386
  }
371
387
  // Ensure .cursor/mcp.json is gitignored (it contains local absolute paths)
372
388
  ensureGitignored(root, '.cursor/mcp.json');
389
+ // .agents/skills/lat-md/SKILL.md — skill for authoring lat.md files
390
+ await writeAgentsSkill(root, latDir, hashes, ask);
373
391
  console.log('');
374
392
  console.log(chalk.yellow(' Note:') +
375
393
  ' Enable MCP in Cursor: Settings → Features → MCP → check "Enable MCP"');
@@ -391,6 +409,8 @@ async function setupCopilot(root, latDir, hashes, ask, style) {
391
409
  addMcpServer(mcpPath, 'servers', style);
392
410
  console.log(chalk.green(' MCP server') + ' registered in .vscode/mcp.json');
393
411
  }
412
+ // .agents/skills/lat-md/SKILL.md — skill for authoring lat.md files
413
+ await writeAgentsSkill(root, latDir, hashes, ask);
394
414
  }
395
415
  async function setupPi(root, latDir, hashes, ask, style) {
396
416
  // AGENTS.md — Pi reads this natively
@@ -403,6 +423,13 @@ async function setupPi(root, latDir, hashes, ask, style) {
403
423
  const hash = await writeTemplateFile(root, latDir, '.pi/extensions/lat.ts', template, 'pi-extension.ts', 'Extension (.pi/extensions/lat.ts)', ' ', ask);
404
424
  if (hash)
405
425
  hashes['.pi/extensions/lat.ts'] = hash;
426
+ // .pi/skills/lat-md/SKILL.md — skill for authoring lat.md files
427
+ console.log('');
428
+ console.log(chalk.dim(' The lat-md skill teaches the agent how to write and maintain lat.md/ files.'));
429
+ const skillTemplate = readSkillTemplate();
430
+ const skillHash = await writeTemplateFile(root, latDir, '.pi/skills/lat-md/SKILL.md', skillTemplate, 'skill.md', 'Skill (.pi/skills/lat-md/SKILL.md)', ' ', ask);
431
+ if (skillHash)
432
+ hashes['.pi/skills/lat-md/SKILL.md'] = skillHash;
406
433
  // Ensure .pi is gitignored (extension contains local absolute paths)
407
434
  ensureGitignored(root, '.pi');
408
435
  }
@@ -477,7 +504,11 @@ async function setupLlmKey(rl) {
477
504
  console.log(chalk.green(' Key saved') + ' to ' + chalk.dim(getConfigPath()));
478
505
  }
479
506
  // ── Main init flow ───────────────────────────────────────────────────
507
+ export function readLogo() {
508
+ return readFileSync(join(findTemplatesDir(), 'logo.txt'), 'utf-8');
509
+ }
480
510
  export async function initCmd(targetDir) {
511
+ console.log(chalk.cyan(readLogo()));
481
512
  // Upfront version check — let the user upgrade before proceeding
482
513
  process.stdout.write(chalk.dim('Checking latest version...'));
483
514
  const latest = await fetchLatestVersion();
@@ -554,7 +585,7 @@ export async function initCmd(targetDir) {
554
585
  {
555
586
  label: selectedAgents.length === 0
556
587
  ? "I don't use any of these"
557
- : 'This is it: exit',
588
+ : 'This is it: continue',
558
589
  value: '__done__',
559
590
  accent: true,
560
591
  },
@@ -638,8 +669,9 @@ export async function initCmd(targetDir) {
638
669
  }
639
670
  if (useCodex) {
640
671
  console.log('');
641
- console.log(chalk.bold('Codex / OpenCode') +
642
- ' — uses AGENTS.md (already created). No additional setup needed.');
672
+ console.log(chalk.bold('Setting up Codex / OpenCode...'));
673
+ console.log(chalk.dim(' Uses AGENTS.md (already created).'));
674
+ await writeAgentsSkill(root, latDir, fileHashes, ask);
643
675
  }
644
676
  // Step 5: LLM key setup
645
677
  await setupLlmKey(rl);
@@ -32,7 +32,7 @@ export async function selectMenu(options, prompt, defaultIndex) {
32
32
  const pointer = selected ? '❯' : ' ';
33
33
  if (selected) {
34
34
  if (opt.accent) {
35
- lines.push(` ${pointer} ${chalk.bgRed.white.bold(` ${opt.label} `)}`);
35
+ lines.push(` ${pointer} ${chalk.bgGreen.black.bold(` ${opt.label} `)}`);
36
36
  }
37
37
  else {
38
38
  lines.push(` ${pointer} ${chalk.bgCyan.black.bold(` ${opt.label} `)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lat.md",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "A knowledge graph for your codebase, written in markdown",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.2",
@@ -0,0 +1,7 @@
1
+ ░██ ░██ ░██
2
+ ░██ ░██ ░██
3
+ ░██ ░██████ ░██████ ░█████████████ ░████████
4
+ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██
5
+ ░██ ░███████ ░██ ░██ ░██ ░██ ░██ ░██
6
+ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░███
7
+ ░██ ░█████░██ ░███ ░██ ░██ ░██ ░██ ░█████░██
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: lat-md
3
+ description: >-
4
+ Writing and maintaining lat.md documentation files — structured markdown that
5
+ describes a project's architecture, design decisions, and test specs. Use when
6
+ creating, editing, or reviewing files in the lat.md/ directory.
7
+ ---
8
+
9
+ # lat.md Authoring Guide
10
+
11
+ This skill covers the syntax, structure rules, and conventions for writing `lat.md/` files. Load it whenever you need to create or edit sections in the `lat.md/` directory.
12
+
13
+ ## What belongs in lat.md
14
+
15
+ `lat.md/` files describe **what** the project does and **why** — domain concepts, key design decisions, business logic, and test specifications. They do NOT duplicate source code. Think of each section as an anchor that source code references back to.
16
+
17
+ Good candidates for sections:
18
+ - Architecture decisions and their rationale
19
+ - Domain concepts and business rules
20
+ - API contracts and protocols
21
+ - Test specifications (what is tested and why)
22
+ - Non-obvious constraints or invariants
23
+
24
+ Bad candidates:
25
+ - Step-by-step code walkthroughs (the code itself is the walkthrough)
26
+ - Auto-generated API docs (use tools for that)
27
+ - Temporary notes or TODOs
28
+
29
+ ## Section structure
30
+
31
+ Every section **must** have a leading paragraph — at least one sentence immediately after the heading, before any child headings or other block content.
32
+
33
+ The first paragraph must be ≤250 characters (excluding `[[wiki link]]` content). This paragraph is the section's identity — it appears in search results, command output, and RAG context.
34
+
35
+ ```markdown
36
+ # Good Section
37
+
38
+ Brief overview of what this section documents and why it matters.
39
+
40
+ More detail can go in subsequent paragraphs, code blocks, or lists.
41
+
42
+ ## Child heading
43
+
44
+ Details about this child topic.
45
+ ```
46
+
47
+ ```markdown
48
+ # Bad Section
49
+
50
+ ## Child heading
51
+
52
+ This is invalid — "Bad Section" has no leading paragraph.
53
+ ```
54
+
55
+ `lat check` enforces this rule.
56
+
57
+ ## Section IDs
58
+
59
+ Sections are addressed by file path and heading chain:
60
+
61
+ - **Full form**: `lat.md/path/to/file#Heading#SubHeading`
62
+ - **Short form**: `file#Heading#SubHeading` (when the file stem is unique)
63
+
64
+ Examples: `lat.md/tests/search#RAG Replay Tests`, `cli#init`, `parser#Wiki Links`.
65
+
66
+ ## Wiki links
67
+
68
+ Cross-reference other sections or source code with `[[target]]` or `[[target|alias]]`.
69
+
70
+ ### Section links
71
+
72
+ ```markdown
73
+ See [[cli#init]] for setup details.
74
+ The parser validates [[parser#Wiki Links|wiki link syntax]].
75
+ ```
76
+
77
+ ### Source code links
78
+
79
+ Reference functions, classes, constants, and methods in source files:
80
+
81
+ ```markdown
82
+ [[src/config.ts#getConfigDir]] — function
83
+ [[src/server.ts#App#listen]] — class method
84
+ [[lib/utils.py#parse_args]] — Python function
85
+ [[src/lib.rs#Greeter#greet]] — Rust impl method
86
+ [[src/app.go#Greeter#Greet]] — Go method
87
+ [[src/app.h#Greeter]] — C struct
88
+ ```
89
+
90
+ `lat check` validates that all targets exist.
91
+
92
+ ## Code refs
93
+
94
+ Tie source code back to `lat.md/` sections with `@lat:` comments:
95
+
96
+ ```typescript
97
+ // @lat: [[cli#init]]
98
+ export function init() { ... }
99
+ ```
100
+
101
+ ```python
102
+ # @lat: [[cli#init]]
103
+ def init():
104
+ ...
105
+ ```
106
+
107
+ Supported comment styles: `//` (JS/TS/Rust/Go/C) and `#` (Python).
108
+
109
+ Place one `@lat:` comment per section, at the relevant code — not at the top of the file.
110
+
111
+ ## Test specs
112
+
113
+ Describe tests as sections in `lat.md/` files. Add frontmatter to require that every leaf section has a matching `@lat:` comment in test code:
114
+
115
+ ```markdown
116
+ ---
117
+ lat:
118
+ require-code-mention: true
119
+ ---
120
+ # Tests
121
+
122
+ Authentication test specifications.
123
+
124
+ ## User login
125
+
126
+ Verify credential validation and error handling.
127
+
128
+ ### Rejects expired tokens
129
+
130
+ Tokens past their expiry timestamp are rejected with 401, even if otherwise valid.
131
+
132
+ ### Handles missing password
133
+
134
+ Login request without a password field returns 400 with a descriptive error.
135
+ ```
136
+
137
+ Each test references its spec:
138
+
139
+ ```python
140
+ # @lat: [[tests#User login#Rejects expired tokens]]
141
+ def test_rejects_expired_tokens():
142
+ ...
143
+ ```
144
+
145
+ Rules:
146
+ - Every leaf section under `require-code-mention: true` must be referenced by exactly one `@lat:` comment
147
+ - Every section MUST have a description — at least one sentence explaining what the test verifies and why
148
+ - `lat check` flags unreferenced specs and dangling code refs
149
+
150
+ ## Frontmatter
151
+
152
+ Optional YAML frontmatter at the top of `lat.md/` files:
153
+
154
+ ```yaml
155
+ ---
156
+ lat:
157
+ require-code-mention: true
158
+ ---
159
+ ```
160
+
161
+ Currently the only supported field is `require-code-mention` for test spec enforcement.
162
+
163
+ ## Validation
164
+
165
+ Always run `lat check` after editing `lat.md/` files. It validates:
166
+ - All wiki links point to existing sections or source code symbols
167
+ - All `@lat:` code refs point to existing sections
168
+ - Every section has a leading paragraph (≤250 chars)
169
+ - All `require-code-mention` leaf sections are referenced in code