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,146 @@
1
+ // Standalone snapshot of the primary language server per supported language,
2
+ // embedded so detect-lsp.ts runs without any external dependency in any project.
3
+ // Each entry mirrors the per-language references/<language>/README.md and the
4
+ // LitClaude .lsp.json shape, which is keyed by language name:
5
+ // { "<language>": { "command": [...], "extensionToLanguage": { ".ext": "<lang>" } } }
6
+ // Edit this table when a reference README changes.
7
+
8
+ export interface LanguageServer {
9
+ readonly language: string
10
+ readonly command: readonly string[]
11
+ readonly extensions: readonly string[]
12
+ readonly installHint: string
13
+ }
14
+
15
+ export const LANGUAGES: readonly LanguageServer[] = [
16
+ {
17
+ language: "typescript",
18
+ command: ["typescript-language-server", "--stdio"],
19
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
20
+ installHint: "npm install -g typescript-language-server typescript",
21
+ },
22
+ {
23
+ language: "python",
24
+ command: ["basedpyright-langserver", "--stdio"],
25
+ extensions: [".py", ".pyi"],
26
+ installHint: "pip install basedpyright (or: uv tool install basedpyright)",
27
+ },
28
+ {
29
+ language: "go",
30
+ command: ["gopls"],
31
+ extensions: [".go"],
32
+ installHint: "go install golang.org/x/tools/gopls@latest",
33
+ },
34
+ {
35
+ language: "rust",
36
+ command: ["rust-analyzer"],
37
+ extensions: [".rs"],
38
+ installHint: "rustup component add rust-analyzer",
39
+ },
40
+ {
41
+ language: "c-cpp",
42
+ command: ["clangd", "--background-index", "--clang-tidy"],
43
+ extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
44
+ installHint: "macOS: brew install llvm | Linux: apt install clangd | https://clangd.llvm.org/installation",
45
+ },
46
+ {
47
+ language: "java",
48
+ command: ["jdtls"],
49
+ extensions: [".java"],
50
+ installHint: "macOS: brew install jdtls | https://github.com/eclipse-jdtls/eclipse.jdt.ls",
51
+ },
52
+ {
53
+ language: "kotlin",
54
+ command: ["kotlin-lsp"],
55
+ extensions: [".kt", ".kts"],
56
+ installHint: "https://github.com/Kotlin/kotlin-lsp",
57
+ },
58
+ {
59
+ language: "csharp",
60
+ command: ["csharp-ls"],
61
+ extensions: [".cs"],
62
+ installHint: "dotnet tool install -g csharp-ls",
63
+ },
64
+ {
65
+ language: "razor",
66
+ command: ["roslyn-language-server", "--stdio"],
67
+ extensions: [".razor", ".cshtml"],
68
+ installHint: "dotnet tool install -g roslyn-language-server --prerelease (see references/csharp/README.md)",
69
+ },
70
+ {
71
+ language: "swift",
72
+ command: ["sourcekit-lsp"],
73
+ extensions: [".swift", ".objc", ".objcpp"],
74
+ installHint: "Included with Xcode (xcode-select --install) or the Swift toolchain",
75
+ },
76
+ {
77
+ language: "ruby",
78
+ command: ["rubocop", "--lsp"],
79
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
80
+ installHint: "gem install rubocop (the builtin runs `rubocop --lsp`)",
81
+ },
82
+ {
83
+ language: "php",
84
+ command: ["intelephense", "--stdio"],
85
+ extensions: [".php"],
86
+ installHint: "npm install -g intelephense",
87
+ },
88
+ {
89
+ language: "dart",
90
+ command: ["dart", "language-server", "--lsp"],
91
+ extensions: [".dart"],
92
+ installHint: "Included with the Dart/Flutter SDK",
93
+ },
94
+ {
95
+ language: "elixir",
96
+ command: ["elixir-ls"],
97
+ extensions: [".ex", ".exs"],
98
+ installHint: "https://github.com/elixir-lsp/elixir-ls",
99
+ },
100
+ {
101
+ language: "zig",
102
+ command: ["zls"],
103
+ extensions: [".zig", ".zon"],
104
+ installHint: "https://github.com/zigtools/zls (match zls version to your zig version)",
105
+ },
106
+ {
107
+ language: "lua",
108
+ command: ["lua-language-server"],
109
+ extensions: [".lua"],
110
+ installHint: "macOS: brew install lua-language-server | https://github.com/LuaLS/lua-language-server",
111
+ },
112
+ {
113
+ language: "bash",
114
+ command: ["bash-language-server", "start"],
115
+ extensions: [".sh", ".bash", ".zsh", ".ksh"],
116
+ installHint: "npm install -g bash-language-server",
117
+ },
118
+ {
119
+ language: "yaml",
120
+ command: ["yaml-language-server", "--stdio"],
121
+ extensions: [".yaml", ".yml"],
122
+ installHint: "npm install -g yaml-language-server",
123
+ },
124
+ {
125
+ language: "terraform",
126
+ command: ["terraform-ls", "serve"],
127
+ extensions: [".tf", ".tfvars"],
128
+ installHint: "macOS: brew install hashicorp/tap/terraform-ls | https://github.com/hashicorp/terraform-ls",
129
+ },
130
+ {
131
+ language: "haskell",
132
+ command: ["haskell-language-server-wrapper", "--lsp"],
133
+ extensions: [".hs", ".lhs"],
134
+ installHint: "ghcup install hls",
135
+ },
136
+ {
137
+ language: "julia",
138
+ command: ["julia", "--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"],
139
+ extensions: [".jl"],
140
+ installHint: "julia -e 'using Pkg; Pkg.add(\"LanguageServer\")' (see references/julia/README.md)",
141
+ },
142
+ ] as const
143
+
144
+ // The LitClaude LSP config file, relative to the plugin root. detect-lsp.ts also
145
+ // accepts an explicit path so it can be pointed at a project copy.
146
+ export const LSP_CONFIG_FILE = "plugins/litclaude/.lsp.json"
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "noEmit": true,
13
+ "lib": ["ESNext"],
14
+ "types": ["node"],
15
+ "allowImportingTsExtensions": true
16
+ },
17
+ "include": ["*.ts"]
18
+ }
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ // verify-lsp.ts <file> [--timeout=ms] [--config=<path>] — perform a real LSP
3
+ // diagnostics roundtrip for <file>: resolve the server for the file extension
4
+ // (from plugins/litclaude/.lsp.json when present, else the embedded table),
5
+ // spawn it, run the JSON-RPC initialize -> initialized -> didOpen handshake over
6
+ // stdio, wait for textDocument/publishDiagnostics, and report OK / FAIL / SKIP.
7
+ //
8
+ // Dependency-free: uses only node:child_process and node:fs. No monorepo imports.
9
+ //
10
+ // Runtime: Node 22.6+ with `node --experimental-strip-types`, or `bun`.
11
+ // node --experimental-strip-types scripts/verify-lsp.ts <file>
12
+ // bun scripts/verify-lsp.ts <file>
13
+ //
14
+ // Exit codes: 0 OK, 1 FAIL (server error / not installed), 2 usage, 3 SKIP
15
+ // (no server known for this extension).
16
+
17
+ import { spawn } from "node:child_process"
18
+ import { existsSync, readFileSync, statSync } from "node:fs"
19
+ import { extname, isAbsolute, resolve } from "node:path"
20
+ import { pathToFileURL } from "node:url"
21
+ import process from "node:process"
22
+
23
+ import { LANGUAGES, LSP_CONFIG_FILE } from "./lsp-server-table.ts"
24
+
25
+ const DEFAULT_TIMEOUT_MS = 60_000
26
+
27
+ interface ResolvedServer {
28
+ readonly language: string
29
+ readonly command: readonly string[]
30
+ }
31
+
32
+ function isRecord(value: unknown): value is Record<string, unknown> {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value)
34
+ }
35
+
36
+ function isStringArray(value: unknown): value is string[] {
37
+ return Array.isArray(value) && value.every((item) => typeof item === "string")
38
+ }
39
+
40
+ // Resolve the server command for `ext`, preferring a LitClaude .lsp.json entry
41
+ // whose `extensionToLanguage` maps the extension, then the embedded table.
42
+ function resolveServer(ext: string, configPath: string): ResolvedServer | null {
43
+ if (existsSync(configPath)) {
44
+ try {
45
+ const parsed: unknown = JSON.parse(readFileSync(configPath, "utf-8"))
46
+ if (isRecord(parsed)) {
47
+ for (const [language, entry] of Object.entries(parsed)) {
48
+ if (!isRecord(entry)) continue
49
+ const map = entry["extensionToLanguage"]
50
+ const command = entry["command"]
51
+ if (isRecord(map) && Object.keys(map).includes(ext) && isStringArray(command)) {
52
+ return { language, command }
53
+ }
54
+ }
55
+ }
56
+ } catch {
57
+ // fall through to embedded table
58
+ }
59
+ }
60
+
61
+ for (const server of LANGUAGES) {
62
+ if (server.extensions.includes(ext)) {
63
+ return { language: server.language, command: [...server.command] }
64
+ }
65
+ }
66
+ return null
67
+ }
68
+
69
+ interface JsonRpcMessage {
70
+ readonly method?: string
71
+ readonly id?: number
72
+ readonly params?: unknown
73
+ readonly result?: unknown
74
+ readonly error?: unknown
75
+ }
76
+
77
+ function encode(message: unknown): Buffer {
78
+ const body = Buffer.from(JSON.stringify(message), "utf-8")
79
+ return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii"), body])
80
+ }
81
+
82
+ // Incremental LSP framing parser: pulls complete Content-Length messages off a
83
+ // growing buffer.
84
+ function makeReader(onMessage: (message: JsonRpcMessage) => void): (chunk: Buffer) => void {
85
+ let buffer = Buffer.alloc(0)
86
+ return (chunk: Buffer): void => {
87
+ buffer = Buffer.concat([buffer, chunk])
88
+ while (true) {
89
+ const headerEnd = buffer.indexOf("\r\n\r\n")
90
+ if (headerEnd === -1) return
91
+ const header = buffer.subarray(0, headerEnd).toString("ascii")
92
+ const match = /content-length:\s*(\d+)/i.exec(header)
93
+ if (!match) {
94
+ buffer = buffer.subarray(headerEnd + 4)
95
+ continue
96
+ }
97
+ const length = Number.parseInt(match[1] ?? "0", 10)
98
+ const start = headerEnd + 4
99
+ if (buffer.length < start + length) return
100
+ const body = buffer.subarray(start, start + length).toString("utf-8")
101
+ buffer = buffer.subarray(start + length)
102
+ try {
103
+ onMessage(JSON.parse(body) as JsonRpcMessage)
104
+ } catch {
105
+ // ignore malformed frame
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ function languageId(ext: string): string {
112
+ const server = LANGUAGES.find((entry) => entry.extensions.includes(ext))
113
+ return server?.language ?? ext.replace(/^\./, "")
114
+ }
115
+
116
+ async function run(filePath: string, timeoutMs: number, configPath: string): Promise<number> {
117
+ const absolute = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath)
118
+ const ext = extname(absolute).toLowerCase()
119
+ const server = resolveServer(ext, configPath)
120
+
121
+ if (server === null) {
122
+ process.stderr.write(`SKIP ${absolute}: no language server known for "${ext}". See references/.\n`)
123
+ return 3
124
+ }
125
+
126
+ const [bin, ...binArgs] = server.command
127
+ if (bin === undefined) {
128
+ process.stderr.write(`SKIP ${absolute}: empty command for ${server.language}.\n`)
129
+ return 3
130
+ }
131
+
132
+ const text = readFileSync(absolute, "utf-8")
133
+ const uri = pathToFileURL(absolute).href
134
+
135
+ return await new Promise<number>((resolveResult) => {
136
+ let child
137
+ try {
138
+ child = spawn(bin, binArgs, { stdio: ["pipe", "pipe", "pipe"] })
139
+ } catch {
140
+ process.stdout.write(`FAIL ${absolute}: language server not installed (${bin})\n`)
141
+ resolveResult(1)
142
+ return
143
+ }
144
+
145
+ let settled = false
146
+ let stderr = ""
147
+ const finish = (code: number, line: string): void => {
148
+ if (settled) return
149
+ settled = true
150
+ clearTimeout(timer)
151
+ try {
152
+ child.kill("SIGKILL")
153
+ } catch {
154
+ /* already gone */
155
+ }
156
+ process.stdout.write(`${line}\n`)
157
+ resolveResult(code)
158
+ }
159
+
160
+ const timer = setTimeout(() => {
161
+ finish(1, `FAIL ${absolute}: timed out after ${timeoutMs}ms waiting for diagnostics`)
162
+ }, timeoutMs)
163
+
164
+ child.on("error", (error: NodeJS.ErrnoException) => {
165
+ if (error.code === "ENOENT") {
166
+ finish(1, `FAIL ${absolute}: language server not installed (${bin})`)
167
+ } else {
168
+ finish(1, `FAIL ${absolute}: ${error.message}`)
169
+ }
170
+ })
171
+ child.on("exit", (code) => {
172
+ if (!settled) {
173
+ finish(1, `FAIL ${absolute}: server exited (code ${code ?? "?"})\n${stderr.trim()}`)
174
+ }
175
+ })
176
+ child.stderr.on("data", (chunk: Buffer) => {
177
+ stderr += chunk.toString("utf-8")
178
+ })
179
+
180
+ const send = (message: unknown): void => {
181
+ child.stdin.write(encode(message))
182
+ }
183
+
184
+ const read = makeReader((message) => {
185
+ if (message.id === 1 && message.result !== undefined) {
186
+ send({ jsonrpc: "2.0", method: "initialized", params: {} })
187
+ send({
188
+ jsonrpc: "2.0",
189
+ method: "textDocument/didOpen",
190
+ params: { textDocument: { uri, languageId: languageId(ext), version: 1, text } },
191
+ })
192
+ }
193
+ if (message.method === "textDocument/publishDiagnostics" && isRecord(message.params)) {
194
+ const diagnostics = message.params["diagnostics"]
195
+ const count = Array.isArray(diagnostics) ? diagnostics.length : 0
196
+ if (message.params["uri"] === uri) {
197
+ finish(0, `OK ${absolute}: LSP roundtrip succeeded (${count} diagnostic(s)) via ${server.language}`)
198
+ }
199
+ }
200
+ })
201
+ child.stdout.on("data", read)
202
+
203
+ send({
204
+ jsonrpc: "2.0",
205
+ id: 1,
206
+ method: "initialize",
207
+ params: {
208
+ processId: process.pid,
209
+ rootUri: pathToFileURL(process.cwd()).href,
210
+ capabilities: { textDocument: { publishDiagnostics: {} } },
211
+ },
212
+ })
213
+ })
214
+ }
215
+
216
+ function parseTimeout(args: readonly string[]): number {
217
+ const flag = args.find((arg) => arg.startsWith("--timeout="))
218
+ if (flag === undefined) return DEFAULT_TIMEOUT_MS
219
+ const parsed = Number.parseInt(flag.slice("--timeout=".length), 10)
220
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS
221
+ }
222
+
223
+ async function main(): Promise<void> {
224
+ const args = process.argv.slice(2)
225
+ const configFlag = args.find((arg) => arg.startsWith("--config="))
226
+ const configPath = configFlag ? configFlag.slice("--config=".length) : LSP_CONFIG_FILE
227
+ const filePath = args.find((arg) => !arg.startsWith("--"))
228
+
229
+ if (filePath === undefined) {
230
+ process.stderr.write("Usage: verify-lsp.ts <file> [--timeout=ms] [--config=<path>]\n")
231
+ process.exit(2)
232
+ }
233
+ if (!existsSync(filePath) || !statSync(filePath).isFile()) {
234
+ process.stderr.write(`verify-lsp: not a file: ${filePath}\n`)
235
+ process.exit(2)
236
+ }
237
+
238
+ const code = await run(filePath, parseTimeout(args), configPath)
239
+ process.exit(code)
240
+ }
241
+
242
+ await main()
@@ -25,19 +25,18 @@ Before the first edit:
25
25
 
26
26
  ## Native Goal + Dynamic Workflow
27
27
 
28
- Before the first checkbox, use Claude Code's native goal surface when available:
29
-
30
- - call `get_goal` if exposed
31
- - call `create_goal` with the plan objective only when no matching active goal
32
- exists
33
- - call `update_goal` only after all top-level checkboxes and final gates are
34
- complete, or when the plan is genuinely blocked
35
- - if the user invokes `/goal`, respect it as the native session goal
36
- - do not auto-type or send `/goal` text from a skill
37
-
38
- If model-facing goal tools are absent, continue with Boulder/ledger discipline.
39
- For long plans, ask once for `/goal <plan completion condition>` if that would
40
- help the user keep the session bounded.
28
+ Before the first checkbox, bind the native goal. Claude Code's `/goal` (v2.1.139+) is a
29
+ **user-typed slash command**, not a model-facing tool, and a hook/skill **cannot invoke it**:
30
+
31
+ - For a long plan, offer the user one ready-to-paste `/goal <plan completion condition>` line —
32
+ this is the real way to bind the native session goal — and keep the Boulder/ledger discipline.
33
+ - Forward-compat only: if a future Claude Code build exposes model-facing goal tools, call
34
+ `get_goal` if exposed, `create_goal` with the plan objective only when no matching active goal
35
+ exists, and `update_goal` only after all top-level checkboxes and final gates are complete (or
36
+ when the plan is genuinely blocked). These are not exposed today.
37
+ - Do not auto-type or send `/goal` text from a skill; if the user invokes `/goal`, respect it.
38
+
39
+ If model-facing goal tools are absent (the case today), continue with Boulder/ledger discipline.
41
40
  If goal tools are not exposed, do not print repeated fallback status; just keep
42
41
  the plan ledger accurate and continue with verified evidence.
43
42