prjct-cli 0.7.2 → 0.8.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/CHANGELOG.md +224 -0
- package/CLAUDE.md +219 -2
- package/README.md +32 -0
- package/core/__tests__/agentic/command-executor.test.js +223 -0
- package/core/__tests__/agentic/context-builder.test.js +160 -0
- package/core/__tests__/agentic/prompt-builder.test.js +212 -0
- package/core/__tests__/agentic/template-loader.test.js +164 -0
- package/core/__tests__/agentic/tool-registry.test.js +243 -0
- package/core/__tests__/domain/agent-generator.test.js +296 -0
- package/core/__tests__/setup.test.js +15 -0
- package/core/__tests__/utils/date-helper.test.js +169 -0
- package/core/__tests__/utils/file-helper.test.js +258 -0
- package/core/command-registry.js +52 -4
- package/core/commands.js +0 -3
- package/core/infrastructure/session-manager.js +0 -1
- package/core/utils/file-helper.js +2 -0
- package/package.json +8 -3
- package/templates/commands/ask.md +386 -0
- package/templates/commands/feature.md +127 -1
- package/templates/commands/help.md +330 -20
- package/templates/commands/init.md +116 -9
- package/templates/commands/suggest.md +555 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('Vitest Setup', () => {
|
|
4
|
+
it('should pass basic assertion', () => {
|
|
5
|
+
expect(true).toBe(true)
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('should handle numbers', () => {
|
|
9
|
+
expect(2 + 2).toBe(4)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should handle strings', () => {
|
|
13
|
+
expect('prjct-cli').toContain('prjct')
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import * as dateHelper from '../../utils/date-helper.js'
|
|
3
|
+
|
|
4
|
+
describe('Date Helper', () => {
|
|
5
|
+
describe('formatDate()', () => {
|
|
6
|
+
it('should format date to YYYY-MM-DD', () => {
|
|
7
|
+
const date = new Date(2025, 9, 4, 12, 0, 0) // October 4, 2025
|
|
8
|
+
const formatted = dateHelper.formatDate(date)
|
|
9
|
+
|
|
10
|
+
expect(formatted).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should pad single digit month', () => {
|
|
14
|
+
const date = new Date(2025, 2, 15) // March 15, 2025
|
|
15
|
+
const formatted = dateHelper.formatDate(date)
|
|
16
|
+
|
|
17
|
+
expect(formatted).toContain('-03-')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should pad single digit day', () => {
|
|
21
|
+
const date = new Date(2025, 9, 5) // October 5, 2025
|
|
22
|
+
const formatted = dateHelper.formatDate(date)
|
|
23
|
+
|
|
24
|
+
expect(formatted).toContain('-05')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should handle edge case dates', () => {
|
|
28
|
+
const date = new Date(2025, 0, 1) // January 1, 2025
|
|
29
|
+
const formatted = dateHelper.formatDate(date)
|
|
30
|
+
|
|
31
|
+
expect(formatted).toBe('2025-01-01')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should handle end of year', () => {
|
|
35
|
+
const date = new Date(2025, 11, 31) // December 31, 2025
|
|
36
|
+
const formatted = dateHelper.formatDate(date)
|
|
37
|
+
|
|
38
|
+
expect(formatted).toBe('2025-12-31')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('formatMonth()', () => {
|
|
43
|
+
it('should format date to YYYY-MM', () => {
|
|
44
|
+
const date = new Date('2025-10-04')
|
|
45
|
+
const formatted = dateHelper.formatMonth(date)
|
|
46
|
+
|
|
47
|
+
expect(formatted).toMatch(/^\d{4}-\d{2}$/)
|
|
48
|
+
expect(formatted).toBe('2025-10')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should pad single digit month', () => {
|
|
52
|
+
const date = new Date('2025-03-15')
|
|
53
|
+
const formatted = dateHelper.formatMonth(date)
|
|
54
|
+
|
|
55
|
+
expect(formatted).toBe('2025-03')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should handle January', () => {
|
|
59
|
+
const date = new Date('2025-01-15')
|
|
60
|
+
const formatted = dateHelper.formatMonth(date)
|
|
61
|
+
|
|
62
|
+
expect(formatted).toBe('2025-01')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should handle December', () => {
|
|
66
|
+
const date = new Date('2025-12-25')
|
|
67
|
+
const formatted = dateHelper.formatMonth(date)
|
|
68
|
+
|
|
69
|
+
expect(formatted).toBe('2025-12')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('getTodayKey()', () => {
|
|
74
|
+
it('should return today in YYYY-MM-DD format', () => {
|
|
75
|
+
const today = dateHelper.getTodayKey()
|
|
76
|
+
|
|
77
|
+
expect(today).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should match formatDate for current date', () => {
|
|
81
|
+
const today = dateHelper.getTodayKey()
|
|
82
|
+
const now = dateHelper.formatDate(new Date())
|
|
83
|
+
|
|
84
|
+
expect(today).toBe(now)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('getDateKey()', () => {
|
|
89
|
+
it('should return date key in YYYY-MM-DD format', () => {
|
|
90
|
+
const date = new Date(2025, 9, 4) // October 4, 2025
|
|
91
|
+
const key = dateHelper.getDateKey(date)
|
|
92
|
+
|
|
93
|
+
expect(key).toBe('2025-10-04')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should be alias for formatDate', () => {
|
|
97
|
+
const date = new Date(2025, 9, 4) // October 4, 2025
|
|
98
|
+
|
|
99
|
+
expect(dateHelper.getDateKey(date)).toBe(dateHelper.formatDate(date))
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('getTimestamp()', () => {
|
|
104
|
+
it('should return ISO timestamp', () => {
|
|
105
|
+
const timestamp = dateHelper.getTimestamp()
|
|
106
|
+
|
|
107
|
+
expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should be valid date string', () => {
|
|
111
|
+
const timestamp = dateHelper.getTimestamp()
|
|
112
|
+
const date = new Date(timestamp)
|
|
113
|
+
|
|
114
|
+
expect(date.toString()).not.toBe('Invalid Date')
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('calculateDuration()', () => {
|
|
119
|
+
it('should calculate duration in minutes', () => {
|
|
120
|
+
const start = new Date('2025-10-04T10:00:00Z')
|
|
121
|
+
const end = new Date('2025-10-04T10:30:00Z')
|
|
122
|
+
const duration = dateHelper.calculateDuration(start, end)
|
|
123
|
+
|
|
124
|
+
expect(duration).toContain('30m')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should calculate duration in hours and minutes', () => {
|
|
128
|
+
const start = new Date('2025-10-04T10:00:00Z')
|
|
129
|
+
const end = new Date('2025-10-04T12:30:00Z')
|
|
130
|
+
const duration = dateHelper.calculateDuration(start, end)
|
|
131
|
+
|
|
132
|
+
expect(duration).toContain('2h')
|
|
133
|
+
expect(duration).toContain('30m')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should handle exact hours', () => {
|
|
137
|
+
const start = new Date('2025-10-04T10:00:00Z')
|
|
138
|
+
const end = new Date('2025-10-04T13:00:00Z')
|
|
139
|
+
const duration = dateHelper.calculateDuration(start, end)
|
|
140
|
+
|
|
141
|
+
expect(duration).toContain('3h')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should handle less than 1 minute', () => {
|
|
145
|
+
const start = new Date('2025-10-04T10:00:00Z')
|
|
146
|
+
const end = new Date('2025-10-04T10:00:30Z')
|
|
147
|
+
const duration = dateHelper.calculateDuration(start, end)
|
|
148
|
+
|
|
149
|
+
expect(duration).toContain('30s')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('Edge Cases', () => {
|
|
154
|
+
it('should handle leap year', () => {
|
|
155
|
+
const date = new Date(2024, 1, 29) // February 29, 2024
|
|
156
|
+
const formatted = dateHelper.formatDate(date)
|
|
157
|
+
|
|
158
|
+
expect(formatted).toBe('2024-02-29')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should handle different years', () => {
|
|
162
|
+
const date2024 = new Date(2024, 9, 4) // October 4, 2024
|
|
163
|
+
const date2025 = new Date(2025, 9, 4) // October 4, 2025
|
|
164
|
+
|
|
165
|
+
expect(dateHelper.formatDate(date2024)).toBe('2024-10-04')
|
|
166
|
+
expect(dateHelper.formatDate(date2025)).toBe('2025-10-04')
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
})
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as fileHelper from '../../utils/file-helper.js'
|
|
3
|
+
import fs from 'fs/promises'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
|
|
7
|
+
describe('File Helper', () => {
|
|
8
|
+
const testDir = path.join(os.tmpdir(), 'prjct-file-helper-test-' + Date.now())
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await fs.mkdir(testDir, { recursive: true })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
try {
|
|
16
|
+
await fs.rm(testDir, { recursive: true, force: true })
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// Ignore cleanup errors
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('readJson()', () => {
|
|
23
|
+
it('should read and parse JSON file', async () => {
|
|
24
|
+
const testFile = path.join(testDir, 'test.json')
|
|
25
|
+
const data = { name: 'test', value: 123 }
|
|
26
|
+
await fs.writeFile(testFile, JSON.stringify(data))
|
|
27
|
+
|
|
28
|
+
const result = await fileHelper.readJson(testFile)
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual(data)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should return default value for non-existent file', async () => {
|
|
34
|
+
const result = await fileHelper.readJson('/nonexistent/file.json', { default: true })
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({ default: true })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should return null by default for non-existent file', async () => {
|
|
40
|
+
const result = await fileHelper.readJson('/nonexistent/file.json')
|
|
41
|
+
|
|
42
|
+
expect(result).toBeNull()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should handle complex JSON structures', async () => {
|
|
46
|
+
const testFile = path.join(testDir, 'complex.json')
|
|
47
|
+
const data = {
|
|
48
|
+
nested: {
|
|
49
|
+
array: [1, 2, 3],
|
|
50
|
+
object: { key: 'value' },
|
|
51
|
+
},
|
|
52
|
+
boolean: true,
|
|
53
|
+
number: 42,
|
|
54
|
+
}
|
|
55
|
+
await fs.writeFile(testFile, JSON.stringify(data))
|
|
56
|
+
|
|
57
|
+
const result = await fileHelper.readJson(testFile)
|
|
58
|
+
|
|
59
|
+
expect(result).toEqual(data)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('writeJson()', () => {
|
|
64
|
+
it('should write JSON file with pretty formatting', async () => {
|
|
65
|
+
const testFile = path.join(testDir, 'write.json')
|
|
66
|
+
const data = { name: 'test', value: 123 }
|
|
67
|
+
|
|
68
|
+
await fileHelper.writeJson(testFile, data)
|
|
69
|
+
|
|
70
|
+
const content = await fs.readFile(testFile, 'utf-8')
|
|
71
|
+
expect(JSON.parse(content)).toEqual(data)
|
|
72
|
+
expect(content).toContain('\n') // Should be pretty-printed
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should use custom indentation', async () => {
|
|
76
|
+
const testFile = path.join(testDir, 'indent.json')
|
|
77
|
+
const data = { key: 'value' }
|
|
78
|
+
|
|
79
|
+
await fileHelper.writeJson(testFile, data, 4)
|
|
80
|
+
|
|
81
|
+
const content = await fs.readFile(testFile, 'utf-8')
|
|
82
|
+
expect(content).toContain(' ') // 4 spaces
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should handle nested objects', async () => {
|
|
86
|
+
const testFile = path.join(testDir, 'nested.json')
|
|
87
|
+
const data = {
|
|
88
|
+
level1: {
|
|
89
|
+
level2: {
|
|
90
|
+
level3: 'deep',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await fileHelper.writeJson(testFile, data)
|
|
96
|
+
|
|
97
|
+
const result = await fileHelper.readJson(testFile)
|
|
98
|
+
expect(result).toEqual(data)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('readFile()', () => {
|
|
103
|
+
it('should read text file', async () => {
|
|
104
|
+
const testFile = path.join(testDir, 'text.txt')
|
|
105
|
+
const content = 'Hello, World!'
|
|
106
|
+
await fs.writeFile(testFile, content)
|
|
107
|
+
|
|
108
|
+
const result = await fileHelper.readFile(testFile)
|
|
109
|
+
|
|
110
|
+
expect(result).toBe(content)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should return default value for non-existent file', async () => {
|
|
114
|
+
const result = await fileHelper.readFile('/nonexistent/file.txt', 'default content')
|
|
115
|
+
|
|
116
|
+
expect(result).toBe('default content')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should return empty string by default', async () => {
|
|
120
|
+
const result = await fileHelper.readFile('/nonexistent/file.txt')
|
|
121
|
+
|
|
122
|
+
expect(result).toBe('')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should handle multi-line content', async () => {
|
|
126
|
+
const testFile = path.join(testDir, 'multiline.txt')
|
|
127
|
+
const content = 'Line 1\nLine 2\nLine 3'
|
|
128
|
+
await fs.writeFile(testFile, content)
|
|
129
|
+
|
|
130
|
+
const result = await fileHelper.readFile(testFile)
|
|
131
|
+
|
|
132
|
+
expect(result).toBe(content)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('writeFile()', () => {
|
|
137
|
+
it('should write text file', async () => {
|
|
138
|
+
const testFile = path.join(testDir, 'write.txt')
|
|
139
|
+
const content = 'Test content'
|
|
140
|
+
|
|
141
|
+
await fileHelper.writeFile(testFile, content)
|
|
142
|
+
|
|
143
|
+
const result = await fs.readFile(testFile, 'utf-8')
|
|
144
|
+
expect(result).toBe(content)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should overwrite existing file', async () => {
|
|
148
|
+
const testFile = path.join(testDir, 'overwrite.txt')
|
|
149
|
+
|
|
150
|
+
await fileHelper.writeFile(testFile, 'First')
|
|
151
|
+
await fileHelper.writeFile(testFile, 'Second')
|
|
152
|
+
|
|
153
|
+
const result = await fs.readFile(testFile, 'utf-8')
|
|
154
|
+
expect(result).toBe('Second')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should create directory if needed', async () => {
|
|
158
|
+
const nestedFile = path.join(testDir, 'nested', 'dir', 'file.txt')
|
|
159
|
+
|
|
160
|
+
await fileHelper.writeFile(nestedFile, 'content')
|
|
161
|
+
|
|
162
|
+
const exists = await fs
|
|
163
|
+
.access(nestedFile)
|
|
164
|
+
.then(() => true)
|
|
165
|
+
.catch(() => false)
|
|
166
|
+
expect(exists).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('fileExists()', () => {
|
|
171
|
+
it('should return true for existing file', async () => {
|
|
172
|
+
const testFile = path.join(testDir, 'exists.txt')
|
|
173
|
+
await fs.writeFile(testFile, 'content')
|
|
174
|
+
|
|
175
|
+
const exists = await fileHelper.fileExists(testFile)
|
|
176
|
+
|
|
177
|
+
expect(exists).toBe(true)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should return false for non-existent file', async () => {
|
|
181
|
+
const exists = await fileHelper.fileExists('/nonexistent/file.txt')
|
|
182
|
+
|
|
183
|
+
expect(exists).toBe(false)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should work with directories', async () => {
|
|
187
|
+
const exists = await fileHelper.fileExists(testDir)
|
|
188
|
+
|
|
189
|
+
expect(exists).toBe(true)
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('ensureDir()', () => {
|
|
194
|
+
it('should create directory if not exists', async () => {
|
|
195
|
+
const newDir = path.join(testDir, 'new', 'nested', 'dir')
|
|
196
|
+
|
|
197
|
+
await fileHelper.ensureDir(newDir)
|
|
198
|
+
|
|
199
|
+
const exists = await fs
|
|
200
|
+
.access(newDir)
|
|
201
|
+
.then(() => true)
|
|
202
|
+
.catch(() => false)
|
|
203
|
+
expect(exists).toBe(true)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should not fail if directory exists', async () => {
|
|
207
|
+
await fileHelper.ensureDir(testDir)
|
|
208
|
+
|
|
209
|
+
// Should not throw
|
|
210
|
+
await expect(fileHelper.ensureDir(testDir)).resolves.not.toThrow()
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('Integration', () => {
|
|
215
|
+
it('should read and write JSON files', async () => {
|
|
216
|
+
const testFile = path.join(testDir, 'integration.json')
|
|
217
|
+
const data = { test: 'data', number: 42 }
|
|
218
|
+
|
|
219
|
+
await fileHelper.writeJson(testFile, data)
|
|
220
|
+
const result = await fileHelper.readJson(testFile)
|
|
221
|
+
|
|
222
|
+
expect(result).toEqual(data)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should handle file operations pipeline', async () => {
|
|
226
|
+
const testFile = path.join(testDir, 'pipeline.txt')
|
|
227
|
+
|
|
228
|
+
// Write
|
|
229
|
+
await fileHelper.writeFile(testFile, 'original')
|
|
230
|
+
|
|
231
|
+
// Check exists
|
|
232
|
+
const exists = await fileHelper.fileExists(testFile)
|
|
233
|
+
expect(exists).toBe(true)
|
|
234
|
+
|
|
235
|
+
// Read
|
|
236
|
+
const content = await fileHelper.readFile(testFile)
|
|
237
|
+
expect(content).toBe('original')
|
|
238
|
+
|
|
239
|
+
// Update
|
|
240
|
+
await fileHelper.writeFile(testFile, 'updated')
|
|
241
|
+
|
|
242
|
+
// Read again
|
|
243
|
+
const updated = await fileHelper.readFile(testFile)
|
|
244
|
+
expect(updated).toBe('updated')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should create nested structure and write files', async () => {
|
|
248
|
+
const nestedDir = path.join(testDir, 'level1', 'level2', 'level3')
|
|
249
|
+
const nestedFile = path.join(nestedDir, 'deep.json')
|
|
250
|
+
|
|
251
|
+
await fileHelper.ensureDir(nestedDir)
|
|
252
|
+
await fileHelper.writeJson(nestedFile, { deep: true })
|
|
253
|
+
|
|
254
|
+
const result = await fileHelper.readJson(nestedFile)
|
|
255
|
+
expect(result).toEqual({ deep: true })
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
})
|
package/core/command-registry.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* - CLAUDE.md (AI assistant instructions)
|
|
9
9
|
* - scripts/validate-commands.js (validation)
|
|
10
10
|
*
|
|
11
|
-
* @version 0.
|
|
11
|
+
* @version 0.8.0 - Conversational interface with zero memorization
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const COMMANDS = [
|
|
15
|
-
// ===== CORE WORKFLOW COMMANDS (
|
|
15
|
+
// ===== CORE WORKFLOW COMMANDS (13 essential) =====
|
|
16
16
|
|
|
17
17
|
// 1. Initialize
|
|
18
18
|
{
|
|
@@ -267,7 +267,55 @@ const COMMANDS = [
|
|
|
267
267
|
],
|
|
268
268
|
},
|
|
269
269
|
|
|
270
|
-
// 11.
|
|
270
|
+
// 11. Ask - Intent to Action
|
|
271
|
+
{
|
|
272
|
+
name: 'ask',
|
|
273
|
+
category: 'core',
|
|
274
|
+
description: 'Conversational intent to action translator',
|
|
275
|
+
usage: {
|
|
276
|
+
claude: '/p:ask "what you want to do"',
|
|
277
|
+
terminal: 'prjct ask "what you want to do"',
|
|
278
|
+
},
|
|
279
|
+
params: '<description>',
|
|
280
|
+
implemented: true,
|
|
281
|
+
hasTemplate: true,
|
|
282
|
+
icon: 'MessageCircle',
|
|
283
|
+
requiresInit: false, // Can work before init to guide setup
|
|
284
|
+
blockingRules: null,
|
|
285
|
+
features: [
|
|
286
|
+
'Natural language understanding',
|
|
287
|
+
'Recommends command flow',
|
|
288
|
+
'Educational explanations',
|
|
289
|
+
'Interactive confirmation',
|
|
290
|
+
'Works in any language',
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// 12. Suggest - Context-Aware Recommendations
|
|
295
|
+
{
|
|
296
|
+
name: 'suggest',
|
|
297
|
+
category: 'core',
|
|
298
|
+
description: 'Context-aware next steps suggestions',
|
|
299
|
+
usage: {
|
|
300
|
+
claude: '/p:suggest',
|
|
301
|
+
terminal: 'prjct suggest',
|
|
302
|
+
},
|
|
303
|
+
params: null,
|
|
304
|
+
implemented: true,
|
|
305
|
+
hasTemplate: true,
|
|
306
|
+
icon: 'Lightbulb',
|
|
307
|
+
requiresInit: true,
|
|
308
|
+
blockingRules: null,
|
|
309
|
+
features: [
|
|
310
|
+
'Analyzes project state',
|
|
311
|
+
'Recommends actions',
|
|
312
|
+
'Urgency detection',
|
|
313
|
+
'Momentum tracking',
|
|
314
|
+
'Personalized suggestions',
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// 13. Architect Execute
|
|
271
319
|
{
|
|
272
320
|
name: 'architect',
|
|
273
321
|
category: 'core',
|
|
@@ -446,7 +494,7 @@ const CATEGORIES = {
|
|
|
446
494
|
core: {
|
|
447
495
|
title: 'Core Workflow',
|
|
448
496
|
icon: 'Zap',
|
|
449
|
-
description: '
|
|
497
|
+
description: '13 essential commands for daily development workflow',
|
|
450
498
|
order: 1,
|
|
451
499
|
},
|
|
452
500
|
optional: {
|
package/core/commands.js
CHANGED
|
@@ -2632,8 +2632,6 @@ Agent: ${agent}
|
|
|
2632
2632
|
const skipped = []
|
|
2633
2633
|
|
|
2634
2634
|
for (const projectId of projectIds) {
|
|
2635
|
-
const globalProjectPath = path.join(globalRoot, projectId)
|
|
2636
|
-
|
|
2637
2635
|
// Read global config to get project path
|
|
2638
2636
|
const globalConfig = await configManager.readGlobalConfig(projectId)
|
|
2639
2637
|
if (!globalConfig || !globalConfig.projectPath) {
|
|
@@ -2704,7 +2702,6 @@ Agent: ${agent}
|
|
|
2704
2702
|
console.log('🏗️ Architect Mode - Code Generation\n')
|
|
2705
2703
|
|
|
2706
2704
|
const globalPath = await this.getGlobalProjectPath(projectPath)
|
|
2707
|
-
const architectSession = require('./domain/architect-session')
|
|
2708
2705
|
|
|
2709
2706
|
// Check if there's a completed plan
|
|
2710
2707
|
const planPath = path.join(globalPath, 'planning', 'architect-session.md')
|
|
@@ -70,6 +70,8 @@ async function readFile(filePath, defaultValue = '') {
|
|
|
70
70
|
* @returns {Promise<void>}
|
|
71
71
|
*/
|
|
72
72
|
async function writeFile(filePath, content) {
|
|
73
|
+
const dir = path.dirname(filePath)
|
|
74
|
+
await fs.mkdir(dir, { recursive: true })
|
|
73
75
|
await fs.writeFile(filePath, content, 'utf-8')
|
|
74
76
|
}
|
|
75
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prjct-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"install-global": "./scripts/install.sh",
|
|
15
|
-
"test": "
|
|
15
|
+
"test": "vitest run --workspace=vitest.workspace.js",
|
|
16
|
+
"test:watch": "vitest --workspace=vitest.workspace.js",
|
|
17
|
+
"test:coverage": "vitest run --coverage --workspace=vitest.workspace.js",
|
|
16
18
|
"validate": "node scripts/validate-commands.js",
|
|
17
19
|
"lint": "eslint \"**/*.js\" --ignore-pattern \"node_modules/**\" --ignore-pattern \"website/**\"",
|
|
18
20
|
"lint:fix": "eslint \"**/*.js\" --fix --ignore-pattern \"node_modules/**\" --ignore-pattern \"website/**\"",
|
|
@@ -43,15 +45,18 @@
|
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@types/node": "^20.0.0",
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
46
49
|
"eslint": "^8.57.1",
|
|
47
50
|
"eslint-config-prettier": "^10.1.8",
|
|
48
51
|
"eslint-config-standard": "^17.1.0",
|
|
49
52
|
"eslint-plugin-import": "^2.32.0",
|
|
50
53
|
"eslint-plugin-n": "^16.6.2",
|
|
51
54
|
"eslint-plugin-promise": "^6.6.0",
|
|
55
|
+
"jsdom": "^27.0.0",
|
|
52
56
|
"prettier": "^3.6.2",
|
|
53
57
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
54
|
-
"typescript": "^5.0.0"
|
|
58
|
+
"typescript": "^5.0.0",
|
|
59
|
+
"vitest": "^3.2.4"
|
|
55
60
|
},
|
|
56
61
|
"repository": {
|
|
57
62
|
"type": "git",
|