litclaude-ai 0.3.3 → 0.3.7

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +5 -5
  3. package/README_ko-KR.md +5 -5
  4. package/RELEASE_CHECKLIST.md +8 -6
  5. package/cover.png +0 -0
  6. package/generate_cover.py +1 -1
  7. package/package.json +1 -1
  8. package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
  9. package/plugins/litclaude/bin/litclaude-hook.js +42 -6
  10. package/plugins/litclaude/commands/dynamic-workflow.md +6 -6
  11. package/plugins/litclaude/commands/litresearch.md +37 -0
  12. package/plugins/litclaude/commands/review-work.md +5 -5
  13. package/plugins/litclaude/hooks/hooks.json +12 -0
  14. package/plugins/litclaude/lib/litgoal/autoloop.mjs +78 -0
  15. package/plugins/litclaude/lib/litgoal/cli.mjs +8 -0
  16. package/plugins/litclaude/skills/lit-loop/SKILL.md +12 -10
  17. package/plugins/litclaude/skills/lit-plan/SKILL.md +9 -10
  18. package/plugins/litclaude/skills/litgoal/SKILL.md +41 -5
  19. package/plugins/litclaude/skills/litresearch/SKILL.md +148 -0
  20. package/plugins/litclaude/skills/lsp-setup/SKILL.md +151 -0
  21. package/plugins/litclaude/skills/lsp-setup/references/bash/README.md +63 -0
  22. package/plugins/litclaude/skills/lsp-setup/references/c-cpp/README.md +72 -0
  23. package/plugins/litclaude/skills/lsp-setup/references/csharp/README.md +81 -0
  24. package/plugins/litclaude/skills/lsp-setup/references/dart/README.md +53 -0
  25. package/plugins/litclaude/skills/lsp-setup/references/elixir/README.md +57 -0
  26. package/plugins/litclaude/skills/lsp-setup/references/go/README.md +59 -0
  27. package/plugins/litclaude/skills/lsp-setup/references/haskell/README.md +64 -0
  28. package/plugins/litclaude/skills/lsp-setup/references/java/README.md +66 -0
  29. package/plugins/litclaude/skills/lsp-setup/references/julia/README.md +62 -0
  30. package/plugins/litclaude/skills/lsp-setup/references/kotlin/README.md +68 -0
  31. package/plugins/litclaude/skills/lsp-setup/references/lua/README.md +64 -0
  32. package/plugins/litclaude/skills/lsp-setup/references/php/README.md +56 -0
  33. package/plugins/litclaude/skills/lsp-setup/references/python/README.md +68 -0
  34. package/plugins/litclaude/skills/lsp-setup/references/ruby/README.md +62 -0
  35. package/plugins/litclaude/skills/lsp-setup/references/rust/README.md +61 -0
  36. package/plugins/litclaude/skills/lsp-setup/references/swift/README.md +60 -0
  37. package/plugins/litclaude/skills/lsp-setup/references/terraform/README.md +61 -0
  38. package/plugins/litclaude/skills/lsp-setup/references/typescript/README.md +77 -0
  39. package/plugins/litclaude/skills/lsp-setup/references/yaml/README.md +60 -0
  40. package/plugins/litclaude/skills/lsp-setup/references/zig/README.md +55 -0
  41. package/plugins/litclaude/skills/lsp-setup/scripts/detect-lsp.ts +220 -0
  42. package/plugins/litclaude/skills/lsp-setup/scripts/lsp-server-table.ts +146 -0
  43. package/plugins/litclaude/skills/lsp-setup/scripts/tsconfig.json +18 -0
  44. package/plugins/litclaude/skills/lsp-setup/scripts/verify-lsp.ts +242 -0
  45. package/plugins/litclaude/skills/start-work/SKILL.md +12 -13
  46. package/plugins/litclaude/skills/visual-qa/SKILL.md +259 -0
  47. package/plugins/litclaude/skills/visual-qa/scripts/ansi.ts +17 -0
  48. package/plugins/litclaude/skills/visual-qa/scripts/cli.ts +96 -0
  49. package/plugins/litclaude/skills/visual-qa/scripts/east-asian-width.ts +72 -0
  50. package/plugins/litclaude/skills/visual-qa/scripts/image-diff.ts +109 -0
  51. package/plugins/litclaude/skills/visual-qa/scripts/png-crc.ts +27 -0
  52. package/plugins/litclaude/skills/visual-qa/scripts/png-decode.ts +206 -0
  53. package/plugins/litclaude/skills/visual-qa/scripts/png-synth.ts +57 -0
  54. package/plugins/litclaude/skills/visual-qa/scripts/tui-grid.ts +88 -0
  55. package/plugins/litclaude/skills/visual-qa/scripts/types.ts +54 -0
  56. package/scripts/qa-portable-install.sh +1 -1
@@ -0,0 +1,61 @@
1
+ # Rust — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `rust-analyzer`
4
+ - **Extensions:** `.rs`
5
+ - **Install hint:** `rustup component add rust-analyzer`
6
+
7
+ ## Install
8
+
9
+ - **macOS:** `rustup component add rust-analyzer` (or `brew install rust-analyzer`)
10
+ - **Linux:** `rustup component add rust-analyzer`
11
+ - **Windows:** `rustup component add rust-analyzer`
12
+
13
+ The rustup component is the recommended path — it stays pinned to your toolchain.
14
+ `rust-analyzer` also needs the `rust-src` component to index the standard library
15
+ (`rustup component add rust-src`).
16
+
17
+ Confirm it resolves:
18
+
19
+ ```bash
20
+ command -v rust-analyzer
21
+ ```
22
+
23
+ ## Configure
24
+
25
+ Add a `rust` entry to `plugins/litclaude/.lsp.json`:
26
+
27
+ ```json
28
+ {
29
+ "rust": {
30
+ "command": ["rust-analyzer"],
31
+ "extensionToLanguage": {
32
+ ".rs": "rust"
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Claude Code routes `.rs` edits to `rust-analyzer` via the extension map. Options
39
+ such as switching the check command to clippy live in `rust-analyzer` settings
40
+ consumed by your editor, not in `.lsp.json`.
41
+
42
+ ## Alternatives
43
+
44
+ None — `rust-analyzer` is the official and sole Rust language server.
45
+
46
+ ## Troubleshooting
47
+ - **PATH:** `rust-analyzer` must be on PATH; reopen shell after install. The rustup shim lives in `~/.cargo/bin`.
48
+ - **Exits while loading rust-src:** if rust-analyzer crashes during stdlib indexing, reinstall the source component:
49
+
50
+ ```bash
51
+ rustup component remove rust-src && rustup component add rust-src
52
+ ```
53
+
54
+ - **No proc-macro / build script support:** ensure the project builds with `cargo check`; rust-analyzer reuses the same toolchain.
55
+
56
+ ## Verify
57
+
58
+ ```bash
59
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/file.rs
60
+ # or: bun ../../scripts/verify-lsp.ts path/to/file.rs
61
+ ```
@@ -0,0 +1,60 @@
1
+ # Swift — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `sourcekit-lsp`
4
+ - **Extensions:** `.swift .objc .objcpp`
5
+ - **Install hint:** `Included with Xcode or the Swift toolchain`
6
+
7
+ ## Install
8
+
9
+ `sourcekit-lsp` ships with the Swift toolchain — no separate install.
10
+
11
+ - **macOS:** `xcode-select --install` (or install full Xcode). It resolves to the active toolchain selected by `xcode-select`.
12
+ - **Linux:** Install a swift.org toolchain (`sourcekit-lsp` ships inside it); add the toolchain's `usr/bin` to PATH.
13
+ - **Windows:** Install the swift.org Windows toolchain; `sourcekit-lsp` is bundled.
14
+
15
+ Confirm it resolves:
16
+
17
+ ```bash
18
+ command -v sourcekit-lsp
19
+ ```
20
+
21
+ ## Configure
22
+
23
+ Add a `swift` entry to `plugins/litclaude/.lsp.json`:
24
+
25
+ ```json
26
+ {
27
+ "swift": {
28
+ "command": ["sourcekit-lsp"],
29
+ "extensionToLanguage": {
30
+ ".swift": "swift",
31
+ ".objc": "objective-c",
32
+ ".objcpp": "objective-cpp"
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Claude Code routes these extensions to `sourcekit-lsp` via the map. For best
39
+ results the project needs a **SwiftPM `Package.swift`** or a
40
+ `compile_commands.json` compilation database so the server can resolve modules.
41
+
42
+ ## Alternatives
43
+
44
+ **No mainstream alternative.** `sourcekit-lsp` is the official Apple/swift.org
45
+ server and the only practical choice.
46
+
47
+ ## Troubleshooting
48
+
49
+ - **PATH:** `sourcekit-lsp` on PATH; reopen shell after install (or after `xcode-select -s`).
50
+ - **Wrong toolchain (macOS):** point `xcode-select` at the right Xcode/toolchain; mismatches cause stale or missing results.
51
+ - **No `Package.swift` / compile db:** add a SwiftPM manifest or generate `compile_commands.json` for accurate indexing.
52
+ - **Objective-C (`.objc`/`.objcpp`):** needs a compilation database to resolve headers and frameworks.
53
+ - **First build slow:** the server builds the module graph on first open; wait for it.
54
+
55
+ ## Verify
56
+
57
+ ```bash
58
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/File.swift
59
+ # or: bun ../../scripts/verify-lsp.ts path/to/File.swift
60
+ ```
@@ -0,0 +1,61 @@
1
+ # Terraform — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `terraform-ls serve`
4
+ - **Extensions:** `.tf .tfvars`
5
+ - **Install hint:** See https://github.com/hashicorp/terraform-ls
6
+
7
+ ## Install
8
+
9
+ - **macOS:** `brew install hashicorp/tap/terraform-ls`
10
+ - **Linux:** download a release from https://github.com/hashicorp/terraform-ls/releases and place `terraform-ls` on PATH (or `apt`/`dnf` via the HashiCorp repo)
11
+ - **Windows:** `choco install terraform-ls` (or download a release zip)
12
+
13
+ `terraform-ls` requires the `terraform` binary itself to be installed and on PATH.
14
+
15
+ Confirm both resolve:
16
+
17
+ ```bash
18
+ command -v terraform-ls
19
+ command -v terraform
20
+ ```
21
+
22
+ ## Configure
23
+
24
+ Add a `terraform` entry to `plugins/litclaude/.lsp.json`:
25
+
26
+ ```json
27
+ {
28
+ "terraform": {
29
+ "command": ["terraform-ls", "serve"],
30
+ "extensionToLanguage": {
31
+ ".tf": "terraform",
32
+ ".tfvars": "terraform-vars"
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Claude Code routes `.tf`/`.tfvars` edits to `terraform-ls` via the map.
39
+ Provider/module completion comes from schemas generated by `terraform init`; run
40
+ it in each module root before expecting full completion:
41
+
42
+ ```bash
43
+ terraform init
44
+ ```
45
+
46
+ ## Alternatives
47
+
48
+ - `tflint` standalone for opinionated linting (complements the LSP).
49
+ - `terraform fmt` for formatting.
50
+
51
+ ## Troubleshooting
52
+ - **PATH:** `terraform-ls` AND `terraform` both on PATH; reopen shell after install.
53
+ - **No provider completion:** run `terraform init` so the `.terraform/` schema cache exists.
54
+ - **`.tfvars` not analyzed:** open the containing module so the server has root context.
55
+
56
+ ## Verify
57
+
58
+ ```bash
59
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/file.tf
60
+ # or: bun ../../scripts/verify-lsp.ts path/to/file.tf
61
+ ```
@@ -0,0 +1,77 @@
1
+ # TypeScript / JavaScript — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `typescript-language-server --stdio`
4
+ - **Extensions:** `.ts .tsx .js .jsx .mjs .cjs .mts .cts`
5
+ - **Install hint:** `npm install -g typescript-language-server typescript`
6
+
7
+ ## Install
8
+
9
+ - **macOS:** `npm install -g typescript-language-server typescript`
10
+ - **Linux:** `npm install -g typescript-language-server typescript`
11
+ - **Windows:** `npm install -g typescript-language-server typescript` (PowerShell or cmd)
12
+
13
+ `typescript-language-server` is only a thin wrapper — it needs the `typescript`
14
+ package (`tsserver`) present too, either globally or in the project's
15
+ `node_modules`. Always install both.
16
+
17
+ Confirm it resolves:
18
+
19
+ ```bash
20
+ command -v typescript-language-server
21
+ ```
22
+
23
+ ## Configure
24
+
25
+ LitClaude declares language servers in `plugins/litclaude/.lsp.json`. Each entry
26
+ is keyed by language name and holds a `command` array plus an
27
+ `extensionToLanguage` map. TypeScript/JavaScript already ships in the default
28
+ config:
29
+
30
+ ```json
31
+ {
32
+ "typescript": {
33
+ "command": ["typescript-language-server", "--stdio"],
34
+ "extensionToLanguage": {
35
+ ".ts": "typescript",
36
+ ".tsx": "typescriptreact",
37
+ ".js": "javascript",
38
+ ".jsx": "javascriptreact",
39
+ ".mjs": "javascript",
40
+ ".cjs": "javascript"
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Add `.mts`/`.cts` to `extensionToLanguage` if your project uses them. Claude Code
47
+ resolves the server for an edited file by matching its extension against these
48
+ maps.
49
+
50
+ ## Alternatives
51
+
52
+ Swap the `command` for your toolchain. Keep the same `.lsp.json` shape and adjust
53
+ `extensionToLanguage` to cover the extensions that server should own:
54
+
55
+ | command | when to choose |
56
+ | ----------------------------------------- | --------------------------------------- |
57
+ | `["deno", "lsp"]` | Deno projects (handles `.ts/.tsx/.js`) |
58
+ | `["biome", "lsp-proxy", "--stdio"]` | Biome lint/format as the LSP |
59
+ | `["vscode-eslint-language-server", "--stdio"]` | ESLint diagnostics |
60
+ | `["oxlint", "--lsp"]` | fast Oxc-based linting |
61
+ | `["vue-language-server", "--stdio"]` | `.vue` single-file components |
62
+ | `["svelteserver", "--stdio"]` | `.svelte` files |
63
+ | `["astro-ls", "--stdio"]` | `.astro` files |
64
+
65
+ `eslint` install: `npm i -g vscode-langservers-extracted`. To run Deno instead
66
+ of the default, replace the `typescript` entry's `command` with `["deno", "lsp"]`.
67
+
68
+ ## Troubleshooting
69
+ - **PATH:** `typescript-language-server` must be on PATH; reopen shell after `npm i -g`. Check your global bin with `npm bin -g`.
70
+ - **Missing tsserver:** errors like "Could not find tsserver" mean the `typescript` package is absent — install it globally or in the project.
71
+
72
+ ## Verify
73
+
74
+ ```bash
75
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/file.ts
76
+ # or: bun ../../scripts/verify-lsp.ts path/to/file.ts
77
+ ```
@@ -0,0 +1,60 @@
1
+ # YAML — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `yaml-language-server --stdio`
4
+ - **Extensions:** `.yaml .yml`
5
+ - **Install hint:** `npm install -g yaml-language-server`
6
+
7
+ ## Install
8
+
9
+ - **macOS:** `npm install -g yaml-language-server`
10
+ - **Linux:** `npm install -g yaml-language-server`
11
+ - **Windows:** `npm install -g yaml-language-server` (PowerShell)
12
+
13
+ Confirm it resolves:
14
+
15
+ ```bash
16
+ command -v yaml-language-server
17
+ ```
18
+
19
+ ## Configure
20
+
21
+ Add a `yaml` entry to `plugins/litclaude/.lsp.json`:
22
+
23
+ ```json
24
+ {
25
+ "yaml": {
26
+ "command": ["yaml-language-server", "--stdio"],
27
+ "extensionToLanguage": {
28
+ ".yaml": "yaml",
29
+ ".yml": "yaml"
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Claude Code routes `.yaml`/`.yml` edits to `yaml-language-server` via the map.
36
+ Schema association is the main reason to tune the server, but those options
37
+ travel through the editor's LSP `initializationOptions` (`yaml.schemas`,
38
+ `yaml.schemaStore`), not `.lsp.json`. An inline
39
+ `# yaml-language-server: $schema=<url>` modeline works without any config:
40
+
41
+ ```yaml
42
+ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
43
+ ```
44
+
45
+ ## Alternatives
46
+
47
+ - `redhat.vscode-yaml` bundles the same server in editors.
48
+ - `yamllint` standalone for style/lint-only checks.
49
+
50
+ ## Troubleshooting
51
+ - **PATH:** `yaml-language-server` on PATH; reopen shell after `npm -g` install.
52
+ - **No validation:** no schema matched — add a `$schema` modeline or a `yaml.schemas` mapping in your editor's init options.
53
+ - **Wrong schema applied:** SchemaStore guessed by filename; pin explicitly with a modeline.
54
+
55
+ ## Verify
56
+
57
+ ```bash
58
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/file.yaml
59
+ # or: bun ../../scripts/verify-lsp.ts path/to/file.yaml
60
+ ```
@@ -0,0 +1,55 @@
1
+ # Zig — LSP setup (Claude Code / LitClaude)
2
+
3
+ - **Recommended server:** `zls`
4
+ - **Extensions:** `.zig .zon`
5
+ - **Install hint:** `https://github.com/zigtools/zls`
6
+
7
+ ## Install
8
+
9
+ ZLS (the Zig Language Server) must be built against the **same Zig version** you use.
10
+ See `https://github.com/zigtools/zls`.
11
+
12
+ - **macOS:** `brew install zls`
13
+ - **Linux:** download a prebuilt release matching your Zig version, or `zig build -Doptimize=ReleaseSafe` from the zls source
14
+ - **Windows:** download the matching release from the zls GitHub releases, or build from source
15
+
16
+ Confirm it resolves:
17
+
18
+ ```bash
19
+ command -v zls
20
+ ```
21
+
22
+ ## Configure
23
+
24
+ Add a `zig` entry to `plugins/litclaude/.lsp.json`:
25
+
26
+ ```json
27
+ {
28
+ "zig": {
29
+ "command": ["zls"],
30
+ "extensionToLanguage": {
31
+ ".zig": "zig",
32
+ ".zon": "zig"
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ Claude Code routes `.zig`/`.zon` edits to `zls` via the map. No extra
39
+ configuration is normally required.
40
+
41
+ ## Alternatives
42
+
43
+ None.
44
+
45
+ ## Troubleshooting
46
+ - **VERSION MATCH (critical):** zls version MUST match your zig version — build/install zls against the exact same Zig. A mismatch causes crashes, parse errors, or silent failures. After upgrading Zig, upgrade/rebuild zls too.
47
+ - **PATH:** `zls` must be on PATH; reopen the shell after install.
48
+ - **zig not found:** zls invokes `zig` for builds — make sure `zig` itself is also on PATH.
49
+
50
+ ## Verify
51
+
52
+ ```bash
53
+ node --experimental-strip-types ../../scripts/verify-lsp.ts path/to/file.zig
54
+ # or: bun ../../scripts/verify-lsp.ts path/to/file.zig
55
+ ```
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ // detect-lsp.ts <targetDir> [--json] [--config=<path>] — scan a directory for
3
+ // source languages and report, per detected language: the recommended language
4
+ // server, whether its executable is on PATH, an install hint, and whether the
5
+ // LitClaude .lsp.json already declares that language.
6
+ //
7
+ // Runtime: Node 22.6+ with `node --experimental-strip-types`, or `bun`.
8
+ // node --experimental-strip-types scripts/detect-lsp.ts <dir>
9
+ // bun scripts/detect-lsp.ts <dir>
10
+
11
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
12
+ import { delimiter, extname, join, sep } from "node:path"
13
+ import process from "node:process"
14
+
15
+ import { LANGUAGES, type LanguageServer, LSP_CONFIG_FILE } from "./lsp-server-table.ts"
16
+
17
+ const SKIP_DIRECTORIES = new Set<string>([
18
+ "node_modules",
19
+ ".git",
20
+ "dist",
21
+ "build",
22
+ ".next",
23
+ "out",
24
+ "target",
25
+ ".venv",
26
+ "venv",
27
+ "vendor",
28
+ ".cache",
29
+ "__pycache__",
30
+ ".turbo",
31
+ "coverage",
32
+ ])
33
+
34
+ const MAX_FILES = 50_000
35
+
36
+ interface ConfigState {
37
+ readonly path: string
38
+ readonly exists: boolean
39
+ readonly languages: readonly string[]
40
+ }
41
+
42
+ interface DetectionResult {
43
+ readonly server: LanguageServer
44
+ readonly executable: string
45
+ readonly installed: boolean
46
+ readonly resolvedPath: string | null
47
+ readonly configured: boolean
48
+ }
49
+
50
+ function collectExtensions(root: string): ReadonlySet<string> {
51
+ const found = new Set<string>()
52
+ const stack: string[] = [root]
53
+ let visited = 0
54
+
55
+ while (stack.length > 0) {
56
+ const current = stack.pop()
57
+ if (current === undefined) break
58
+
59
+ let entries: string[]
60
+ try {
61
+ entries = readdirSync(current)
62
+ } catch {
63
+ continue
64
+ }
65
+
66
+ for (const entry of entries) {
67
+ if (visited >= MAX_FILES) return found
68
+ const fullPath = join(current, entry)
69
+
70
+ let kind: "dir" | "file" | "other" = "other"
71
+ try {
72
+ const stat = statSync(fullPath)
73
+ kind = stat.isDirectory() ? "dir" : stat.isFile() ? "file" : "other"
74
+ } catch {
75
+ continue
76
+ }
77
+
78
+ if (kind === "dir") {
79
+ if (!SKIP_DIRECTORIES.has(entry)) stack.push(fullPath)
80
+ } else if (kind === "file") {
81
+ visited += 1
82
+ const ext = extname(entry).toLowerCase()
83
+ if (ext.length > 0) found.add(ext)
84
+ }
85
+ }
86
+ }
87
+
88
+ return found
89
+ }
90
+
91
+ function pathDirectories(): readonly string[] {
92
+ return (process.env["PATH"] ?? "").split(delimiter).filter((dir: string) => dir.length > 0)
93
+ }
94
+
95
+ function resolveExecutable(command: string): string | null {
96
+ const extensions =
97
+ process.platform === "win32" ? (process.env["PATHEXT"]?.split(";") ?? [".EXE", ".CMD", ".BAT"]) : [""]
98
+ const bases =
99
+ command.includes("/") || command.includes(sep)
100
+ ? [command]
101
+ : pathDirectories().map((dir: string) => join(dir, command))
102
+
103
+ for (const base of bases) {
104
+ for (const ext of extensions) {
105
+ const candidate = ext.length > 0 ? `${base}${ext}` : base
106
+ try {
107
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate
108
+ } catch {
109
+ continue
110
+ }
111
+ }
112
+ }
113
+ return null
114
+ }
115
+
116
+ function isRecord(value: unknown): value is Record<string, unknown> {
117
+ return typeof value === "object" && value !== null && !Array.isArray(value)
118
+ }
119
+
120
+ // LitClaude .lsp.json is keyed by language name:
121
+ // { "typescript": { "command": [...], "extensionToLanguage": {...} } }
122
+ function parseConfiguredLanguages(path: string): readonly string[] {
123
+ let parsed: unknown
124
+ try {
125
+ parsed = JSON.parse(readFileSync(path, "utf-8"))
126
+ } catch {
127
+ return []
128
+ }
129
+ if (!isRecord(parsed)) return []
130
+ return Object.keys(parsed)
131
+ }
132
+
133
+ function readConfigState(configPath: string): ConfigState {
134
+ if (!existsSync(configPath)) return { path: configPath, exists: false, languages: [] }
135
+ return { path: configPath, exists: true, languages: parseConfiguredLanguages(configPath) }
136
+ }
137
+
138
+ function detect(root: string, configured: readonly string[]): readonly DetectionResult[] {
139
+ const extensions = collectExtensions(root)
140
+ const configuredSet = new Set(configured)
141
+ const results: DetectionResult[] = []
142
+
143
+ for (const server of LANGUAGES) {
144
+ if (!server.extensions.some((ext: string) => extensions.has(ext))) continue
145
+
146
+ const executable = server.command[0] ?? server.language
147
+ const resolvedPath = resolveExecutable(executable)
148
+ results.push({
149
+ server,
150
+ executable,
151
+ installed: resolvedPath !== null,
152
+ resolvedPath,
153
+ configured: configuredSet.has(server.language),
154
+ })
155
+ }
156
+
157
+ return results
158
+ }
159
+
160
+ function renderReport(root: string, results: readonly DetectionResult[], config: ConfigState): string {
161
+ const lines: string[] = [`LSP setup scan: ${root}`]
162
+ lines.push(
163
+ `Config file: ${config.path} (${config.exists ? `present: ${config.languages.length} language(s)` : "absent"})`,
164
+ "",
165
+ )
166
+
167
+ if (results.length === 0) {
168
+ lines.push("No languages with a recommended LSP server were detected here.")
169
+ return lines.join("\n")
170
+ }
171
+
172
+ lines.push("DETECTED LANGUAGES (recommended server per language)")
173
+ for (const result of results) {
174
+ const mark = result.installed ? "OK " : "MISS"
175
+ const state = result.installed ? `installed (${result.resolvedPath})` : "NOT installed"
176
+ const config = result.configured ? "declared in .lsp.json" : "NOT in .lsp.json"
177
+ lines.push(
178
+ `[${mark}] ${result.server.language.padEnd(12)} exe=${result.executable} ${state} ${config}`,
179
+ )
180
+ if (!result.installed) lines.push(` install: ${result.server.installHint}`)
181
+ }
182
+
183
+ const missing = results.filter((result) => !result.installed)
184
+ const notConfigured = results.filter((result) => !result.configured)
185
+ lines.push(
186
+ "",
187
+ missing.length === 0
188
+ ? `All ${results.length} detected server(s) installed.`
189
+ : `${missing.length}/${results.length} server(s) NOT installed: ${missing.map((m) => m.server.language).join(", ")}`,
190
+ notConfigured.length === 0
191
+ ? "All detected languages are declared in .lsp.json."
192
+ : `${notConfigured.length} language(s) absent from .lsp.json: ${notConfigured.map((m) => m.server.language).join(", ")}`,
193
+ "Next: read references/<language>/README.md, then add the language to plugins/litclaude/.lsp.json.",
194
+ )
195
+ return lines.join("\n")
196
+ }
197
+
198
+ function main(): void {
199
+ const args = process.argv.slice(2)
200
+ const wantsJson = args.includes("--json")
201
+ const configFlag = args.find((arg: string) => arg.startsWith("--config="))
202
+ const configPath = configFlag ? configFlag.slice("--config=".length) : LSP_CONFIG_FILE
203
+ const root = args.find((arg: string) => !arg.startsWith("--")) ?? process.cwd()
204
+
205
+ if (!existsSync(root)) {
206
+ process.stderr.write(`detect-lsp: target directory does not exist: ${root}\n`)
207
+ process.exit(2)
208
+ }
209
+
210
+ const config = readConfigState(configPath)
211
+ const results = detect(root, config.languages)
212
+
213
+ if (wantsJson) {
214
+ process.stdout.write(`${JSON.stringify({ root, config, results }, null, 2)}\n`)
215
+ return
216
+ }
217
+ process.stdout.write(`${renderReport(root, results, config)}\n`)
218
+ }
219
+
220
+ main()