@plaited/development-skills 0.4.0 → 0.5.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.
- package/.claude/commands/lsp-find.md +34 -11
- package/.claude/commands/lsp-hover.md +11 -2
- package/.claude/commands/lsp-refs.md +9 -0
- package/.claude/rules/code-review.md +0 -32
- package/.claude/rules/module-organization.md +92 -0
- package/.claude/rules/testing.md +80 -0
- package/.claude/skills/scaffold-rules/SKILL.md +7 -0
- package/.claude/skills/typescript-lsp/SKILL.md +15 -5
- package/LICENSE +1 -1
- package/README.md +5 -0
- package/package.json +2 -1
- package/src/lsp-analyze.ts +1 -1
- package/src/lsp-find.ts +14 -33
- package/src/lsp-hover.ts +1 -1
- package/src/lsp-references.ts +1 -1
- package/src/lsp-symbols.ts +1 -1
- package/src/resolve-file-path.ts +27 -13
- package/src/tests/resolve-file-path.spec.ts +100 -19
|
@@ -1,43 +1,66 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Search for TypeScript
|
|
2
|
+
description: Search for TypeScript SYMBOLS (functions, types, classes) - NOT text
|
|
3
3
|
allowed-tools: Bash
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LSP Find
|
|
7
7
|
|
|
8
|
-
Search for symbols (functions, types, classes, variables) across the
|
|
8
|
+
Search for TypeScript **symbols** (functions, types, classes, variables) across the codebase.
|
|
9
9
|
|
|
10
10
|
**Arguments:** $ARGUMENTS
|
|
11
11
|
|
|
12
|
+
## When to Use Each Tool
|
|
13
|
+
|
|
14
|
+
| Tool | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| **Glob** | Find files by pattern |
|
|
17
|
+
| **Grep** | Search text content |
|
|
18
|
+
| **lsp-find** | Search TypeScript symbols |
|
|
19
|
+
| **lsp-hover** | Get type info + TSDoc documentation |
|
|
20
|
+
|
|
12
21
|
## Usage
|
|
13
22
|
|
|
14
23
|
```
|
|
15
|
-
/lsp-find <query>
|
|
24
|
+
/lsp-find <query> <context-file>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `query`: TypeScript symbol name (function, type, class, variable)
|
|
28
|
+
- `context-file`: Any `.ts` file in the project for LSP context
|
|
29
|
+
|
|
30
|
+
## When NOT to Use
|
|
31
|
+
|
|
16
32
|
```
|
|
33
|
+
# ❌ WRONG: Searching for text (use Grep instead)
|
|
34
|
+
/lsp-find scaffold
|
|
35
|
+
/lsp-find TODO
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
-
|
|
37
|
+
# ❌ WRONG: Missing context file
|
|
38
|
+
/lsp-find createClient
|
|
39
|
+
|
|
40
|
+
# ✅ CORRECT: Symbol search with context file
|
|
41
|
+
/lsp-find createClient src/lsp-client.ts
|
|
42
|
+
```
|
|
20
43
|
|
|
21
44
|
## Instructions
|
|
22
45
|
|
|
23
46
|
### Step 1: Parse Arguments
|
|
24
47
|
|
|
25
|
-
Extract query and
|
|
48
|
+
Extract query and context file from `$ARGUMENTS`.
|
|
26
49
|
|
|
27
|
-
If
|
|
50
|
+
If either is missing, show usage:
|
|
28
51
|
```
|
|
29
|
-
Usage: /lsp-find <query>
|
|
52
|
+
Usage: /lsp-find <query> <context-file>
|
|
30
53
|
|
|
31
54
|
Examples:
|
|
32
|
-
/lsp-find
|
|
33
|
-
/lsp-find
|
|
55
|
+
/lsp-find LspClient src/lsp-client.ts
|
|
56
|
+
/lsp-find createClient src/app.ts
|
|
34
57
|
```
|
|
35
58
|
|
|
36
59
|
### Step 2: Run LSP Find
|
|
37
60
|
|
|
38
61
|
Execute the development-skills CLI command:
|
|
39
62
|
```bash
|
|
40
|
-
bunx @plaited/development-skills lsp-find <query>
|
|
63
|
+
bunx @plaited/development-skills lsp-find <query> <context-file>
|
|
41
64
|
```
|
|
42
65
|
|
|
43
66
|
### Step 3: Format Output
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Get TypeScript type
|
|
2
|
+
description: Get TypeScript type signature + TSDoc documentation at a position
|
|
3
3
|
allowed-tools: Bash
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LSP Hover
|
|
7
7
|
|
|
8
|
-
Get type
|
|
8
|
+
Get type signature and TSDoc documentation at a specific position in a TypeScript/JavaScript file.
|
|
9
9
|
|
|
10
10
|
**Arguments:** $ARGUMENTS
|
|
11
11
|
|
|
12
|
+
## When to Use Each Tool
|
|
13
|
+
|
|
14
|
+
| Tool | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| **Glob** | Find files by pattern |
|
|
17
|
+
| **Grep** | Search text content |
|
|
18
|
+
| **lsp-find** | Search TypeScript symbols |
|
|
19
|
+
| **lsp-hover** | Get type info + TSDoc documentation |
|
|
20
|
+
|
|
12
21
|
## Usage
|
|
13
22
|
|
|
14
23
|
```
|
|
@@ -9,6 +9,15 @@ Find all references to a symbol at a specific position. Use this before modifyin
|
|
|
9
9
|
|
|
10
10
|
**Arguments:** $ARGUMENTS
|
|
11
11
|
|
|
12
|
+
## When to Use Each Tool
|
|
13
|
+
|
|
14
|
+
| Tool | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| **Glob** | Find files by pattern |
|
|
17
|
+
| **Grep** | Search text content |
|
|
18
|
+
| **lsp-find** | Search TypeScript symbols |
|
|
19
|
+
| **lsp-refs** | Find all references to a symbol |
|
|
20
|
+
|
|
12
21
|
## Usage
|
|
13
22
|
|
|
14
23
|
```
|
|
@@ -221,38 +221,6 @@ import config from './config.json'
|
|
|
221
221
|
|
|
222
222
|
**Rationale:** Import attributes (ES2025) explicitly declare module types to the runtime. The `with { type: 'json' }` syntax is the standard (replacing the deprecated `assert` keyword). This provides runtime enforcement—if the file isn't valid JSON, the import fails.
|
|
223
223
|
|
|
224
|
-
## Module Organization
|
|
225
|
-
|
|
226
|
-
Organize module exports into separate files by category:
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
module/
|
|
230
|
-
├── module.types.ts # Type definitions only
|
|
231
|
-
├── module.schemas.ts # Zod schemas and schema-inferred types
|
|
232
|
-
├── module.constants.ts # Constants and enum-like objects
|
|
233
|
-
├── module-client.ts # Implementation
|
|
234
|
-
└── index.ts # Public API exports (barrel file)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Key Principles
|
|
238
|
-
|
|
239
|
-
- **Types file**: Contains only type definitions, does NOT re-export from sibling files
|
|
240
|
-
- **Schemas file**: Contains Zod schemas and their inferred types, does NOT export constants
|
|
241
|
-
- **Constants file**: Contains all constant values (error codes, method names, defaults)
|
|
242
|
-
- **Barrel file**: Uses wildcard exports; non-public files are simply not included
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
// ✅ Good: Direct imports from specific files
|
|
246
|
-
import type { Config } from './module.types.ts'
|
|
247
|
-
import { ConfigSchema } from './module.schemas.ts'
|
|
248
|
-
import { METHODS, ERROR_CODES } from './module.constants.ts'
|
|
249
|
-
|
|
250
|
-
// ❌ Avoid: Expecting types file to re-export everything
|
|
251
|
-
import { Config, ConfigSchema, METHODS } from './module.types.ts'
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**Rationale:** Prevents circular dependencies, makes dependencies explicit, improves tree-shaking.
|
|
255
|
-
|
|
256
224
|
## Documentation Standards
|
|
257
225
|
|
|
258
226
|
### Mermaid Diagrams Only
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
RULE TEMPLATE - Distributed via /scaffold-rules
|
|
3
|
+
Variables: {{#if bun}}
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Module Organization
|
|
7
|
+
|
|
8
|
+
## No Index Files
|
|
9
|
+
|
|
10
|
+
**Never use `index.ts` or `index.js` files.** These create implicit magic and complicate debugging.
|
|
11
|
+
|
|
12
|
+
### Re-export Pattern
|
|
13
|
+
|
|
14
|
+
Use named re-export files at the parent level, matching the folder name:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/
|
|
18
|
+
├── acp/ # Feature module
|
|
19
|
+
│ ├── acp.types.ts
|
|
20
|
+
│ ├── acp.schemas.ts
|
|
21
|
+
│ └── acp.ts # Main implementation
|
|
22
|
+
├── acp.ts # Re-exports public API from acp/
|
|
23
|
+
├── utils/
|
|
24
|
+
│ └── format.ts
|
|
25
|
+
└── utils.ts # Re-exports public API from utils/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Single Feature Packages
|
|
29
|
+
|
|
30
|
+
When a package has one primary feature, expose that re-export file directly as main:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"main": "src/acp.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./src/acp.ts",
|
|
37
|
+
"./utils": "./src/utils.ts"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Only use `main.ts` if the package truly has multiple co-equal entry points that need a unified export.
|
|
43
|
+
|
|
44
|
+
## Explicit Import Extensions
|
|
45
|
+
|
|
46
|
+
{{#if bun}}
|
|
47
|
+
Always include `.ts` extensions in imports. Bun runs TypeScript natively—no compilation required:
|
|
48
|
+
{{/if}}
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ✅ Good
|
|
52
|
+
import { Config } from './module.types.ts'
|
|
53
|
+
import { createClient } from '../acp/acp.ts'
|
|
54
|
+
|
|
55
|
+
// ❌ Avoid
|
|
56
|
+
import { Config } from './module.types'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Rationale:** Explicit extensions enable direct execution, clearer module graphs, and align with ES module standards. With `allowImportingTsExtensions: true`, TypeScript supports this pattern.
|
|
60
|
+
|
|
61
|
+
## File Organization Within Modules
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
feature/
|
|
65
|
+
├── feature.types.ts # Type definitions only
|
|
66
|
+
├── feature.schemas.ts # Zod schemas + inferred types
|
|
67
|
+
├── feature.constants.ts # Constants, error codes
|
|
68
|
+
└── feature.ts # Main implementation
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **Types file**: Type definitions only, no re-exports from siblings
|
|
72
|
+
- **Schemas file**: Zod schemas and `z.infer<>` types
|
|
73
|
+
- **Constants file**: All constant values
|
|
74
|
+
- **Main file**: Primary implementation, named after the feature
|
|
75
|
+
|
|
76
|
+
### Key Principles
|
|
77
|
+
|
|
78
|
+
- **Direct imports**: Import from specific files, not through re-exports within a module
|
|
79
|
+
- **Re-exports at boundaries**: Only use re-export files to expose public API at module boundaries
|
|
80
|
+
- **No circular re-exports**: Types file does NOT re-export from siblings
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ✅ Good: Direct imports from specific files
|
|
84
|
+
import type { Config } from './feature.types.ts'
|
|
85
|
+
import { ConfigSchema } from './feature.schemas.ts'
|
|
86
|
+
import { ERROR_CODES } from './feature.constants.ts'
|
|
87
|
+
|
|
88
|
+
// ❌ Avoid: Expecting one file to re-export everything
|
|
89
|
+
import { Config, ConfigSchema, ERROR_CODES } from './feature.types.ts'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Rationale:** Prevents circular dependencies, makes dependencies explicit, improves tree-shaking.
|
package/.claude/rules/testing.md
CHANGED
|
@@ -75,6 +75,86 @@ If a value might not exist, the test should either:
|
|
|
75
75
|
2. Assert that it doesn't exist (if that's the expected behavior)
|
|
76
76
|
3. Restructure the test to ensure the value is always present
|
|
77
77
|
|
|
78
|
+
## Test Coverage Principles
|
|
79
|
+
|
|
80
|
+
### Expansion Checklist
|
|
81
|
+
|
|
82
|
+
When writing tests, ensure coverage of:
|
|
83
|
+
|
|
84
|
+
- [ ] **Happy path** - Expected normal usage
|
|
85
|
+
- [ ] **Edge cases** - Empty strings, special characters, boundary values
|
|
86
|
+
- [ ] **Fallback paths** - What happens when try/catch catches?
|
|
87
|
+
- [ ] **Real dependencies** - Use installed packages (e.g., `@modelcontextprotocol/sdk`) not fake paths
|
|
88
|
+
- [ ] **Category organization** - Group related tests in `describe` blocks
|
|
89
|
+
|
|
90
|
+
### Meaningful Test Coverage
|
|
91
|
+
|
|
92
|
+
Tests should verify behavior, not just exercise code. Each test should answer: "What breaks if this test fails?"
|
|
93
|
+
|
|
94
|
+
**Coverage priorities:**
|
|
95
|
+
1. **Happy path** - Normal expected usage
|
|
96
|
+
2. **Edge cases** - Boundary conditions, empty inputs, unusual formats
|
|
97
|
+
3. **Error paths** - Invalid inputs, missing dependencies, fallback behavior
|
|
98
|
+
4. **Real integrations** - Use actual installed packages when testing module resolution
|
|
99
|
+
|
|
100
|
+
### Use Real Dependencies
|
|
101
|
+
|
|
102
|
+
When testing features that interact with packages or the file system, prefer real installed packages over mocked/fake paths:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// ✅ Good: Real scoped package with subpath exports
|
|
106
|
+
test('resolves scoped package subpath', () => {
|
|
107
|
+
const result = resolveFilePath('@modelcontextprotocol/sdk/client')
|
|
108
|
+
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// ✅ Good: Real package deep subpath
|
|
112
|
+
test('resolves deep subpath with extension', () => {
|
|
113
|
+
const result = resolveFilePath('@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js')
|
|
114
|
+
expect(result).toContain('bearerAuth')
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Test Both Branches
|
|
119
|
+
|
|
120
|
+
When code has conditional logic, try/catch, or fallbacks, test both paths:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
describe('package resolution', () => {
|
|
124
|
+
test('success: resolves existing package', () => {
|
|
125
|
+
const result = resolveFilePath('typescript')
|
|
126
|
+
expect(result).toContain('node_modules/typescript')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('fallback: returns cwd path for non-existent package', () => {
|
|
130
|
+
const result = resolveFilePath('nonexistent-package')
|
|
131
|
+
expect(result).toBe(join(cwd, 'nonexistent-package'))
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Organize with Describe Blocks
|
|
137
|
+
|
|
138
|
+
Group tests by category for better readability and failure diagnosis:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
describe('resolveFilePath', () => {
|
|
142
|
+
describe('absolute paths', () => {
|
|
143
|
+
test('returns as-is', () => { /* ... */ })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('relative paths with extension', () => {
|
|
147
|
+
test('resolves ./ paths', () => { /* ... */ })
|
|
148
|
+
test('resolves ../ paths', () => { /* ... */ })
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('scoped package specifiers', () => {
|
|
152
|
+
test('resolves subpath exports', () => { /* ... */ })
|
|
153
|
+
test('falls back for non-existent', () => { /* ... */ })
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
78
158
|
## Docker Integration Tests
|
|
79
159
|
|
|
80
160
|
Tests that require external services or API keys can run in Docker containers for consistent, isolated execution.
|
|
@@ -66,6 +66,13 @@ Bun test runner conventions:
|
|
|
66
66
|
- No conditionals around assertions
|
|
67
67
|
- Assert existence before checking values
|
|
68
68
|
|
|
69
|
+
### Module Organization
|
|
70
|
+
Import/export patterns for Bun/TypeScript projects:
|
|
71
|
+
- No `index.ts` files—use named re-export files matching folder names
|
|
72
|
+
- Single feature packages expose the feature file directly as main
|
|
73
|
+
- Explicit `.ts` extensions in all imports
|
|
74
|
+
- Flat re-export structure: `src/acp.ts` re-exports from `src/acp/`
|
|
75
|
+
|
|
69
76
|
## Merge Behavior
|
|
70
77
|
|
|
71
78
|
**Always scans existing rules first.** When existing rules are found:
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: typescript-lsp
|
|
3
|
-
description:
|
|
3
|
+
description: Search TypeScript SYMBOLS (functions, types, classes) - NOT text. Use Glob to find files, Grep for text search, LSP for symbol search. Provides type-aware results that understand imports, exports, and relationships.
|
|
4
4
|
license: ISC
|
|
5
5
|
compatibility: Requires bun
|
|
6
6
|
allowed-tools: Bash
|
|
7
7
|
metadata:
|
|
8
8
|
file-triggers: "*.ts,*.tsx,*.js,*.jsx"
|
|
9
|
-
replaces-tools: Grep
|
|
10
9
|
---
|
|
11
10
|
|
|
12
11
|
# TypeScript LSP Skill
|
|
@@ -24,14 +23,25 @@ Use these tools to:
|
|
|
24
23
|
- **Verify before editing** - Check all usages before modifying or deleting exports
|
|
25
24
|
- **Navigate code** - Jump to definitions, find implementations
|
|
26
25
|
|
|
27
|
-
## When to Use
|
|
26
|
+
## When to Use Each Tool
|
|
27
|
+
|
|
28
|
+
| Tool | Purpose |
|
|
29
|
+
|------|---------|
|
|
30
|
+
| **Glob** | Find files by pattern |
|
|
31
|
+
| **Grep** | Search text content |
|
|
32
|
+
| **lsp-find** | Search TypeScript symbols |
|
|
33
|
+
| **lsp-hover** | Get type info + TSDoc documentation |
|
|
34
|
+
| **lsp-refs** | Find all references to a symbol |
|
|
35
|
+
| **lsp-analyze** | Batch analysis of file structure |
|
|
36
|
+
|
|
37
|
+
### LSP vs Grep/Glob
|
|
28
38
|
|
|
29
39
|
| Task | Use LSP | Use Grep/Glob |
|
|
30
40
|
|------|---------|---------------|
|
|
31
|
-
| Find all usages of a function/type | ✅ `lsp-
|
|
41
|
+
| Find all usages of a function/type | ✅ `lsp-refs` | ❌ Misses re-exports, aliases |
|
|
32
42
|
| Search for a symbol by name | ✅ `lsp-find` | ❌ Matches strings, comments |
|
|
43
|
+
| Get type signature + TSDoc | ✅ `lsp-hover` | ❌ Not possible |
|
|
33
44
|
| Understand file exports | ✅ `lsp-analyze --exports` | ❌ Doesn't resolve re-exports |
|
|
34
|
-
| Get type signature | ✅ `lsp-hover` | ❌ Not possible |
|
|
35
45
|
| Find files by pattern | ❌ | ✅ `Glob` |
|
|
36
46
|
| Search non-TS files (md, json) | ❌ | ✅ `Grep` |
|
|
37
47
|
| Search for text in comments/strings | ❌ | ✅ `Grep` |
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# development-skills
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@plaited/development-skills)
|
|
4
|
+
[](https://github.com/plaited/development-skills/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
TypeScript LSP, code documentation, and validation tools. Available as both a CLI tool and as installable skills for AI coding agents.
|
|
4
9
|
|
|
5
10
|
## CLI Tool
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plaited/development-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Development skills for Claude Code - TypeScript LSP, code documentation, and validation tools",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"engines": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@biomejs/biome": "2.3.11",
|
|
51
|
+
"@modelcontextprotocol/sdk": "1.22.0",
|
|
51
52
|
"@types/bun": "1.3.6",
|
|
52
53
|
"format-package": "7.0.0",
|
|
53
54
|
"lint-staged": "16.2.7",
|
package/src/lsp-analyze.ts
CHANGED
|
@@ -125,7 +125,7 @@ Examples:
|
|
|
125
125
|
console.error('Error: File path required')
|
|
126
126
|
process.exit(1)
|
|
127
127
|
}
|
|
128
|
-
const absolutePath =
|
|
128
|
+
const absolutePath = resolveFilePath(filePath)
|
|
129
129
|
const uri = `file://${absolutePath}`
|
|
130
130
|
const rootUri = `file://${process.cwd()}`
|
|
131
131
|
|
package/src/lsp-find.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Search for symbols across the workspace by name
|
|
3
|
+
* Search for TypeScript symbols across the workspace by name
|
|
4
4
|
*
|
|
5
|
-
* Usage: bun lsp-find.ts <query>
|
|
5
|
+
* Usage: bun lsp-find.ts <query> <context-file>
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { parseArgs } from 'node:util'
|
|
@@ -10,25 +10,9 @@ import { LspClient } from './lsp-client.ts'
|
|
|
10
10
|
import { resolveFilePath } from './resolve-file-path.ts'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Search for TypeScript symbols across the workspace by name
|
|
14
14
|
*
|
|
15
|
-
* @
|
|
16
|
-
* Checks common TypeScript entry points in order of preference
|
|
17
|
-
*/
|
|
18
|
-
const findDefaultContextFile = async (): Promise<string | null> => {
|
|
19
|
-
const candidates = [`${process.cwd()}/src/index.ts`, `${process.cwd()}/src/main.ts`, `${process.cwd()}/index.ts`]
|
|
20
|
-
for (const candidate of candidates) {
|
|
21
|
-
if (await Bun.file(candidate).exists()) {
|
|
22
|
-
return candidate
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Search for symbols across the workspace by name
|
|
30
|
-
*
|
|
31
|
-
* @param args - Command line arguments [query, file?]
|
|
15
|
+
* @param args - Command line arguments [query, context-file]
|
|
32
16
|
*/
|
|
33
17
|
export const lspFind = async (args: string[]) => {
|
|
34
18
|
const { positionals } = parseArgs({
|
|
@@ -38,10 +22,15 @@ export const lspFind = async (args: string[]) => {
|
|
|
38
22
|
|
|
39
23
|
const [query, filePath] = positionals
|
|
40
24
|
|
|
41
|
-
if (!query) {
|
|
42
|
-
console.error('
|
|
43
|
-
console.error('
|
|
44
|
-
console.error('
|
|
25
|
+
if (!query || !filePath) {
|
|
26
|
+
console.error('Error: context-file required')
|
|
27
|
+
console.error('')
|
|
28
|
+
console.error('lsp-find searches TypeScript SYMBOLS (functions, types, classes).')
|
|
29
|
+
console.error(' - Use Glob to find files by pattern')
|
|
30
|
+
console.error(' - Use Grep to search text content')
|
|
31
|
+
console.error(' - Use lsp-find <query> <context-file> for symbol search')
|
|
32
|
+
console.error('')
|
|
33
|
+
console.error('Provide any .ts file as context-file (e.g., src/app.ts)')
|
|
45
34
|
process.exit(1)
|
|
46
35
|
}
|
|
47
36
|
|
|
@@ -51,15 +40,7 @@ export const lspFind = async (args: string[]) => {
|
|
|
51
40
|
try {
|
|
52
41
|
await client.start()
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
const contextFile = filePath ? await resolveFilePath(filePath) : await findDefaultContextFile()
|
|
56
|
-
|
|
57
|
-
if (!contextFile) {
|
|
58
|
-
console.error('Error: No context file found.')
|
|
59
|
-
console.error('Provide a file path or ensure src/index.ts, src/main.ts, or index.ts exists.')
|
|
60
|
-
await client.stop()
|
|
61
|
-
process.exit(1)
|
|
62
|
-
}
|
|
43
|
+
const contextFile = resolveFilePath(filePath)
|
|
63
44
|
|
|
64
45
|
const file = Bun.file(contextFile)
|
|
65
46
|
if (!(await file.exists())) {
|
package/src/lsp-hover.ts
CHANGED
|
@@ -38,7 +38,7 @@ export const lspHover = async (args: string[]) => {
|
|
|
38
38
|
process.exit(1)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const absolutePath =
|
|
41
|
+
const absolutePath = resolveFilePath(filePath)
|
|
42
42
|
const uri = `file://${absolutePath}`
|
|
43
43
|
const rootUri = `file://${process.cwd()}`
|
|
44
44
|
|
package/src/lsp-references.ts
CHANGED
|
@@ -38,7 +38,7 @@ export const lspRefs = async (args: string[]) => {
|
|
|
38
38
|
process.exit(1)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const absolutePath =
|
|
41
|
+
const absolutePath = resolveFilePath(filePath)
|
|
42
42
|
const uri = `file://${absolutePath}`
|
|
43
43
|
const rootUri = `file://${process.cwd()}`
|
|
44
44
|
|
package/src/lsp-symbols.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const lspSymbols = async (args: string[]) => {
|
|
|
28
28
|
process.exit(1)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const absolutePath =
|
|
31
|
+
const absolutePath = resolveFilePath(filePath)
|
|
32
32
|
const uri = `file://${absolutePath}`
|
|
33
33
|
const rootUri = `file://${process.cwd()}`
|
|
34
34
|
|
package/src/resolve-file-path.ts
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check if a path has a source file extension
|
|
5
|
+
*/
|
|
6
|
+
const hasSourceExtension = (path: string): boolean => /\.(tsx?|jsx?|mjs|cjs|json)$/.test(path)
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* Resolve a file path to an absolute path
|
|
3
10
|
*
|
|
4
11
|
* @remarks
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
12
|
+
* Resolution order:
|
|
13
|
+
* 1. Absolute paths (starting with `/`) - returned as-is
|
|
14
|
+
* 2. Explicit relative with extension (`./foo.ts`, `../bar.ts`) - resolved from cwd
|
|
15
|
+
* 3. Everything else - try Bun.resolveSync(), fallback to cwd
|
|
16
|
+
*
|
|
17
|
+
* The "everything else" category includes:
|
|
18
|
+
* - Package specifiers: `@org/pkg`, `lodash`, `typescript/lib/typescript.js`
|
|
19
|
+
* - Relative without extension: `./testing` (for package.json exports)
|
|
20
|
+
* - Implicit relative: `src/foo.ts` (fails Bun.resolveSync, falls back to cwd)
|
|
9
21
|
*/
|
|
10
|
-
export const resolveFilePath =
|
|
11
|
-
|
|
22
|
+
export const resolveFilePath = (filePath: string): string => {
|
|
23
|
+
const cwd = process.cwd()
|
|
24
|
+
|
|
25
|
+
// Absolute path - return as-is
|
|
12
26
|
if (filePath.startsWith('/')) {
|
|
13
27
|
return filePath
|
|
14
28
|
}
|
|
15
29
|
|
|
16
|
-
//
|
|
17
|
-
if (filePath.startsWith('.')) {
|
|
18
|
-
return
|
|
30
|
+
// Explicit relative with extension - resolve directly from cwd
|
|
31
|
+
if (filePath.startsWith('.') && hasSourceExtension(filePath)) {
|
|
32
|
+
return join(cwd, filePath)
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
//
|
|
35
|
+
// Everything else: try Bun.resolveSync
|
|
36
|
+
// Handles packages, exports field resolution, and implicit relative paths (via fallback)
|
|
22
37
|
try {
|
|
23
|
-
return
|
|
38
|
+
return Bun.resolveSync(filePath, cwd)
|
|
24
39
|
} catch {
|
|
25
|
-
|
|
26
|
-
return `${process.cwd()}/${filePath}`
|
|
40
|
+
return join(cwd, filePath)
|
|
27
41
|
}
|
|
28
42
|
}
|
|
@@ -1,33 +1,114 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { join } from 'node:path'
|
|
2
3
|
import { resolveFilePath } from '../resolve-file-path.ts'
|
|
3
4
|
|
|
4
5
|
describe('resolveFilePath', () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
describe('absolute paths', () => {
|
|
7
|
+
test('returns absolute path as-is', () => {
|
|
8
|
+
const absolutePath = '/Users/test/file.ts'
|
|
9
|
+
const result = resolveFilePath(absolutePath)
|
|
10
|
+
expect(result).toBe(absolutePath)
|
|
11
|
+
})
|
|
9
12
|
})
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
describe('relative paths with extension', () => {
|
|
15
|
+
test('resolves ./path from cwd', () => {
|
|
16
|
+
const relativePath = './src/resolve-file-path.ts'
|
|
17
|
+
const result = resolveFilePath(relativePath)
|
|
18
|
+
expect(result).toBe(join(process.cwd(), relativePath))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('resolves ../path from cwd', () => {
|
|
22
|
+
const relativePath = '../other/file.ts'
|
|
23
|
+
const result = resolveFilePath(relativePath)
|
|
24
|
+
expect(result).toBe(join(process.cwd(), relativePath))
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('resolves various file extensions', () => {
|
|
28
|
+
const extensions = ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json']
|
|
29
|
+
|
|
30
|
+
for (const ext of extensions) {
|
|
31
|
+
const path = `./src/file.${ext}`
|
|
32
|
+
const result = resolveFilePath(path)
|
|
33
|
+
expect(result).toBe(join(process.cwd(), path))
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('relative paths without extension', () => {
|
|
39
|
+
test('falls back to cwd when no exports match', () => {
|
|
40
|
+
// ./testing has no extension, tries Bun.resolveSync first
|
|
41
|
+
// Falls back to cwd since this project has no exports field
|
|
42
|
+
const path = './testing'
|
|
43
|
+
const result = resolveFilePath(path)
|
|
44
|
+
expect(result).toBe(join(process.cwd(), path))
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('implicit relative paths', () => {
|
|
49
|
+
test('resolves src/foo.ts via fallback to cwd', () => {
|
|
50
|
+
// No ./ prefix, has extension - tries Bun.resolveSync (fails), falls back to cwd
|
|
51
|
+
const path = 'src/resolve-file-path.ts'
|
|
52
|
+
const result = resolveFilePath(path)
|
|
53
|
+
expect(result).toBe(join(process.cwd(), path))
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('resolves nested implicit path via fallback', () => {
|
|
57
|
+
const path = 'src/tests/fixtures/sample.ts'
|
|
58
|
+
const result = resolveFilePath(path)
|
|
59
|
+
expect(result).toBe(join(process.cwd(), path))
|
|
60
|
+
})
|
|
15
61
|
})
|
|
16
62
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
63
|
+
describe('bare package specifiers', () => {
|
|
64
|
+
test('resolves bare package name', () => {
|
|
65
|
+
const result = resolveFilePath('typescript')
|
|
66
|
+
expect(result).toContain('node_modules/typescript')
|
|
67
|
+
expect(result.startsWith('/')).toBe(true)
|
|
68
|
+
})
|
|
21
69
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
70
|
+
test('falls back to cwd for non-existent package', () => {
|
|
71
|
+
const invalidPath = 'nonexistent-package'
|
|
72
|
+
const result = resolveFilePath(invalidPath)
|
|
73
|
+
expect(result).toBe(join(process.cwd(), invalidPath))
|
|
74
|
+
})
|
|
25
75
|
})
|
|
26
76
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
77
|
+
describe('scoped package specifiers', () => {
|
|
78
|
+
test('resolves scoped package subpath export', () => {
|
|
79
|
+
// @modelcontextprotocol/sdk/client is a defined export
|
|
80
|
+
const result = resolveFilePath('@modelcontextprotocol/sdk/client')
|
|
81
|
+
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
82
|
+
expect(result).toContain('client')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('resolves scoped package server subpath', () => {
|
|
86
|
+
const result = resolveFilePath('@modelcontextprotocol/sdk/server')
|
|
87
|
+
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
88
|
+
expect(result).toContain('server')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('resolves scoped package deep subpath with extension', () => {
|
|
92
|
+
// Deep subpath with .js extension via wildcard export "./*"
|
|
93
|
+
const path = '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js'
|
|
94
|
+
const result = resolveFilePath(path)
|
|
95
|
+
expect(result).toContain('node_modules/@modelcontextprotocol/sdk')
|
|
96
|
+
expect(result).toContain('bearerAuth')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('falls back to cwd for non-existent scoped package', () => {
|
|
100
|
+
const scopedPkg = '@nonexistent/pkg/src/file.ts'
|
|
101
|
+
const result = resolveFilePath(scopedPkg)
|
|
102
|
+
expect(result).toBe(join(process.cwd(), scopedPkg))
|
|
103
|
+
})
|
|
104
|
+
})
|
|
30
105
|
|
|
31
|
-
|
|
106
|
+
describe('package subpaths', () => {
|
|
107
|
+
test('resolves package subpath with extension', () => {
|
|
108
|
+
// typescript/lib/typescript.js is a real subpath
|
|
109
|
+
const result = resolveFilePath('typescript/lib/typescript.js')
|
|
110
|
+
expect(result).toContain('node_modules/typescript')
|
|
111
|
+
expect(result).toContain('typescript.js')
|
|
112
|
+
})
|
|
32
113
|
})
|
|
33
114
|
})
|