prjct-cli 0.15.0 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/bin/dev.js +0 -1
  3. package/bin/serve.js +19 -20
  4. package/core/agentic/agent-router.ts +79 -14
  5. package/core/agentic/command-executor/command-executor.ts +2 -74
  6. package/core/agentic/services.ts +0 -48
  7. package/core/agentic/template-loader.ts +35 -1
  8. package/core/commands/base.ts +96 -77
  9. package/core/commands/planning.ts +13 -2
  10. package/core/commands/setup.ts +3 -85
  11. package/core/errors.ts +209 -0
  12. package/core/infrastructure/config-manager.ts +22 -5
  13. package/core/infrastructure/setup.ts +5 -50
  14. package/core/storage/storage-manager.ts +42 -6
  15. package/core/utils/logger.ts +19 -12
  16. package/package.json +2 -4
  17. package/templates/agentic/subagent-generation.md +109 -0
  18. package/templates/commands/setup.md +18 -3
  19. package/templates/commands/sync.md +74 -13
  20. package/templates/mcp-config.json +20 -1
  21. package/templates/subagents/domain/backend.md +105 -0
  22. package/templates/subagents/domain/database.md +118 -0
  23. package/templates/subagents/domain/devops.md +148 -0
  24. package/templates/subagents/domain/frontend.md +99 -0
  25. package/templates/subagents/domain/testing.md +169 -0
  26. package/templates/subagents/workflow/prjct-planner.md +158 -0
  27. package/templates/subagents/workflow/prjct-shipper.md +179 -0
  28. package/templates/subagents/workflow/prjct-workflow.md +98 -0
  29. package/bin/generate-views.js +0 -209
  30. package/bin/migrate-to-json.js +0 -742
  31. package/core/agentic/context-filter.ts +0 -365
  32. package/core/agentic/parallel-tools.ts +0 -165
  33. package/core/agentic/response-templates.ts +0 -164
  34. package/core/agentic/semantic-compression.ts +0 -273
  35. package/core/agentic/think-blocks.ts +0 -202
  36. package/core/agentic/validation-rules.ts +0 -313
  37. package/core/domain/agent-matcher.ts +0 -130
  38. package/core/domain/agent-validator.ts +0 -250
  39. package/core/domain/architect-session.ts +0 -315
  40. package/core/domain/product-standards.ts +0 -106
  41. package/core/domain/smart-cache.ts +0 -167
  42. package/core/domain/task-analyzer.ts +0 -296
  43. package/core/infrastructure/legacy-installer-detector/cleanup.ts +0 -216
  44. package/core/infrastructure/legacy-installer-detector/detection.ts +0 -95
  45. package/core/infrastructure/legacy-installer-detector/index.ts +0 -171
  46. package/core/infrastructure/legacy-installer-detector/migration.ts +0 -87
  47. package/core/infrastructure/legacy-installer-detector/types.ts +0 -42
  48. package/core/infrastructure/legacy-installer-detector.ts +0 -7
  49. package/core/infrastructure/migrator/file-operations.ts +0 -125
  50. package/core/infrastructure/migrator/index.ts +0 -288
  51. package/core/infrastructure/migrator/project-scanner.ts +0 -90
  52. package/core/infrastructure/migrator/reports.ts +0 -117
  53. package/core/infrastructure/migrator/types.ts +0 -124
  54. package/core/infrastructure/migrator/validation.ts +0 -94
  55. package/core/infrastructure/migrator/version-migration.ts +0 -117
  56. package/core/infrastructure/migrator.ts +0 -10
  57. package/core/infrastructure/uuid-migration.ts +0 -750
  58. package/templates/commands/migrate-all.md +0 -96
  59. package/templates/commands/migrate.md +0 -140
@@ -1,296 +0,0 @@
1
- /**
2
- * TaskAnalyzer - Deep Semantic Task Analysis
3
- *
4
- * Analyzes tasks semantically, not just keywords
5
- * Considers project context, history, and relationships
6
- *
7
- * @version 1.0.0
8
- */
9
-
10
- import fs from 'fs/promises'
11
- import path from 'path'
12
- import analyzer from './analyzer'
13
- import configManager from '../infrastructure/config-manager'
14
- import pathManager from '../infrastructure/path-manager'
15
-
16
- interface Task {
17
- description?: string
18
- type?: string
19
- }
20
-
21
- interface HistoryEntry {
22
- type: string
23
- domain?: string
24
- description?: string
25
- }
26
-
27
- interface SemanticAnalysis {
28
- intent: string | null
29
- requiresMultipleAgents: boolean
30
- text: string
31
- }
32
-
33
- interface HistoricalAnalysis {
34
- confidence: number
35
- patterns: Array<{ domain: string; description: string }>
36
- suggestedDomain?: string | null
37
- }
38
-
39
- interface ProjectData {
40
- packageJson: unknown
41
- extensions: Record<string, number>
42
- directories: string[]
43
- configFiles: string[]
44
- }
45
-
46
- interface TaskAnalysisResult {
47
- primaryDomain: string
48
- confidence: number
49
- semantic: SemanticAnalysis
50
- historical: HistoricalAnalysis
51
- complexity: string
52
- projectData: ProjectData
53
- matchedKeywords: string[]
54
- reason: string
55
- alternatives: string[]
56
- }
57
-
58
- class TaskAnalyzer {
59
- projectPath: string
60
- projectId: string | null = null
61
- taskHistory: HistoryEntry[] | null = null
62
-
63
- constructor(projectPath: string) {
64
- this.projectPath = projectPath
65
- }
66
-
67
- /**
68
- * Initialize analyzer with project context
69
- */
70
- async initialize(): Promise<void> {
71
- this.projectId = await configManager.getProjectId(this.projectPath)
72
- analyzer.init(this.projectPath)
73
- await this.loadTaskHistory()
74
- }
75
-
76
- /**
77
- * Deep semantic analysis of a task
78
- *
79
- * 100% AGENTIC: No hardcoded patterns. Uses task description
80
- * and historical patterns only. Claude decides domain relevance.
81
- */
82
- async analyzeTask(task: Task): Promise<TaskAnalysisResult> {
83
- if (!this.projectId) {
84
- await this.initialize()
85
- }
86
-
87
- const description = (task.description || '').toLowerCase()
88
- const type = (task.type || '').toLowerCase()
89
- const fullText = `${description} ${type}`.trim()
90
-
91
- // Get raw project data (no categorization)
92
- const projectData: ProjectData = {
93
- packageJson: await analyzer.readPackageJson(),
94
- extensions: await analyzer.getFileExtensions(),
95
- directories: await analyzer.listDirectories(),
96
- configFiles: await analyzer.listConfigFiles(),
97
- }
98
-
99
- // Semantic understanding (intent-based, not keyword-based)
100
- const semantic = this.analyzeSemantics(fullText)
101
-
102
- // Historical patterns
103
- const historical = await this.analyzeHistory(fullText)
104
-
105
- // Complexity estimation
106
- const complexity = this.estimateComplexity(fullText)
107
-
108
- // Primary domain from history and intent (not hardcoded patterns)
109
- const primaryDomain = this.selectPrimaryDomain(semantic, historical)
110
- const confidence = this.calculateConfidence(semantic, historical)
111
-
112
- return {
113
- primaryDomain,
114
- confidence,
115
- semantic,
116
- historical,
117
- complexity,
118
- projectData, // Raw data for Claude to analyze
119
- matchedKeywords: [], // No keyword matching - Claude decides
120
- reason: this.buildReason(primaryDomain, semantic, historical),
121
- alternatives: ['full-stack', 'generalist'],
122
- }
123
- }
124
-
125
- /**
126
- * Domain detection removed - 100% AGENTIC
127
- *
128
- * NO hardcoded keyword lists or framework categorization.
129
- * Claude analyzes the task description and project context
130
- * to determine the appropriate domain.
131
- *
132
- * This method is kept for backward compatibility but returns empty.
133
- * Use analyzeTask() which provides raw data for Claude.
134
- */
135
- detectDomains(text: string): Record<string, unknown> {
136
- // No hardcoded patterns - Claude decides domain
137
- return {}
138
- }
139
-
140
- /**
141
- * Semantic analysis - understand intent
142
- * AGENTIC: Claude uses templates/analysis/intent.md for detailed analysis
143
- * This returns minimal structure - Claude determines actual intent
144
- */
145
- analyzeSemantics(text: string): SemanticAnalysis {
146
- // AGENTIC: Return structure only, Claude analyzes via template
147
- return {
148
- intent: null, // Claude determines via templates/analysis/intent.md
149
- requiresMultipleAgents: false, // Claude decides based on context
150
- text: text, // Pass text for Claude to analyze
151
- }
152
- }
153
-
154
- /**
155
- * Analyze historical patterns
156
- */
157
- async analyzeHistory(text: string): Promise<HistoricalAnalysis> {
158
- if (!this.taskHistory) {
159
- return { confidence: 0, patterns: [] }
160
- }
161
-
162
- // Find similar tasks in history
163
- const similar = this.taskHistory.filter((task) => {
164
- const similarity = this.calculateTextSimilarity(text, task.description || '')
165
- return similarity > 0.5
166
- })
167
-
168
- if (similar.length === 0) {
169
- return { confidence: 0, patterns: [] }
170
- }
171
-
172
- // Find most common domain for similar tasks
173
- const domainCounts: Record<string, number> = {}
174
- similar.forEach((task) => {
175
- if (task.domain) {
176
- domainCounts[task.domain] = (domainCounts[task.domain] || 0) + 1
177
- }
178
- })
179
-
180
- const mostCommon = Object.entries(domainCounts).sort((a, b) => b[1] - a[1])[0]
181
-
182
- return {
183
- confidence: mostCommon ? mostCommon[1] / similar.length : 0,
184
- patterns: similar.map((t) => ({ domain: t.domain || '', description: t.description || '' })),
185
- suggestedDomain: mostCommon ? mostCommon[0] : null,
186
- }
187
- }
188
-
189
- /**
190
- * Estimate task complexity
191
- * AGENTIC: Claude uses templates/analysis/complexity.md for real estimation
192
- * This returns default - Claude determines actual complexity
193
- */
194
- estimateComplexity(text: string): string {
195
- // AGENTIC: Return default, Claude analyzes via templates/analysis/complexity.md
196
- return 'medium'
197
- }
198
-
199
- /**
200
- * Select primary domain from history and semantics
201
- *
202
- * 100% AGENTIC: No keyword matching. Uses only:
203
- * - Historical patterns from past tasks
204
- * - Basic intent detection
205
- * Claude decides actual domain based on project context.
206
- */
207
- selectPrimaryDomain(semantic: SemanticAnalysis, historical: HistoricalAnalysis): string {
208
- // Priority: historical > default
209
- if (historical && historical.suggestedDomain && historical.confidence > 0.7) {
210
- return historical.suggestedDomain
211
- }
212
-
213
- // Map intent to suggested domain (loose mapping, Claude refines)
214
- if (semantic && semantic.intent === 'test') {
215
- return 'qa'
216
- }
217
-
218
- // Default: generalist (Claude decides based on context)
219
- return 'generalist'
220
- }
221
-
222
- /**
223
- * Calculate confidence based on available signals
224
- */
225
- calculateConfidence(semantic: SemanticAnalysis, historical: HistoricalAnalysis): number {
226
- let confidence = 0.5 // Base confidence
227
-
228
- // Boost from historical patterns
229
- if (historical && historical.confidence > 0) {
230
- confidence += historical.confidence * 0.3
231
- }
232
-
233
- // Boost from semantic understanding
234
- if (semantic && semantic.intent) {
235
- confidence += 0.1
236
- }
237
-
238
- return Math.min(confidence, 1.0)
239
- }
240
-
241
- /**
242
- * Build human-readable reason
243
- */
244
- buildReason(primaryDomain: string, semantic: SemanticAnalysis, historical: HistoricalAnalysis): string {
245
- const parts: string[] = []
246
-
247
- if (historical && historical.suggestedDomain && historical.confidence > 0.7) {
248
- parts.push(`Historical: similar tasks used ${primaryDomain}`)
249
- }
250
-
251
- if (semantic && semantic.intent) {
252
- parts.push(`Intent: ${semantic.intent}`)
253
- }
254
-
255
- return parts.join(' | ') || 'Claude will analyze task in context'
256
- }
257
-
258
- /**
259
- * Load task history from memory
260
- */
261
- async loadTaskHistory(): Promise<void> {
262
- try {
263
- const memoryPath = pathManager.getFilePath(this.projectId!, 'memory', 'context.jsonl')
264
- const content = await fs.readFile(memoryPath, 'utf-8')
265
- const lines = content.split('\n').filter(Boolean)
266
-
267
- this.taskHistory = lines
268
- .map((line) => {
269
- try {
270
- return JSON.parse(line) as HistoryEntry
271
- } catch {
272
- return null
273
- }
274
- })
275
- .filter((entry): entry is HistoryEntry => entry !== null && entry.type === 'task_start' && !!entry.domain)
276
- .slice(-100) // Last 100 tasks
277
- } catch {
278
- this.taskHistory = []
279
- }
280
- }
281
-
282
- /**
283
- * Calculate text similarity (simple Jaccard)
284
- */
285
- calculateTextSimilarity(text1: string, text2: string): number {
286
- const words1 = new Set(text1.toLowerCase().split(/\s+/))
287
- const words2 = new Set(text2.toLowerCase().split(/\s+/))
288
-
289
- const intersection = new Set([...words1].filter((w) => words2.has(w)))
290
- const union = new Set([...words1, ...words2])
291
-
292
- return intersection.size / union.size
293
- }
294
- }
295
-
296
- export default TaskAnalyzer
@@ -1,216 +0,0 @@
1
- /**
2
- * Cleanup - Legacy Installation Cleanup
3
- */
4
-
5
- import fs from 'fs/promises'
6
- import path from 'path'
7
- import os from 'os'
8
- import { legacyInstallDir, isWindows } from './detection'
9
- import type { CleanupResult } from './types'
10
-
11
- /**
12
- * Get platform-specific shell config files
13
- */
14
- export function getShellConfigFiles(): string[] {
15
- if (isWindows) {
16
- const profilePaths: string[] = []
17
-
18
- if (process.env.USERPROFILE) {
19
- profilePaths.push(
20
- path.join(
21
- process.env.USERPROFILE,
22
- 'Documents',
23
- 'PowerShell',
24
- 'Microsoft.PowerShell_profile.ps1'
25
- ),
26
- path.join(
27
- process.env.USERPROFILE,
28
- 'Documents',
29
- 'WindowsPowerShell',
30
- 'Microsoft.PowerShell_profile.ps1'
31
- )
32
- )
33
- }
34
-
35
- return profilePaths
36
- } else {
37
- return [
38
- path.join(os.homedir(), '.zshrc'),
39
- path.join(os.homedir(), '.bashrc'),
40
- path.join(os.homedir(), '.profile'),
41
- path.join(os.homedir(), '.bash_profile'),
42
- ]
43
- }
44
- }
45
-
46
- /**
47
- * Remove legacy installation files (keep projects data)
48
- */
49
- export async function cleanupLegacyInstallation(): Promise<CleanupResult> {
50
- const result: CleanupResult = {
51
- success: false,
52
- message: '',
53
- }
54
-
55
- try {
56
- const dirsToRemove = [
57
- 'bin',
58
- 'core',
59
- 'templates',
60
- 'scripts',
61
- 'node_modules',
62
- '.git',
63
- '__tests__',
64
- 'website',
65
- 'docs',
66
- '.github',
67
- ]
68
- const filesToRemove = [
69
- 'package.json',
70
- 'package-lock.json',
71
- 'README.md',
72
- 'LICENSE',
73
- 'CHANGELOG.md',
74
- 'CLAUDE.md',
75
- 'CONTRIBUTING.md',
76
- 'MIGRATION.md',
77
- 'TESTING.md',
78
- '.gitignore',
79
- '.eslintrc.js',
80
- '.prettierrc',
81
- 'vitest.config.js',
82
- 'vitest.workspace.js',
83
- ]
84
-
85
- let removedItems = 0
86
-
87
- for (const dir of dirsToRemove) {
88
- const dirPath = path.join(legacyInstallDir, dir)
89
- try {
90
- await fs.rm(dirPath, { recursive: true, force: true })
91
- removedItems++
92
- } catch {
93
- // Directory doesn't exist
94
- }
95
- }
96
-
97
- for (const file of filesToRemove) {
98
- const filePath = path.join(legacyInstallDir, file)
99
- try {
100
- await fs.unlink(filePath)
101
- removedItems++
102
- } catch {
103
- // File doesn't exist
104
- }
105
- }
106
-
107
- result.success = true
108
- result.message = `Removed ${removedItems} legacy installation items`
109
- return result
110
- } catch (error) {
111
- result.message = `Cleanup failed: ${(error as Error).message}`
112
- return result
113
- }
114
- }
115
-
116
- /**
117
- * Clean up legacy PATH entries from shell config files
118
- */
119
- export async function cleanupLegacyPATH(): Promise<CleanupResult> {
120
- const result: CleanupResult = {
121
- success: false,
122
- message: '',
123
- filesModified: 0,
124
- }
125
-
126
- try {
127
- const shellConfigs = getShellConfigFiles()
128
-
129
- for (const configFile of shellConfigs) {
130
- try {
131
- const content = await fs.readFile(configFile, 'utf8')
132
-
133
- if (!content.includes('.prjct-cli/bin')) {
134
- continue
135
- }
136
-
137
- const lines = content.split('\n')
138
- const filteredLines = lines.filter((line) => {
139
- return !line.includes('.prjct-cli/bin') && !line.includes('# prjct/cli')
140
- })
141
-
142
- const cleanedLines: string[] = []
143
- for (let i = 0; i < filteredLines.length; i++) {
144
- const line = filteredLines[i]
145
- const prevLine = filteredLines[i - 1]
146
-
147
- if (line.trim() === '' && prevLine && prevLine.trim() === '') {
148
- continue
149
- }
150
-
151
- cleanedLines.push(line)
152
- }
153
-
154
- await fs.writeFile(configFile, cleanedLines.join('\n'), 'utf8')
155
- result.filesModified!++
156
- } catch {
157
- // File doesn't exist or can't read
158
- }
159
- }
160
-
161
- result.success = true
162
- result.message =
163
- result.filesModified! > 0
164
- ? `Cleaned PATH from ${result.filesModified} shell config(s)`
165
- : 'No legacy PATH entries found'
166
-
167
- return result
168
- } catch (error) {
169
- result.message = `PATH cleanup failed: ${(error as Error).message}`
170
- return result
171
- }
172
- }
173
-
174
- /**
175
- * Clean up legacy symlinks
176
- */
177
- export async function cleanupLegacySymlinks(): Promise<CleanupResult> {
178
- const result: CleanupResult = {
179
- success: false,
180
- message: '',
181
- }
182
-
183
- if (isWindows) {
184
- result.success = true
185
- result.message = 'No symlinks on Windows'
186
- return result
187
- }
188
-
189
- try {
190
- const symlinkPath = path.join(os.homedir(), '.local', 'bin', 'prjct')
191
-
192
- try {
193
- const stat = await fs.lstat(symlinkPath)
194
-
195
- if (stat.isSymbolicLink()) {
196
- const target = await fs.readlink(symlinkPath)
197
-
198
- if (target.includes('.prjct-cli')) {
199
- await fs.unlink(symlinkPath)
200
- result.success = true
201
- result.message = 'Removed legacy symlink'
202
- return result
203
- }
204
- }
205
- } catch {
206
- // Symlink doesn't exist
207
- }
208
-
209
- result.success = true
210
- result.message = 'No legacy symlinks found'
211
- return result
212
- } catch (error) {
213
- result.message = `Symlink cleanup failed: ${(error as Error).message}`
214
- return result
215
- }
216
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * Detection - Legacy Installation Detection
3
- */
4
-
5
- import fs from 'fs/promises'
6
- import path from 'path'
7
- import os from 'os'
8
- import { execSync } from 'child_process'
9
-
10
- export const legacyInstallDir = path.join(os.homedir(), '.prjct-cli')
11
- export const npmGlobalProjectsDir = path.join(os.homedir(), '.prjct-cli', 'projects')
12
- export const isWindows = process.platform === 'win32'
13
-
14
- /**
15
- * Check if legacy curl installation exists
16
- */
17
- export async function hasLegacyInstallation(): Promise<boolean> {
18
- try {
19
- const stat = await fs.stat(legacyInstallDir)
20
- if (!stat.isDirectory()) return false
21
-
22
- // Check for .git directory (indicates curl install)
23
- try {
24
- await fs.access(path.join(legacyInstallDir, '.git'))
25
- return true
26
- } catch {
27
- // No .git, check for other legacy indicators
28
- const entries = await fs.readdir(legacyInstallDir)
29
-
30
- // Legacy has: bin/, core/, templates/, scripts/, package.json
31
- const legacyFiles = ['bin', 'core', 'templates', 'scripts', 'package.json']
32
- const hasLegacyFiles = legacyFiles.every((file) => entries.includes(file))
33
-
34
- if (hasLegacyFiles) {
35
- return true
36
- }
37
-
38
- // Only has projects/ and config/ = already migrated
39
- const onlyDataDirs = entries.every((entry) => ['projects', 'config'].includes(entry))
40
- return !onlyDataDirs
41
- }
42
- } catch {
43
- return false
44
- }
45
- }
46
-
47
- /**
48
- * Get npm global installation path
49
- */
50
- export function getNpmGlobalPath(): string {
51
- try {
52
- const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim()
53
- return path.join(npmRoot, 'prjct-cli')
54
- } catch {
55
- // Fallback to common locations
56
- const nodePath = process.execPath
57
- const nodeDir = path.dirname(path.dirname(nodePath))
58
- return path.join(nodeDir, 'lib', 'node_modules', 'prjct-cli')
59
- }
60
- }
61
-
62
- /**
63
- * Check if user has npm global installation
64
- */
65
- export async function hasNpmInstallation(): Promise<boolean> {
66
- try {
67
- execSync('npm list -g prjct-cli', { stdio: 'ignore' })
68
- return true
69
- } catch {
70
- return false
71
- }
72
- }
73
-
74
- /**
75
- * Get version of legacy installation
76
- */
77
- export async function getLegacyVersion(): Promise<string | null> {
78
- try {
79
- const packageJsonPath = path.join(legacyInstallDir, 'package.json')
80
- const content = await fs.readFile(packageJsonPath, 'utf8')
81
- const pkg = JSON.parse(content)
82
- return pkg.version || 'unknown'
83
- } catch {
84
- return null
85
- }
86
- }
87
-
88
- /**
89
- * Quick check - silent, returns true if cleanup needed
90
- */
91
- export async function needsCleanup(): Promise<boolean> {
92
- const hasLegacy = await hasLegacyInstallation()
93
- const hasNpm = await hasNpmInstallation()
94
- return hasLegacy && hasNpm
95
- }