prjct-cli 0.15.1 → 0.17.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 +35 -0
- package/bin/dev.js +0 -1
- package/bin/serve.js +19 -20
- package/core/agentic/agent-router.ts +79 -14
- package/core/agentic/command-executor/command-executor.ts +2 -74
- package/core/agentic/services.ts +0 -48
- package/core/agentic/template-loader.ts +35 -1
- package/core/commands/base.ts +96 -77
- package/core/commands/planning.ts +13 -2
- package/core/commands/setup.ts +3 -85
- package/core/errors.ts +209 -0
- package/core/infrastructure/config-manager.ts +22 -5
- package/core/infrastructure/setup.ts +5 -50
- package/core/storage/storage-manager.ts +42 -6
- package/core/utils/logger.ts +19 -12
- package/package.json +2 -4
- package/templates/agentic/subagent-generation.md +109 -0
- package/templates/commands/sync.md +74 -13
- package/templates/subagents/domain/backend.md +105 -0
- package/templates/subagents/domain/database.md +118 -0
- package/templates/subagents/domain/devops.md +148 -0
- package/templates/subagents/domain/frontend.md +99 -0
- package/templates/subagents/domain/testing.md +169 -0
- package/templates/subagents/workflow/prjct-planner.md +158 -0
- package/templates/subagents/workflow/prjct-shipper.md +179 -0
- package/templates/subagents/workflow/prjct-workflow.md +98 -0
- package/bin/generate-views.js +0 -209
- package/bin/migrate-to-json.js +0 -742
- package/core/agentic/context-filter.ts +0 -365
- package/core/agentic/parallel-tools.ts +0 -165
- package/core/agentic/response-templates.ts +0 -164
- package/core/agentic/semantic-compression.ts +0 -273
- package/core/agentic/think-blocks.ts +0 -202
- package/core/agentic/validation-rules.ts +0 -313
- package/core/domain/agent-matcher.ts +0 -130
- package/core/domain/agent-validator.ts +0 -250
- package/core/domain/architect-session.ts +0 -315
- package/core/domain/product-standards.ts +0 -106
- package/core/domain/smart-cache.ts +0 -167
- package/core/domain/task-analyzer.ts +0 -296
- package/core/infrastructure/legacy-installer-detector/cleanup.ts +0 -216
- package/core/infrastructure/legacy-installer-detector/detection.ts +0 -95
- package/core/infrastructure/legacy-installer-detector/index.ts +0 -171
- package/core/infrastructure/legacy-installer-detector/migration.ts +0 -87
- package/core/infrastructure/legacy-installer-detector/types.ts +0 -42
- package/core/infrastructure/legacy-installer-detector.ts +0 -7
- package/core/infrastructure/migrator/file-operations.ts +0 -125
- package/core/infrastructure/migrator/index.ts +0 -288
- package/core/infrastructure/migrator/project-scanner.ts +0 -90
- package/core/infrastructure/migrator/reports.ts +0 -117
- package/core/infrastructure/migrator/types.ts +0 -124
- package/core/infrastructure/migrator/validation.ts +0 -94
- package/core/infrastructure/migrator/version-migration.ts +0 -117
- package/core/infrastructure/migrator.ts +0 -10
- package/core/infrastructure/uuid-migration.ts +0 -750
- package/templates/commands/migrate-all.md +0 -96
- package/templates/commands/migrate.md +0 -140
package/core/commands/base.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Base class and helpers for PrjctCommands
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import path from 'path'
|
|
6
|
-
|
|
7
5
|
import commandExecutor from '../agentic/command-executor'
|
|
8
6
|
import contextBuilder from '../agentic/context-builder'
|
|
9
7
|
import toolRegistry from '../agentic/tool-registry'
|
|
@@ -12,7 +10,6 @@ import pathManager from '../infrastructure/path-manager'
|
|
|
12
10
|
import configManager from '../infrastructure/config-manager'
|
|
13
11
|
import authorDetector from '../infrastructure/author-detector'
|
|
14
12
|
import agentDetector from '../infrastructure/agent-detector'
|
|
15
|
-
import migrator from '../infrastructure/migrator'
|
|
16
13
|
import UpdateChecker from '../infrastructure/update-checker'
|
|
17
14
|
import dateHelper from '../utils/date-helper'
|
|
18
15
|
import jsonlHelper from '../utils/jsonl-helper'
|
|
@@ -24,10 +21,23 @@ import type {
|
|
|
24
21
|
AgentInfo,
|
|
25
22
|
Author,
|
|
26
23
|
AgentAssignmentResult,
|
|
27
|
-
ComplexityResult,
|
|
28
|
-
HealthResult,
|
|
29
24
|
Context
|
|
30
25
|
} from './types'
|
|
26
|
+
import { ProjectError, AgentError } from '../errors'
|
|
27
|
+
|
|
28
|
+
// Valid agent types - whitelist for security (prevents path traversal)
|
|
29
|
+
const VALID_AGENT_TYPES = ['claude'] as const
|
|
30
|
+
type ValidAgentType = typeof VALID_AGENT_TYPES[number]
|
|
31
|
+
|
|
32
|
+
// Lazy-loaded to avoid circular dependencies
|
|
33
|
+
let _planningCommands: import('./planning').PlanningCommands | null = null
|
|
34
|
+
async function getPlanningCommands(): Promise<import('./planning').PlanningCommands> {
|
|
35
|
+
if (!_planningCommands) {
|
|
36
|
+
const { PlanningCommands } = await import('./planning')
|
|
37
|
+
_planningCommands = new PlanningCommands()
|
|
38
|
+
}
|
|
39
|
+
return _planningCommands
|
|
40
|
+
}
|
|
31
41
|
|
|
32
42
|
/**
|
|
33
43
|
* Base class with shared state and utilities
|
|
@@ -62,10 +72,16 @@ export class PrjctCommandsBase {
|
|
|
62
72
|
this.agentInfo = await agentDetector.detect()
|
|
63
73
|
|
|
64
74
|
if (!this.agentInfo.isSupported) {
|
|
65
|
-
throw
|
|
75
|
+
throw AgentError.notSupported(this.agentInfo.type)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Security: validate agent type against whitelist to prevent path traversal
|
|
79
|
+
const agentType = this.agentInfo.type as ValidAgentType
|
|
80
|
+
if (!VALID_AGENT_TYPES.includes(agentType)) {
|
|
81
|
+
throw AgentError.notSupported(this.agentInfo.type)
|
|
66
82
|
}
|
|
67
83
|
|
|
68
|
-
const { default: Agent } = await import(`../infrastructure/agents/${
|
|
84
|
+
const { default: Agent } = await import(`../infrastructure/agents/${agentType}-agent`)
|
|
69
85
|
this.agent = new Agent()
|
|
70
86
|
|
|
71
87
|
return this.agent
|
|
@@ -80,8 +96,8 @@ export class PrjctCommandsBase {
|
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
out.spin('initializing project...')
|
|
83
|
-
|
|
84
|
-
const initResult = await
|
|
99
|
+
const planning = await getPlanningCommands()
|
|
100
|
+
const initResult = await planning.init(null, projectPath)
|
|
85
101
|
if (!initResult.success) {
|
|
86
102
|
return initResult
|
|
87
103
|
}
|
|
@@ -107,13 +123,12 @@ export class PrjctCommandsBase {
|
|
|
107
123
|
* Get global project path
|
|
108
124
|
*/
|
|
109
125
|
async getGlobalProjectPath(projectPath: string): Promise<string> {
|
|
110
|
-
if (await migrator.needsMigration(projectPath)) {
|
|
111
|
-
throw new Error('Project needs migration. Run /p:migrate first.')
|
|
112
|
-
}
|
|
113
|
-
|
|
114
126
|
const projectId = await configManager.getProjectId(projectPath)
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
if (!projectId) {
|
|
128
|
+
throw ProjectError.notInitialized()
|
|
129
|
+
}
|
|
130
|
+
await pathManager.ensureProjectStructure(projectId)
|
|
131
|
+
return pathManager.getGlobalProjectPath(projectId)
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
/**
|
|
@@ -174,64 +189,6 @@ export class PrjctCommandsBase {
|
|
|
174
189
|
}
|
|
175
190
|
}
|
|
176
191
|
|
|
177
|
-
/**
|
|
178
|
-
* Assign agent for a task
|
|
179
|
-
*/
|
|
180
|
-
async _assignAgentForTask(taskDescription: string, projectPath: string, _context: Context): Promise<AgentAssignmentResult> {
|
|
181
|
-
try {
|
|
182
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
183
|
-
const agentsPath = pathManager.getFilePath(projectId!, 'agents', '')
|
|
184
|
-
const agentFiles = await fileHelper.listFiles(agentsPath, { extension: '.md' })
|
|
185
|
-
const agents = agentFiles.map(f => f.replace('.md', ''))
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
agent: { name: agents[0] || 'generalist', domain: 'auto' },
|
|
189
|
-
routing: {
|
|
190
|
-
confidence: 0.8,
|
|
191
|
-
reason: 'Claude assigns via templates/agent-assignment.md',
|
|
192
|
-
availableAgents: agents
|
|
193
|
-
},
|
|
194
|
-
_agenticNote: 'Use templates/agent-assignment.md for actual assignment'
|
|
195
|
-
}
|
|
196
|
-
} catch {
|
|
197
|
-
return {
|
|
198
|
-
agent: { name: 'generalist', domain: 'general' },
|
|
199
|
-
routing: { confidence: 0.5, reason: 'Fallback - no agents found' }
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Detect task complexity
|
|
206
|
-
*/
|
|
207
|
-
_detectComplexity(_task: string): ComplexityResult {
|
|
208
|
-
return { level: 'medium', hours: 4, type: 'feature' }
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Calculate project health score
|
|
213
|
-
*/
|
|
214
|
-
_calculateHealth(stats: { activeTask: boolean; featuresShipped: number }): HealthResult {
|
|
215
|
-
const hasActivity = stats.activeTask || stats.featuresShipped > 0
|
|
216
|
-
return {
|
|
217
|
-
score: hasActivity ? 70 : 50,
|
|
218
|
-
message: hasActivity ? '🟢 Active' : '🟡 Ready to start',
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Render ASCII progress bar
|
|
224
|
-
*/
|
|
225
|
-
_renderProgressBar(label: string, value: number, max: number): void {
|
|
226
|
-
const percentage = Math.min(100, Math.round((value / max) * 100))
|
|
227
|
-
const barLength = 30
|
|
228
|
-
const filled = Math.round((percentage / 100) * barLength)
|
|
229
|
-
const empty = barLength - filled
|
|
230
|
-
|
|
231
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty)
|
|
232
|
-
console.log(` ${label}: [${bar}] ${percentage}%`)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
192
|
/**
|
|
236
193
|
* Breakdown feature into tasks
|
|
237
194
|
*/
|
|
@@ -252,11 +209,73 @@ export class PrjctCommandsBase {
|
|
|
252
209
|
}
|
|
253
210
|
|
|
254
211
|
/**
|
|
255
|
-
*
|
|
212
|
+
* Assign agent for a task using AgentRouter
|
|
213
|
+
* Returns agent info for Claude to delegate work
|
|
256
214
|
*/
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
215
|
+
async _assignAgentForTask(
|
|
216
|
+
task: string,
|
|
217
|
+
projectPath: string,
|
|
218
|
+
_context: Context
|
|
219
|
+
): Promise<AgentAssignmentResult> {
|
|
220
|
+
try {
|
|
221
|
+
await this.agentRouter.initialize(projectPath)
|
|
222
|
+
const agents = await this.agentRouter.getAgentNames()
|
|
223
|
+
|
|
224
|
+
if (agents.length === 0) {
|
|
225
|
+
return {
|
|
226
|
+
agent: { name: 'generalist' },
|
|
227
|
+
routing: {
|
|
228
|
+
confidence: 1.0,
|
|
229
|
+
reason: 'No specialized agents available',
|
|
230
|
+
availableAgents: [],
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Simple keyword matching for agent assignment
|
|
236
|
+
// Claude will make the final decision via templates
|
|
237
|
+
const taskLower = task.toLowerCase()
|
|
238
|
+
let bestMatch = 'generalist'
|
|
239
|
+
|
|
240
|
+
for (const agentName of agents) {
|
|
241
|
+
const nameLower = agentName.toLowerCase()
|
|
242
|
+
if (taskLower.includes(nameLower) || nameLower.includes('general')) {
|
|
243
|
+
bestMatch = agentName
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
// Common domain keywords
|
|
247
|
+
if ((nameLower.includes('fe') || nameLower.includes('frontend')) &&
|
|
248
|
+
(taskLower.includes('ui') || taskLower.includes('component') || taskLower.includes('react'))) {
|
|
249
|
+
bestMatch = agentName
|
|
250
|
+
break
|
|
251
|
+
}
|
|
252
|
+
if ((nameLower.includes('be') || nameLower.includes('backend')) &&
|
|
253
|
+
(taskLower.includes('api') || taskLower.includes('server') || taskLower.includes('database'))) {
|
|
254
|
+
bestMatch = agentName
|
|
255
|
+
break
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await this.agentRouter.logUsage(task, bestMatch, projectPath)
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
agent: { name: bestMatch },
|
|
263
|
+
routing: {
|
|
264
|
+
confidence: 0.7,
|
|
265
|
+
reason: 'Keyword-based agent matching',
|
|
266
|
+
availableAgents: agents,
|
|
267
|
+
},
|
|
268
|
+
_agenticNote: 'Claude should verify this assignment using agent context',
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
return {
|
|
272
|
+
agent: { name: 'generalist' },
|
|
273
|
+
routing: {
|
|
274
|
+
confidence: 1.0,
|
|
275
|
+
reason: 'Agent routing unavailable',
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
}
|
|
260
279
|
}
|
|
261
280
|
}
|
|
262
281
|
|
|
@@ -22,6 +22,16 @@ import { queueStorage, ideasStorage } from '../storage'
|
|
|
22
22
|
import authorDetector from '../infrastructure/author-detector'
|
|
23
23
|
import commandInstaller from '../infrastructure/command-installer'
|
|
24
24
|
|
|
25
|
+
// Lazy-loaded to avoid circular dependencies
|
|
26
|
+
let _analysisCommands: import('./analysis').AnalysisCommands | null = null
|
|
27
|
+
async function getAnalysisCommands(): Promise<import('./analysis').AnalysisCommands> {
|
|
28
|
+
if (!_analysisCommands) {
|
|
29
|
+
const { AnalysisCommands } = await import('./analysis')
|
|
30
|
+
_analysisCommands = new AnalysisCommands()
|
|
31
|
+
}
|
|
32
|
+
return _analysisCommands
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export class PlanningCommands extends PrjctCommandsBase {
|
|
26
36
|
/**
|
|
27
37
|
* /p:init - Initialize prjct project
|
|
@@ -82,11 +92,12 @@ export class PlanningCommands extends PrjctCommandsBase {
|
|
|
82
92
|
|
|
83
93
|
if (hasCode || !isEmpty) {
|
|
84
94
|
out.spin('analyzing project...')
|
|
85
|
-
const
|
|
95
|
+
const analysis = await getAnalysisCommands()
|
|
96
|
+
const analysisResult = await analysis.analyze({}, projectPath)
|
|
86
97
|
|
|
87
98
|
if (analysisResult.success) {
|
|
88
99
|
out.spin('generating agents...')
|
|
89
|
-
await
|
|
100
|
+
await analysis.sync(projectPath)
|
|
90
101
|
out.done('initialized')
|
|
91
102
|
return { success: true, mode: 'existing', projectId }
|
|
92
103
|
}
|
package/core/commands/setup.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Setup Commands: start, setup,
|
|
2
|
+
* Setup Commands: start, setup, installStatusLine, showAsciiArt
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import path from 'path'
|
|
6
6
|
import fs from 'fs'
|
|
7
|
-
import { promises as fsPromises } from 'fs'
|
|
8
7
|
import os from 'os'
|
|
9
8
|
import chalk from 'chalk'
|
|
10
9
|
|
|
11
|
-
import migrator from '../infrastructure/migrator'
|
|
12
10
|
import commandInstaller from '../infrastructure/command-installer'
|
|
13
|
-
import type { CommandResult, SetupOptions
|
|
14
|
-
import {
|
|
15
|
-
PrjctCommandsBase,
|
|
16
|
-
configManager,
|
|
17
|
-
dateHelper
|
|
18
|
-
} from './base'
|
|
11
|
+
import type { CommandResult, SetupOptions } from './types'
|
|
12
|
+
import { PrjctCommandsBase } from './base'
|
|
19
13
|
import { VERSION } from '../utils/version'
|
|
20
14
|
|
|
21
15
|
export class SetupCommands extends PrjctCommandsBase {
|
|
@@ -225,80 +219,4 @@ fi
|
|
|
225
219
|
console.log('')
|
|
226
220
|
}
|
|
227
221
|
|
|
228
|
-
/**
|
|
229
|
-
* Migrate all legacy projects
|
|
230
|
-
*/
|
|
231
|
-
async migrateAll(options: MigrateOptions = {}): Promise<CommandResult> {
|
|
232
|
-
console.log('🔄 Scanning for legacy prjct projects...\n')
|
|
233
|
-
|
|
234
|
-
const homeDir = os.homedir()
|
|
235
|
-
const globalRoot = path.join(homeDir, '.prjct-cli', 'projects')
|
|
236
|
-
|
|
237
|
-
let projectIds: string[] = []
|
|
238
|
-
try {
|
|
239
|
-
const dirs = await fsPromises.readdir(globalRoot)
|
|
240
|
-
projectIds = dirs.filter((d: string) => !d.startsWith('.'))
|
|
241
|
-
} catch {
|
|
242
|
-
return {
|
|
243
|
-
success: false,
|
|
244
|
-
message: '❌ No prjct projects found',
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
console.log(`📁 Found ${projectIds.length} projects in global storage\n`)
|
|
249
|
-
|
|
250
|
-
const migrated: { projectId: string; path: string }[] = []
|
|
251
|
-
const failed: { projectId: string; path: string; error: string }[] = []
|
|
252
|
-
const skipped: { projectId: string; reason: string }[] = []
|
|
253
|
-
|
|
254
|
-
for (const projectId of projectIds) {
|
|
255
|
-
const globalConfig = await configManager.readGlobalConfig(projectId) as GlobalConfig | null
|
|
256
|
-
if (!globalConfig || !globalConfig.projectPath) {
|
|
257
|
-
skipped.push({ projectId, reason: 'No project path in config' })
|
|
258
|
-
continue
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const projectPath = globalConfig.projectPath!
|
|
262
|
-
|
|
263
|
-
if (!(await migrator.needsMigration(projectPath))) {
|
|
264
|
-
skipped.push({ projectId, reason: 'Already migrated' })
|
|
265
|
-
continue
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
console.log(`🔄 Migrating: ${projectPath}`)
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
const result = await migrator.migrate(projectPath, options) as MigrationResult
|
|
272
|
-
|
|
273
|
-
if (result.success) {
|
|
274
|
-
migrated.push({ projectId, path: projectPath })
|
|
275
|
-
console.log(` ✅ Migrated successfully`)
|
|
276
|
-
} else {
|
|
277
|
-
const issues = result.issues?.join(', ') || 'Unknown error'
|
|
278
|
-
failed.push({ projectId, path: projectPath, error: issues })
|
|
279
|
-
console.log(` ❌ ${issues}`)
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
failed.push({ projectId, path: projectPath, error: (error as Error).message })
|
|
283
|
-
console.log(` ❌ ${(error as Error).message}`)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
console.log('')
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
console.log('\n📊 Migration Summary:')
|
|
290
|
-
console.log(` ✅ Migrated: ${migrated.length}`)
|
|
291
|
-
console.log(` ⏭️ Skipped: ${skipped.length}`)
|
|
292
|
-
console.log(` ❌ Failed: ${failed.length}`)
|
|
293
|
-
|
|
294
|
-
if (failed.length > 0) {
|
|
295
|
-
console.log('\n❌ Failed migrations:')
|
|
296
|
-
failed.forEach((f) => console.log(` - ${f.path}: ${f.error}`))
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return {
|
|
300
|
-
success: failed.length === 0,
|
|
301
|
-
message: '',
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
222
|
}
|
package/core/errors.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Error Hierarchy for prjct-cli
|
|
3
|
+
*
|
|
4
|
+
* Base error class with specific subclasses for different error domains.
|
|
5
|
+
* Enables typed error handling and better error messages.
|
|
6
|
+
*
|
|
7
|
+
* @module core/errors
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base error class for all prjct errors
|
|
13
|
+
*/
|
|
14
|
+
export class PrjctError extends Error {
|
|
15
|
+
readonly code: string
|
|
16
|
+
readonly isOperational: boolean
|
|
17
|
+
|
|
18
|
+
constructor(message: string, code = 'PRJCT_ERROR') {
|
|
19
|
+
super(message)
|
|
20
|
+
this.name = 'PrjctError'
|
|
21
|
+
this.code = code
|
|
22
|
+
this.isOperational = true // Distinguishes from programming errors
|
|
23
|
+
Error.captureStackTrace?.(this, this.constructor)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Configuration-related errors
|
|
29
|
+
*/
|
|
30
|
+
export class ConfigError extends PrjctError {
|
|
31
|
+
constructor(message: string, code = 'CONFIG_ERROR') {
|
|
32
|
+
super(message, code)
|
|
33
|
+
this.name = 'ConfigError'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static notFound(path: string): ConfigError {
|
|
37
|
+
return new ConfigError(`Configuration not found: ${path}`, 'CONFIG_NOT_FOUND')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static invalid(reason: string): ConfigError {
|
|
41
|
+
return new ConfigError(`Invalid configuration: ${reason}`, 'CONFIG_INVALID')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static parseError(path: string): ConfigError {
|
|
45
|
+
return new ConfigError(`Failed to parse configuration: ${path}`, 'CONFIG_PARSE_ERROR')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Storage and file system errors
|
|
51
|
+
*/
|
|
52
|
+
export class StorageError extends PrjctError {
|
|
53
|
+
constructor(message: string, code = 'STORAGE_ERROR') {
|
|
54
|
+
super(message, code)
|
|
55
|
+
this.name = 'StorageError'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static readFailed(path: string): StorageError {
|
|
59
|
+
return new StorageError(`Failed to read: ${path}`, 'STORAGE_READ_FAILED')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static writeFailed(path: string): StorageError {
|
|
63
|
+
return new StorageError(`Failed to write: ${path}`, 'STORAGE_WRITE_FAILED')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static notFound(path: string): StorageError {
|
|
67
|
+
return new StorageError(`File not found: ${path}`, 'STORAGE_NOT_FOUND')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Project-related errors
|
|
73
|
+
*/
|
|
74
|
+
export class ProjectError extends PrjctError {
|
|
75
|
+
constructor(message: string, code = 'PROJECT_ERROR') {
|
|
76
|
+
super(message, code)
|
|
77
|
+
this.name = 'ProjectError'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static notInitialized(): ProjectError {
|
|
81
|
+
return new ProjectError('Project not initialized. Run /p:init first.', 'PROJECT_NOT_INIT')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static notFound(projectId: string): ProjectError {
|
|
85
|
+
return new ProjectError(`Project not found: ${projectId}`, 'PROJECT_NOT_FOUND')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static invalidId(projectId: string): ProjectError {
|
|
89
|
+
return new ProjectError(`Invalid project ID: ${projectId}`, 'PROJECT_INVALID_ID')
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Command execution errors
|
|
95
|
+
*/
|
|
96
|
+
export class CommandError extends PrjctError {
|
|
97
|
+
constructor(message: string, code = 'COMMAND_ERROR') {
|
|
98
|
+
super(message, code)
|
|
99
|
+
this.name = 'CommandError'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static notFound(commandName: string): CommandError {
|
|
103
|
+
return new CommandError(`Command not found: ${commandName}`, 'COMMAND_NOT_FOUND')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static invalidParams(reason: string): CommandError {
|
|
107
|
+
return new CommandError(`Invalid parameters: ${reason}`, 'COMMAND_INVALID_PARAMS')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static executionFailed(commandName: string, reason: string): CommandError {
|
|
111
|
+
return new CommandError(`Command '${commandName}' failed: ${reason}`, 'COMMAND_EXEC_FAILED')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Template-related errors
|
|
117
|
+
*/
|
|
118
|
+
export class TemplateError extends PrjctError {
|
|
119
|
+
constructor(message: string, code = 'TEMPLATE_ERROR') {
|
|
120
|
+
super(message, code)
|
|
121
|
+
this.name = 'TemplateError'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static notFound(templateName: string): TemplateError {
|
|
125
|
+
return new TemplateError(`Template not found: ${templateName}.md`, 'TEMPLATE_NOT_FOUND')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static parseFailed(templateName: string): TemplateError {
|
|
129
|
+
return new TemplateError(`Failed to parse template: ${templateName}`, 'TEMPLATE_PARSE_ERROR')
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Agent-related errors
|
|
135
|
+
*/
|
|
136
|
+
export class AgentError extends PrjctError {
|
|
137
|
+
constructor(message: string, code = 'AGENT_ERROR') {
|
|
138
|
+
super(message, code)
|
|
139
|
+
this.name = 'AgentError'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static notSupported(agentType: string): AgentError {
|
|
143
|
+
return new AgentError(`Unsupported agent type: ${agentType}`, 'AGENT_NOT_SUPPORTED')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static initFailed(reason: string): AgentError {
|
|
147
|
+
return new AgentError(`Agent initialization failed: ${reason}`, 'AGENT_INIT_FAILED')
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Type guard to check if error is a PrjctError
|
|
153
|
+
*/
|
|
154
|
+
export function isPrjctError(error: unknown): error is PrjctError {
|
|
155
|
+
return error instanceof PrjctError
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Type guard for specific error types
|
|
160
|
+
*/
|
|
161
|
+
export function isConfigError(error: unknown): error is ConfigError {
|
|
162
|
+
return error instanceof ConfigError
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isStorageError(error: unknown): error is StorageError {
|
|
166
|
+
return error instanceof StorageError
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isProjectError(error: unknown): error is ProjectError {
|
|
170
|
+
return error instanceof ProjectError
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function isCommandError(error: unknown): error is CommandError {
|
|
174
|
+
return error instanceof CommandError
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function isTemplateError(error: unknown): error is TemplateError {
|
|
178
|
+
return error instanceof TemplateError
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function isAgentError(error: unknown): error is AgentError {
|
|
182
|
+
return error instanceof AgentError
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract error message safely from unknown error
|
|
187
|
+
*/
|
|
188
|
+
export function getErrorMessage(error: unknown): string {
|
|
189
|
+
if (isPrjctError(error)) {
|
|
190
|
+
return error.message
|
|
191
|
+
}
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
return error.message
|
|
194
|
+
}
|
|
195
|
+
if (typeof error === 'string') {
|
|
196
|
+
return error
|
|
197
|
+
}
|
|
198
|
+
return 'Unknown error'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Extract error code safely from unknown error
|
|
203
|
+
*/
|
|
204
|
+
export function getErrorCode(error: unknown): string {
|
|
205
|
+
if (isPrjctError(error)) {
|
|
206
|
+
return error.code
|
|
207
|
+
}
|
|
208
|
+
return 'UNKNOWN_ERROR'
|
|
209
|
+
}
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
import fs from 'fs/promises'
|
|
14
14
|
import path from 'path'
|
|
15
15
|
import pathManager from './path-manager'
|
|
16
|
+
import authorDetector from './author-detector'
|
|
16
17
|
import { VERSION } from '../utils/version'
|
|
18
|
+
import { ConfigError, getErrorMessage } from '../errors'
|
|
17
19
|
|
|
18
20
|
interface Author {
|
|
19
21
|
name: string
|
|
@@ -50,7 +52,13 @@ class ConfigManager {
|
|
|
50
52
|
const configPath = pathManager.getLocalConfigPath(projectPath)
|
|
51
53
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
52
54
|
return JSON.parse(content)
|
|
53
|
-
} catch {
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// File not found is expected - return null
|
|
57
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
// JSON parse errors or other issues - log and return null
|
|
61
|
+
console.warn(`Warning: Could not read config at ${projectPath}: ${getErrorMessage(error)}`)
|
|
54
62
|
return null
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -77,7 +85,13 @@ class ConfigManager {
|
|
|
77
85
|
const configPath = pathManager.getGlobalProjectConfigPath(projectId)
|
|
78
86
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
79
87
|
return JSON.parse(content)
|
|
80
|
-
} catch {
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// File not found is expected for new projects
|
|
90
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
// Log other errors for debugging
|
|
94
|
+
console.warn(`Warning: Could not read global config for ${projectId}: ${getErrorMessage(error)}`)
|
|
81
95
|
return null
|
|
82
96
|
}
|
|
83
97
|
}
|
|
@@ -202,7 +216,12 @@ class ConfigManager {
|
|
|
202
216
|
try {
|
|
203
217
|
const coreFiles = await fs.readdir(path.join(globalPath, 'core'))
|
|
204
218
|
return coreFiles.length === 0
|
|
205
|
-
} catch {
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// Directory not found means migration needed
|
|
221
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
// Permission errors or other issues - assume migration needed
|
|
206
225
|
return true
|
|
207
226
|
}
|
|
208
227
|
}
|
|
@@ -275,8 +294,6 @@ class ConfigManager {
|
|
|
275
294
|
* Get current author for session (detect or get from global config)
|
|
276
295
|
*/
|
|
277
296
|
async getCurrentAuthor(projectPath: string): Promise<string> {
|
|
278
|
-
// Dynamic import to avoid circular dependency
|
|
279
|
-
const authorDetector = (await import('./author-detector')).default
|
|
280
297
|
const author = await authorDetector.detect()
|
|
281
298
|
|
|
282
299
|
const projectId = await this.getProjectId(projectPath)
|