prjct-cli 0.64.0 → 0.64.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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.64.1] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- generate IDE context files with correct paths and formats (PRJ-122) (#107)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.64.1] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **IDE context file generation (PRJ-122)**: Fixed Cursor and Windsurf context file paths and formats
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
Fixed misalignment between `ai-provider.ts` (correct paths) and `ai-tools/registry.ts` (incorrect paths). Updated Cursor output to `.cursor/rules/prjct.mdc` with MDC format and `alwaysApply: true` frontmatter. Updated Windsurf output to `.windsurf/rules/prjct.md` with `trigger: always_on` frontmatter. Modified sync-service default behavior to auto-detect IDE tools when `.cursor/` or `.windsurf/` directories exist.
|
|
19
|
+
|
|
20
|
+
### Learnings
|
|
21
|
+
|
|
22
|
+
- Two separate implementations existed (ai-provider vs ai-tools) that needed synchronization
|
|
23
|
+
- IDE formats differ: Cursor uses `alwaysApply: true`, Windsurf uses `trigger: always_on`
|
|
24
|
+
- Auto-detection pattern: check for directory existence to enable optional features
|
|
25
|
+
|
|
26
|
+
### Test Plan
|
|
27
|
+
|
|
28
|
+
#### For QA
|
|
29
|
+
1. Run `prjct sync` in project with `.cursor/` dir - verify `.cursor/rules/prjct.mdc` generated
|
|
30
|
+
2. Run `prjct sync` in project with `.windsurf/` dir - verify `.windsurf/rules/prjct.md` generated
|
|
31
|
+
3. Verify Cursor file has `alwaysApply: true` in frontmatter
|
|
32
|
+
4. Verify Windsurf file has `trigger: always_on` in frontmatter
|
|
33
|
+
5. Run `bun test` - all 405 tests pass
|
|
34
|
+
|
|
35
|
+
#### For Users
|
|
36
|
+
- `prjct sync` now auto-generates IDE context files when `.cursor/` or `.windsurf/` directories exist
|
|
37
|
+
- No manual flags needed - detection is automatic
|
|
38
|
+
- No breaking changes
|
|
39
|
+
|
|
40
|
+
|
|
3
41
|
## [0.64.0] - 2026-02-05
|
|
4
42
|
|
|
5
43
|
### Features
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AI Tools Formatters
|
|
3
|
+
*
|
|
4
|
+
* @see PRJ-122
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from 'bun:test'
|
|
8
|
+
import {
|
|
9
|
+
formatForClaude,
|
|
10
|
+
formatForContinue,
|
|
11
|
+
formatForCopilot,
|
|
12
|
+
formatForCursor,
|
|
13
|
+
formatForWindsurf,
|
|
14
|
+
getFormatter,
|
|
15
|
+
type ProjectContext,
|
|
16
|
+
} from '../../ai-tools/formatters'
|
|
17
|
+
import { AI_TOOLS, getAIToolConfig } from '../../ai-tools/registry'
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Test Fixtures
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
const mockContext: ProjectContext = {
|
|
24
|
+
projectId: 'test-project-id',
|
|
25
|
+
name: 'test-project',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
ecosystem: 'Node.js',
|
|
28
|
+
projectType: 'library',
|
|
29
|
+
languages: ['TypeScript'],
|
|
30
|
+
frameworks: ['Hono'],
|
|
31
|
+
repoPath: '/Users/test/project',
|
|
32
|
+
branch: 'main',
|
|
33
|
+
fileCount: 100,
|
|
34
|
+
commits: 50,
|
|
35
|
+
hasChanges: false,
|
|
36
|
+
commands: {
|
|
37
|
+
install: 'bun install',
|
|
38
|
+
dev: 'bun run dev',
|
|
39
|
+
test: 'bun test',
|
|
40
|
+
build: 'bun run build',
|
|
41
|
+
lint: 'bun run lint',
|
|
42
|
+
format: 'bun run format',
|
|
43
|
+
},
|
|
44
|
+
agents: {
|
|
45
|
+
workflow: ['prjct-planner', 'prjct-shipper'],
|
|
46
|
+
domain: ['backend'],
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Registry Tests
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
describe('AI Tools Registry', () => {
|
|
55
|
+
test('cursor uses correct output file path', () => {
|
|
56
|
+
expect(AI_TOOLS.cursor.outputFile).toBe('.cursor/rules/prjct.mdc')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('windsurf uses correct output file path', () => {
|
|
60
|
+
expect(AI_TOOLS.windsurf.outputFile).toBe('.windsurf/rules/prjct.md')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('copilot uses correct output file path', () => {
|
|
64
|
+
expect(AI_TOOLS.copilot.outputFile).toBe('.github/copilot-instructions.md')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('claude uses correct output file path', () => {
|
|
68
|
+
expect(AI_TOOLS.claude.outputFile).toBe('CLAUDE.md')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('continue uses correct output file path', () => {
|
|
72
|
+
expect(AI_TOOLS.continue.outputFile).toBe('.continue/config.json')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('getAIToolConfig returns config for valid tool', () => {
|
|
76
|
+
const config = getAIToolConfig('cursor')
|
|
77
|
+
expect(config).not.toBeNull()
|
|
78
|
+
expect(config?.id).toBe('cursor')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('getAIToolConfig returns null for invalid tool', () => {
|
|
82
|
+
const config = getAIToolConfig('invalid-tool')
|
|
83
|
+
expect(config).toBeNull()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Formatter Tests
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
describe('formatForCursor', () => {
|
|
92
|
+
const config = AI_TOOLS.cursor
|
|
93
|
+
|
|
94
|
+
test('generates MDC format with YAML frontmatter', () => {
|
|
95
|
+
const result = formatForCursor(mockContext, config)
|
|
96
|
+
|
|
97
|
+
// Should start with YAML frontmatter
|
|
98
|
+
expect(result.startsWith('---\n')).toBe(true)
|
|
99
|
+
expect(result).toContain('description: prjct context for test-project')
|
|
100
|
+
expect(result).toContain('alwaysApply: true')
|
|
101
|
+
expect(result).toContain('---')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('includes project info', () => {
|
|
105
|
+
const result = formatForCursor(mockContext, config)
|
|
106
|
+
|
|
107
|
+
expect(result).toContain('test-project')
|
|
108
|
+
expect(result).toContain('library')
|
|
109
|
+
expect(result).toContain('Node.js')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('includes tech stack', () => {
|
|
113
|
+
const result = formatForCursor(mockContext, config)
|
|
114
|
+
|
|
115
|
+
expect(result).toContain('TypeScript')
|
|
116
|
+
expect(result).toContain('Hono')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('includes commands', () => {
|
|
120
|
+
const result = formatForCursor(mockContext, config)
|
|
121
|
+
|
|
122
|
+
expect(result).toContain('bun install')
|
|
123
|
+
expect(result).toContain('bun run dev')
|
|
124
|
+
expect(result).toContain('bun test')
|
|
125
|
+
expect(result).toContain('bun run build')
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('formatForWindsurf', () => {
|
|
130
|
+
const config = AI_TOOLS.windsurf
|
|
131
|
+
|
|
132
|
+
test('generates MD format with YAML frontmatter', () => {
|
|
133
|
+
const result = formatForWindsurf(mockContext, config)
|
|
134
|
+
|
|
135
|
+
// Should start with YAML frontmatter
|
|
136
|
+
expect(result.startsWith('---\n')).toBe(true)
|
|
137
|
+
expect(result).toContain('description: prjct context for test-project')
|
|
138
|
+
expect(result).toContain('trigger: always_on')
|
|
139
|
+
expect(result).toContain('---')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('uses trigger: always_on (not alwaysApply)', () => {
|
|
143
|
+
const result = formatForWindsurf(mockContext, config)
|
|
144
|
+
|
|
145
|
+
expect(result).toContain('trigger: always_on')
|
|
146
|
+
expect(result).not.toContain('alwaysApply')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('includes project info', () => {
|
|
150
|
+
const result = formatForWindsurf(mockContext, config)
|
|
151
|
+
|
|
152
|
+
expect(result).toContain('test-project')
|
|
153
|
+
expect(result).toContain('library')
|
|
154
|
+
expect(result).toContain('Node.js')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('includes commands in bash block', () => {
|
|
158
|
+
const result = formatForWindsurf(mockContext, config)
|
|
159
|
+
|
|
160
|
+
expect(result).toContain('```bash')
|
|
161
|
+
expect(result).toContain('bun install')
|
|
162
|
+
expect(result).toContain('bun run dev')
|
|
163
|
+
expect(result).toContain('bun test')
|
|
164
|
+
expect(result).toContain('bun run build')
|
|
165
|
+
expect(result).toContain('```')
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('formatForCopilot', () => {
|
|
170
|
+
const config = AI_TOOLS.copilot
|
|
171
|
+
|
|
172
|
+
test('generates minimal format without frontmatter', () => {
|
|
173
|
+
const result = formatForCopilot(mockContext, config)
|
|
174
|
+
|
|
175
|
+
// Copilot uses plain markdown, no YAML frontmatter
|
|
176
|
+
expect(result.startsWith('# Copilot Instructions')).toBe(true)
|
|
177
|
+
expect(result).not.toContain('---')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('includes project info', () => {
|
|
181
|
+
const result = formatForCopilot(mockContext, config)
|
|
182
|
+
|
|
183
|
+
expect(result).toContain('test-project')
|
|
184
|
+
expect(result).toContain('library')
|
|
185
|
+
expect(result).toContain('Node.js')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('includes essential commands only', () => {
|
|
189
|
+
const result = formatForCopilot(mockContext, config)
|
|
190
|
+
|
|
191
|
+
expect(result).toContain('bun test')
|
|
192
|
+
expect(result).toContain('bun run build')
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
describe('formatForClaude', () => {
|
|
197
|
+
const config = AI_TOOLS.claude
|
|
198
|
+
|
|
199
|
+
test('generates detailed markdown format', () => {
|
|
200
|
+
const result = formatForClaude(mockContext, config)
|
|
201
|
+
|
|
202
|
+
expect(result).toContain('# test-project - Project Rules')
|
|
203
|
+
expect(result).toContain('<!-- projectId: test-project-id -->')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('includes prjct workflow reference', () => {
|
|
207
|
+
const result = formatForClaude(mockContext, config)
|
|
208
|
+
|
|
209
|
+
expect(result).toContain('## PRJCT RULES')
|
|
210
|
+
expect(result).toContain('p. sync')
|
|
211
|
+
expect(result).toContain('p. task')
|
|
212
|
+
expect(result).toContain('p. done')
|
|
213
|
+
expect(result).toContain('p. ship')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('includes commands table', () => {
|
|
217
|
+
const result = formatForClaude(mockContext, config)
|
|
218
|
+
|
|
219
|
+
expect(result).toContain('| Action | Command |')
|
|
220
|
+
expect(result).toContain('bun install')
|
|
221
|
+
expect(result).toContain('bun run dev')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('includes agent references', () => {
|
|
225
|
+
const result = formatForClaude(mockContext, config)
|
|
226
|
+
|
|
227
|
+
expect(result).toContain('prjct-planner')
|
|
228
|
+
expect(result).toContain('prjct-shipper')
|
|
229
|
+
expect(result).toContain('backend')
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('formatForContinue', () => {
|
|
234
|
+
const config = AI_TOOLS.continue
|
|
235
|
+
|
|
236
|
+
test('generates valid JSON format', () => {
|
|
237
|
+
const result = formatForContinue(mockContext, config)
|
|
238
|
+
|
|
239
|
+
const parsed = JSON.parse(result)
|
|
240
|
+
expect(parsed).toBeDefined()
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('includes system message with project info', () => {
|
|
244
|
+
const result = formatForContinue(mockContext, config)
|
|
245
|
+
const parsed = JSON.parse(result)
|
|
246
|
+
|
|
247
|
+
expect(parsed.systemMessage).toContain('test-project')
|
|
248
|
+
expect(parsed.systemMessage).toContain('library')
|
|
249
|
+
expect(parsed.systemMessage).toContain('Node.js')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('includes context providers', () => {
|
|
253
|
+
const result = formatForContinue(mockContext, config)
|
|
254
|
+
const parsed = JSON.parse(result)
|
|
255
|
+
|
|
256
|
+
expect(parsed.contextProviders).toBeArray()
|
|
257
|
+
expect(parsed.contextProviders.length).toBeGreaterThan(0)
|
|
258
|
+
expect(parsed.contextProviders).toContainEqual({ name: 'code' })
|
|
259
|
+
expect(parsed.contextProviders).toContainEqual({ name: 'diff' })
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('includes slash commands', () => {
|
|
263
|
+
const result = formatForContinue(mockContext, config)
|
|
264
|
+
const parsed = JSON.parse(result)
|
|
265
|
+
|
|
266
|
+
expect(parsed.slashCommands).toBeArray()
|
|
267
|
+
expect(parsed.slashCommands.length).toBeGreaterThan(0)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('includes custom test command with project test command', () => {
|
|
271
|
+
const result = formatForContinue(mockContext, config)
|
|
272
|
+
const parsed = JSON.parse(result)
|
|
273
|
+
|
|
274
|
+
expect(parsed.customCommands).toBeArray()
|
|
275
|
+
const testCmd = parsed.customCommands.find((c: { name: string }) => c.name === 'test')
|
|
276
|
+
expect(testCmd).toBeDefined()
|
|
277
|
+
expect(testCmd.prompt).toContain('bun test')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// getFormatter Tests
|
|
283
|
+
// =============================================================================
|
|
284
|
+
|
|
285
|
+
describe('getFormatter', () => {
|
|
286
|
+
test('returns formatter for claude', () => {
|
|
287
|
+
const formatter = getFormatter('claude')
|
|
288
|
+
expect(formatter).toBe(formatForClaude)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('returns formatter for cursor', () => {
|
|
292
|
+
const formatter = getFormatter('cursor')
|
|
293
|
+
expect(formatter).toBe(formatForCursor)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test('returns formatter for windsurf', () => {
|
|
297
|
+
const formatter = getFormatter('windsurf')
|
|
298
|
+
expect(formatter).toBe(formatForWindsurf)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test('returns formatter for copilot', () => {
|
|
302
|
+
const formatter = getFormatter('copilot')
|
|
303
|
+
expect(formatter).toBe(formatForCopilot)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
test('returns formatter for continue', () => {
|
|
307
|
+
const formatter = getFormatter('continue')
|
|
308
|
+
expect(formatter).toBe(formatForContinue)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('returns null for unknown tool', () => {
|
|
312
|
+
const formatter = getFormatter('unknown-tool')
|
|
313
|
+
expect(formatter).toBeNull()
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
// =============================================================================
|
|
318
|
+
// Edge Cases
|
|
319
|
+
// =============================================================================
|
|
320
|
+
|
|
321
|
+
describe('Formatter Edge Cases', () => {
|
|
322
|
+
test('handles empty languages array', () => {
|
|
323
|
+
const ctx = { ...mockContext, languages: [] }
|
|
324
|
+
|
|
325
|
+
const cursorResult = formatForCursor(ctx, AI_TOOLS.cursor)
|
|
326
|
+
expect(cursorResult).not.toContain('Languages:')
|
|
327
|
+
|
|
328
|
+
const windsurfResult = formatForWindsurf(ctx, AI_TOOLS.windsurf)
|
|
329
|
+
expect(windsurfResult).toContain('## Stack')
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
test('handles empty frameworks array', () => {
|
|
333
|
+
const ctx = { ...mockContext, frameworks: [] }
|
|
334
|
+
|
|
335
|
+
const cursorResult = formatForCursor(ctx, AI_TOOLS.cursor)
|
|
336
|
+
expect(cursorResult).not.toContain('Frameworks:')
|
|
337
|
+
|
|
338
|
+
const windsurfResult = formatForWindsurf(ctx, AI_TOOLS.windsurf)
|
|
339
|
+
expect(windsurfResult).toContain('## Stack')
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test('handles empty agents', () => {
|
|
343
|
+
const ctx = { ...mockContext, agents: { workflow: [], domain: [] } }
|
|
344
|
+
|
|
345
|
+
const claudeResult = formatForClaude(ctx, AI_TOOLS.claude)
|
|
346
|
+
expect(claudeResult).toContain('**Domain**: none')
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
test('handles special characters in project name', () => {
|
|
350
|
+
const ctx = { ...mockContext, name: '@scope/my-project' }
|
|
351
|
+
|
|
352
|
+
const cursorResult = formatForCursor(ctx, AI_TOOLS.cursor)
|
|
353
|
+
expect(cursorResult).toContain('@scope/my-project')
|
|
354
|
+
|
|
355
|
+
const claudeResult = formatForClaude(ctx, AI_TOOLS.claude)
|
|
356
|
+
expect(claudeResult).toContain('@scope/my-project')
|
|
357
|
+
})
|
|
358
|
+
})
|
|
@@ -114,50 +114,60 @@ Load from \`~/.prjct-cli/projects/${ctx.projectId}/agents/\`:
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Format context for Cursor (.
|
|
118
|
-
*
|
|
117
|
+
* Format context for Cursor (.cursor/rules/prjct.mdc)
|
|
118
|
+
* MDC format with YAML frontmatter, optimized for inline suggestions
|
|
119
|
+
*
|
|
120
|
+
* @see https://cursor.com/docs/context/rules
|
|
119
121
|
*/
|
|
120
122
|
export function formatForCursor(ctx: ProjectContext, _config: AIToolConfig): string {
|
|
121
|
-
const
|
|
123
|
+
const lines: string[] = []
|
|
124
|
+
|
|
125
|
+
// MDC format with YAML frontmatter
|
|
126
|
+
lines.push('---')
|
|
127
|
+
lines.push(`description: prjct context for ${ctx.name}`)
|
|
128
|
+
lines.push('globs:')
|
|
129
|
+
lines.push('alwaysApply: true')
|
|
130
|
+
lines.push('---')
|
|
131
|
+
lines.push('')
|
|
122
132
|
|
|
123
133
|
// Project identity
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
lines.push(`You are working on ${ctx.name}, a ${ctx.projectType} ${ctx.ecosystem} project.`)
|
|
135
|
+
lines.push('')
|
|
126
136
|
|
|
127
137
|
// Tech stack
|
|
128
|
-
|
|
138
|
+
lines.push('## Tech Stack')
|
|
129
139
|
if (ctx.languages.length > 0) {
|
|
130
|
-
|
|
140
|
+
lines.push(`- Languages: ${ctx.languages.join(', ')}`)
|
|
131
141
|
}
|
|
132
142
|
if (ctx.frameworks.length > 0) {
|
|
133
|
-
|
|
143
|
+
lines.push(`- Frameworks: ${ctx.frameworks.join(', ')}`)
|
|
134
144
|
}
|
|
135
|
-
|
|
145
|
+
lines.push('')
|
|
136
146
|
|
|
137
147
|
// Commands
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
lines.push('## Commands')
|
|
149
|
+
lines.push(`- Install: \`${ctx.commands.install}\``)
|
|
150
|
+
lines.push(`- Dev: \`${ctx.commands.dev}\``)
|
|
151
|
+
lines.push(`- Test: \`${ctx.commands.test}\``)
|
|
152
|
+
lines.push(`- Build: \`${ctx.commands.build}\``)
|
|
153
|
+
lines.push('')
|
|
144
154
|
|
|
145
155
|
// Code style - language agnostic
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
lines.push('## Code Style')
|
|
157
|
+
lines.push(`- Follow ${ctx.ecosystem} conventions`)
|
|
158
|
+
lines.push('- Match existing code patterns in this project')
|
|
159
|
+
lines.push('- Use idiomatic constructs for the language')
|
|
160
|
+
lines.push('')
|
|
151
161
|
|
|
152
162
|
// Best practices
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return
|
|
163
|
+
lines.push('## Best Practices')
|
|
164
|
+
lines.push('- Write clean, readable code')
|
|
165
|
+
lines.push('- Add comments only for complex logic')
|
|
166
|
+
lines.push('- Keep functions small and focused')
|
|
167
|
+
lines.push('- Handle errors appropriately')
|
|
168
|
+
lines.push('- Write tests for new functionality')
|
|
169
|
+
|
|
170
|
+
return lines.join('\n')
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
/**
|
|
@@ -194,48 +204,57 @@ export function formatForCopilot(ctx: ProjectContext, _config: AIToolConfig): st
|
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
/**
|
|
197
|
-
* Format context for Windsurf (.
|
|
198
|
-
*
|
|
207
|
+
* Format context for Windsurf (.windsurf/rules/prjct.md)
|
|
208
|
+
* MD format with YAML frontmatter, optimized for Cascade AI
|
|
209
|
+
*
|
|
210
|
+
* @see https://docs.windsurf.com/windsurf/cascade/memories
|
|
199
211
|
*/
|
|
200
212
|
export function formatForWindsurf(ctx: ProjectContext, _config: AIToolConfig): string {
|
|
201
|
-
const
|
|
213
|
+
const lines: string[] = []
|
|
214
|
+
|
|
215
|
+
// YAML frontmatter (Windsurf uses trigger: always_on instead of alwaysApply)
|
|
216
|
+
lines.push('---')
|
|
217
|
+
lines.push(`description: prjct context for ${ctx.name}`)
|
|
218
|
+
lines.push('trigger: always_on')
|
|
219
|
+
lines.push('---')
|
|
220
|
+
lines.push('')
|
|
202
221
|
|
|
203
222
|
// Project identity
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
lines.push(`# ${ctx.name}`)
|
|
224
|
+
lines.push('')
|
|
225
|
+
lines.push(`${ctx.projectType} project using ${ctx.ecosystem}.`)
|
|
226
|
+
lines.push('')
|
|
208
227
|
|
|
209
228
|
// Tech stack (concise)
|
|
210
|
-
|
|
211
|
-
|
|
229
|
+
lines.push('## Stack')
|
|
230
|
+
lines.push(`- ${ctx.languages.join(', ')}`)
|
|
212
231
|
if (ctx.frameworks.length > 0) {
|
|
213
|
-
|
|
232
|
+
lines.push(`- ${ctx.frameworks.join(', ')}`)
|
|
214
233
|
}
|
|
215
|
-
|
|
234
|
+
lines.push('')
|
|
216
235
|
|
|
217
236
|
// Commands (essential only)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
lines.push('## Commands')
|
|
238
|
+
lines.push('```bash')
|
|
239
|
+
lines.push(`# Install`)
|
|
240
|
+
lines.push(ctx.commands.install)
|
|
241
|
+
lines.push(`# Dev`)
|
|
242
|
+
lines.push(ctx.commands.dev)
|
|
243
|
+
lines.push(`# Test`)
|
|
244
|
+
lines.push(ctx.commands.test)
|
|
245
|
+
lines.push(`# Build`)
|
|
246
|
+
lines.push(ctx.commands.build)
|
|
247
|
+
lines.push('```')
|
|
248
|
+
lines.push('')
|
|
230
249
|
|
|
231
250
|
// Code style - language agnostic
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
lines.push('## Rules')
|
|
252
|
+
lines.push(`- Follow ${ctx.ecosystem} conventions`)
|
|
253
|
+
lines.push('- Match existing project patterns')
|
|
254
|
+
lines.push('- Clean code, minimal comments')
|
|
255
|
+
lines.push('- Test new functionality')
|
|
237
256
|
|
|
238
|
-
return
|
|
257
|
+
return lines.join('\n')
|
|
239
258
|
}
|
|
240
259
|
|
|
241
260
|
/**
|
|
@@ -40,7 +40,7 @@ export const AI_TOOLS: Record<string, AIToolConfig> = {
|
|
|
40
40
|
cursor: {
|
|
41
41
|
id: 'cursor',
|
|
42
42
|
name: 'Cursor',
|
|
43
|
-
outputFile: '.
|
|
43
|
+
outputFile: '.cursor/rules/prjct.mdc',
|
|
44
44
|
outputPath: 'repo',
|
|
45
45
|
maxTokens: 2000,
|
|
46
46
|
format: 'concise',
|
|
@@ -58,7 +58,7 @@ export const AI_TOOLS: Record<string, AIToolConfig> = {
|
|
|
58
58
|
windsurf: {
|
|
59
59
|
id: 'windsurf',
|
|
60
60
|
name: 'Windsurf',
|
|
61
|
-
outputFile: '.
|
|
61
|
+
outputFile: '.windsurf/rules/prjct.md',
|
|
62
62
|
outputPath: 'repo',
|
|
63
63
|
maxTokens: 2000,
|
|
64
64
|
format: 'concise',
|
|
@@ -142,9 +142,14 @@ class SyncService {
|
|
|
142
142
|
const startTime = Date.now()
|
|
143
143
|
|
|
144
144
|
// Resolve AI tools: supports 'auto', 'all', or specific list
|
|
145
|
+
// Default behavior: claude + any detected IDE tools (.cursor/, .windsurf/)
|
|
145
146
|
let aiToolIds: string[]
|
|
146
147
|
if (!options.aiTools || options.aiTools.length === 0) {
|
|
147
|
-
|
|
148
|
+
// Start with default CLI tools and add detected IDE tools
|
|
149
|
+
const detectedIdeTools = detectInstalledTools(projectPath).filter(
|
|
150
|
+
(id) => !DEFAULT_AI_TOOLS.includes(id)
|
|
151
|
+
)
|
|
152
|
+
aiToolIds = [...DEFAULT_AI_TOOLS, ...detectedIdeTools]
|
|
148
153
|
} else if (options.aiTools[0] === 'auto') {
|
|
149
154
|
aiToolIds = detectInstalledTools(projectPath)
|
|
150
155
|
if (aiToolIds.length === 0) aiToolIds = ['claude'] // fallback
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -19701,35 +19701,41 @@ Load from \`~/.prjct-cli/projects/${ctx.projectId}/agents/\`:
|
|
|
19701
19701
|
`;
|
|
19702
19702
|
}
|
|
19703
19703
|
function formatForCursor(ctx, _config) {
|
|
19704
|
-
const
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19704
|
+
const lines = [];
|
|
19705
|
+
lines.push("---");
|
|
19706
|
+
lines.push(`description: prjct context for ${ctx.name}`);
|
|
19707
|
+
lines.push("globs:");
|
|
19708
|
+
lines.push("alwaysApply: true");
|
|
19709
|
+
lines.push("---");
|
|
19710
|
+
lines.push("");
|
|
19711
|
+
lines.push(`You are working on ${ctx.name}, a ${ctx.projectType} ${ctx.ecosystem} project.`);
|
|
19712
|
+
lines.push("");
|
|
19713
|
+
lines.push("## Tech Stack");
|
|
19708
19714
|
if (ctx.languages.length > 0) {
|
|
19709
|
-
|
|
19715
|
+
lines.push(`- Languages: ${ctx.languages.join(", ")}`);
|
|
19710
19716
|
}
|
|
19711
19717
|
if (ctx.frameworks.length > 0) {
|
|
19712
|
-
|
|
19713
|
-
}
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
19728
|
-
|
|
19729
|
-
|
|
19730
|
-
|
|
19731
|
-
|
|
19732
|
-
return
|
|
19718
|
+
lines.push(`- Frameworks: ${ctx.frameworks.join(", ")}`);
|
|
19719
|
+
}
|
|
19720
|
+
lines.push("");
|
|
19721
|
+
lines.push("## Commands");
|
|
19722
|
+
lines.push(`- Install: \`${ctx.commands.install}\``);
|
|
19723
|
+
lines.push(`- Dev: \`${ctx.commands.dev}\``);
|
|
19724
|
+
lines.push(`- Test: \`${ctx.commands.test}\``);
|
|
19725
|
+
lines.push(`- Build: \`${ctx.commands.build}\``);
|
|
19726
|
+
lines.push("");
|
|
19727
|
+
lines.push("## Code Style");
|
|
19728
|
+
lines.push(`- Follow ${ctx.ecosystem} conventions`);
|
|
19729
|
+
lines.push("- Match existing code patterns in this project");
|
|
19730
|
+
lines.push("- Use idiomatic constructs for the language");
|
|
19731
|
+
lines.push("");
|
|
19732
|
+
lines.push("## Best Practices");
|
|
19733
|
+
lines.push("- Write clean, readable code");
|
|
19734
|
+
lines.push("- Add comments only for complex logic");
|
|
19735
|
+
lines.push("- Keep functions small and focused");
|
|
19736
|
+
lines.push("- Handle errors appropriately");
|
|
19737
|
+
lines.push("- Write tests for new functionality");
|
|
19738
|
+
return lines.join("\n");
|
|
19733
19739
|
}
|
|
19734
19740
|
function formatForCopilot(ctx, _config) {
|
|
19735
19741
|
const lines = [];
|
|
@@ -19752,35 +19758,40 @@ function formatForCopilot(ctx, _config) {
|
|
|
19752
19758
|
return lines.join("\n");
|
|
19753
19759
|
}
|
|
19754
19760
|
function formatForWindsurf(ctx, _config) {
|
|
19755
|
-
const
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
|
|
19761
|
+
const lines = [];
|
|
19762
|
+
lines.push("---");
|
|
19763
|
+
lines.push(`description: prjct context for ${ctx.name}`);
|
|
19764
|
+
lines.push("trigger: always_on");
|
|
19765
|
+
lines.push("---");
|
|
19766
|
+
lines.push("");
|
|
19767
|
+
lines.push(`# ${ctx.name}`);
|
|
19768
|
+
lines.push("");
|
|
19769
|
+
lines.push(`${ctx.projectType} project using ${ctx.ecosystem}.`);
|
|
19770
|
+
lines.push("");
|
|
19771
|
+
lines.push("## Stack");
|
|
19772
|
+
lines.push(`- ${ctx.languages.join(", ")}`);
|
|
19762
19773
|
if (ctx.frameworks.length > 0) {
|
|
19763
|
-
|
|
19764
|
-
}
|
|
19765
|
-
|
|
19766
|
-
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19782
|
-
|
|
19783
|
-
return
|
|
19774
|
+
lines.push(`- ${ctx.frameworks.join(", ")}`);
|
|
19775
|
+
}
|
|
19776
|
+
lines.push("");
|
|
19777
|
+
lines.push("## Commands");
|
|
19778
|
+
lines.push("```bash");
|
|
19779
|
+
lines.push(`# Install`);
|
|
19780
|
+
lines.push(ctx.commands.install);
|
|
19781
|
+
lines.push(`# Dev`);
|
|
19782
|
+
lines.push(ctx.commands.dev);
|
|
19783
|
+
lines.push(`# Test`);
|
|
19784
|
+
lines.push(ctx.commands.test);
|
|
19785
|
+
lines.push(`# Build`);
|
|
19786
|
+
lines.push(ctx.commands.build);
|
|
19787
|
+
lines.push("```");
|
|
19788
|
+
lines.push("");
|
|
19789
|
+
lines.push("## Rules");
|
|
19790
|
+
lines.push(`- Follow ${ctx.ecosystem} conventions`);
|
|
19791
|
+
lines.push("- Match existing project patterns");
|
|
19792
|
+
lines.push("- Clean code, minimal comments");
|
|
19793
|
+
lines.push("- Test new functionality");
|
|
19794
|
+
return lines.join("\n");
|
|
19784
19795
|
}
|
|
19785
19796
|
function formatForContinue(ctx, _config) {
|
|
19786
19797
|
const systemMessage = [
|
|
@@ -19907,7 +19918,7 @@ var init_registry = __esm({
|
|
|
19907
19918
|
cursor: {
|
|
19908
19919
|
id: "cursor",
|
|
19909
19920
|
name: "Cursor",
|
|
19910
|
-
outputFile: ".
|
|
19921
|
+
outputFile: ".cursor/rules/prjct.mdc",
|
|
19911
19922
|
outputPath: "repo",
|
|
19912
19923
|
maxTokens: 2e3,
|
|
19913
19924
|
format: "concise",
|
|
@@ -19925,7 +19936,7 @@ var init_registry = __esm({
|
|
|
19925
19936
|
windsurf: {
|
|
19926
19937
|
id: "windsurf",
|
|
19927
19938
|
name: "Windsurf",
|
|
19928
|
-
outputFile: ".
|
|
19939
|
+
outputFile: ".windsurf/rules/prjct.md",
|
|
19929
19940
|
outputPath: "repo",
|
|
19930
19941
|
maxTokens: 2e3,
|
|
19931
19942
|
format: "concise",
|
|
@@ -20704,7 +20715,10 @@ var init_sync_service = __esm({
|
|
|
20704
20715
|
const startTime = Date.now();
|
|
20705
20716
|
let aiToolIds;
|
|
20706
20717
|
if (!options.aiTools || options.aiTools.length === 0) {
|
|
20707
|
-
|
|
20718
|
+
const detectedIdeTools = detectInstalledTools(projectPath).filter(
|
|
20719
|
+
(id) => !DEFAULT_AI_TOOLS.includes(id)
|
|
20720
|
+
);
|
|
20721
|
+
aiToolIds = [...DEFAULT_AI_TOOLS, ...detectedIdeTools];
|
|
20708
20722
|
} else if (options.aiTools[0] === "auto") {
|
|
20709
20723
|
aiToolIds = detectInstalledTools(projectPath);
|
|
20710
20724
|
if (aiToolIds.length === 0) aiToolIds = ["claude"];
|
|
@@ -26845,7 +26859,7 @@ var require_package = __commonJS({
|
|
|
26845
26859
|
"package.json"(exports, module) {
|
|
26846
26860
|
module.exports = {
|
|
26847
26861
|
name: "prjct-cli",
|
|
26848
|
-
version: "0.64.
|
|
26862
|
+
version: "0.64.1",
|
|
26849
26863
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
26850
26864
|
main: "core/index.ts",
|
|
26851
26865
|
bin: {
|