prjct-cli 0.15.1 → 0.18.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 (72) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/dev.js +0 -1
  3. package/bin/serve.js +19 -20
  4. package/core/__tests__/agentic/memory-system.test.ts +2 -1
  5. package/core/__tests__/agentic/plan-mode.test.ts +2 -1
  6. package/core/agentic/agent-router.ts +79 -14
  7. package/core/agentic/command-executor/command-executor.ts +2 -74
  8. package/core/agentic/services.ts +0 -48
  9. package/core/agentic/template-loader.ts +35 -1
  10. package/core/command-registry/setup-commands.ts +15 -0
  11. package/core/commands/base.ts +96 -77
  12. package/core/commands/planning.ts +13 -2
  13. package/core/commands/setup.ts +3 -85
  14. package/core/domain/agent-generator.ts +9 -17
  15. package/core/errors.ts +209 -0
  16. package/core/infrastructure/config-manager.ts +22 -5
  17. package/core/infrastructure/path-manager.ts +23 -1
  18. package/core/infrastructure/setup.ts +5 -50
  19. package/core/storage/ideas-storage.ts +4 -0
  20. package/core/storage/queue-storage.ts +4 -0
  21. package/core/storage/shipped-storage.ts +4 -0
  22. package/core/storage/state-storage.ts +4 -0
  23. package/core/storage/storage-manager.ts +52 -13
  24. package/core/sync/auth-config.ts +145 -0
  25. package/core/sync/index.ts +30 -0
  26. package/core/sync/oauth-handler.ts +148 -0
  27. package/core/sync/sync-client.ts +252 -0
  28. package/core/sync/sync-manager.ts +358 -0
  29. package/core/utils/logger.ts +19 -12
  30. package/package.json +2 -4
  31. package/templates/agentic/subagent-generation.md +109 -0
  32. package/templates/commands/auth.md +234 -0
  33. package/templates/commands/sync.md +129 -13
  34. package/templates/subagents/domain/backend.md +105 -0
  35. package/templates/subagents/domain/database.md +118 -0
  36. package/templates/subagents/domain/devops.md +148 -0
  37. package/templates/subagents/domain/frontend.md +99 -0
  38. package/templates/subagents/domain/testing.md +169 -0
  39. package/templates/subagents/workflow/prjct-planner.md +158 -0
  40. package/templates/subagents/workflow/prjct-shipper.md +179 -0
  41. package/templates/subagents/workflow/prjct-workflow.md +98 -0
  42. package/bin/generate-views.js +0 -209
  43. package/bin/migrate-to-json.js +0 -742
  44. package/core/agentic/context-filter.ts +0 -365
  45. package/core/agentic/parallel-tools.ts +0 -165
  46. package/core/agentic/response-templates.ts +0 -164
  47. package/core/agentic/semantic-compression.ts +0 -273
  48. package/core/agentic/think-blocks.ts +0 -202
  49. package/core/agentic/validation-rules.ts +0 -313
  50. package/core/domain/agent-matcher.ts +0 -130
  51. package/core/domain/agent-validator.ts +0 -250
  52. package/core/domain/architect-session.ts +0 -315
  53. package/core/domain/product-standards.ts +0 -106
  54. package/core/domain/smart-cache.ts +0 -167
  55. package/core/domain/task-analyzer.ts +0 -296
  56. package/core/infrastructure/legacy-installer-detector/cleanup.ts +0 -216
  57. package/core/infrastructure/legacy-installer-detector/detection.ts +0 -95
  58. package/core/infrastructure/legacy-installer-detector/index.ts +0 -171
  59. package/core/infrastructure/legacy-installer-detector/migration.ts +0 -87
  60. package/core/infrastructure/legacy-installer-detector/types.ts +0 -42
  61. package/core/infrastructure/legacy-installer-detector.ts +0 -7
  62. package/core/infrastructure/migrator/file-operations.ts +0 -125
  63. package/core/infrastructure/migrator/index.ts +0 -288
  64. package/core/infrastructure/migrator/project-scanner.ts +0 -90
  65. package/core/infrastructure/migrator/reports.ts +0 -117
  66. package/core/infrastructure/migrator/types.ts +0 -124
  67. package/core/infrastructure/migrator/validation.ts +0 -94
  68. package/core/infrastructure/migrator/version-migration.ts +0 -117
  69. package/core/infrastructure/migrator.ts +0 -10
  70. package/core/infrastructure/uuid-migration.ts +0 -750
  71. package/templates/commands/migrate-all.md +0 -96
  72. package/templates/commands/migrate.md +0 -140
@@ -1,313 +0,0 @@
1
- /**
2
- * Validation Rules
3
- * Pre-flight validation for commands before execution
4
- *
5
- * @module agentic/validation-rules
6
- * @version 1.0.0
7
- */
8
-
9
- import fs from 'fs/promises'
10
- import path from 'path'
11
- import os from 'os'
12
-
13
- interface Context {
14
- projectPath: string
15
- projectId?: string | null
16
- paths: Record<string, string>
17
- params: Record<string, unknown>
18
- }
19
-
20
- interface ValidationResult {
21
- valid: boolean
22
- errors: string[]
23
- warnings: string[]
24
- suggestions: string[]
25
- }
26
-
27
- type Validator = (context: Context) => Promise<ValidationResult>
28
-
29
- /**
30
- * Command-specific validators
31
- */
32
- const validators: Record<string, Validator> = {
33
- /**
34
- * Validate /p:done can execute
35
- */
36
- async done(context): Promise<ValidationResult> {
37
- const errors: string[] = []
38
- const warnings: string[] = []
39
- const suggestions: string[] = []
40
-
41
- // Check if now.md exists and has content
42
- try {
43
- const nowContent = await fs.readFile(context.paths.now, 'utf-8')
44
- if (!nowContent.trim() || nowContent.includes('No current task')) {
45
- errors.push('No active task to complete')
46
- suggestions.push('Start a task with /p:now "task description"')
47
- }
48
- } catch {
49
- errors.push('now.md does not exist')
50
- suggestions.push('Initialize project with /p:init')
51
- }
52
-
53
- return {
54
- valid: errors.length === 0,
55
- errors,
56
- warnings,
57
- suggestions,
58
- }
59
- },
60
-
61
- /**
62
- * Validate /p:ship can execute
63
- */
64
- async ship(context): Promise<ValidationResult> {
65
- const errors: string[] = []
66
- const warnings: string[] = []
67
- const suggestions: string[] = []
68
-
69
- // Check if now.md has content to ship
70
- try {
71
- const nowContent = await fs.readFile(context.paths.now, 'utf-8')
72
- if (!nowContent.trim() || nowContent.includes('No current task')) {
73
- warnings.push('No active task to ship')
74
- }
75
- } catch {
76
- warnings.push('now.md does not exist')
77
- }
78
-
79
- // Check if shipped.md exists
80
- try {
81
- await fs.access(path.dirname(context.paths.shipped))
82
- } catch {
83
- warnings.push('shipped.md directory does not exist')
84
- suggestions.push('Run /p:init to initialize project structure')
85
- }
86
-
87
- return {
88
- valid: errors.length === 0,
89
- errors,
90
- warnings,
91
- suggestions,
92
- }
93
- },
94
-
95
- /**
96
- * Validate /p:now can execute
97
- */
98
- async now(context): Promise<ValidationResult> {
99
- const errors: string[] = []
100
- const warnings: string[] = []
101
- const suggestions: string[] = []
102
-
103
- // Check if project is initialized
104
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
105
- try {
106
- await fs.access(configPath)
107
- } catch {
108
- errors.push('Project not initialized')
109
- suggestions.push('Run /p:init to initialize')
110
- }
111
-
112
- // Check if already has a task
113
- try {
114
- const nowContent = await fs.readFile(context.paths.now, 'utf-8')
115
- if (nowContent.trim() && !nowContent.includes('No current task')) {
116
- warnings.push('Already has an active task')
117
- suggestions.push('Complete it first with /p:done')
118
- }
119
- } catch {
120
- // now.md doesn't exist - that's ok for /p:now
121
- }
122
-
123
- return {
124
- valid: errors.length === 0,
125
- errors,
126
- warnings,
127
- suggestions,
128
- }
129
- },
130
-
131
- /**
132
- * Validate /p:init can execute
133
- */
134
- async init(context): Promise<ValidationResult> {
135
- const errors: string[] = []
136
- const warnings: string[] = []
137
- const suggestions: string[] = []
138
-
139
- // Check if already initialized
140
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
141
- try {
142
- await fs.access(configPath)
143
- warnings.push('Project already initialized')
144
- suggestions.push('Use /p:sync to refresh or delete .prjct/ to reinitialize')
145
- } catch {
146
- // Not initialized - good
147
- }
148
-
149
- // Check if global storage is writable
150
- const globalPath = path.join(os.homedir(), '.prjct-cli')
151
- try {
152
- await fs.mkdir(globalPath, { recursive: true })
153
- } catch {
154
- errors.push('Cannot create ~/.prjct-cli directory')
155
- suggestions.push('Check filesystem permissions')
156
- }
157
-
158
- return {
159
- valid: errors.length === 0,
160
- errors,
161
- warnings,
162
- suggestions,
163
- }
164
- },
165
-
166
- /**
167
- * Validate /p:sync can execute
168
- */
169
- async sync(context): Promise<ValidationResult> {
170
- const errors: string[] = []
171
- const warnings: string[] = []
172
- const suggestions: string[] = []
173
-
174
- // Check if project is initialized
175
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
176
- try {
177
- await fs.access(configPath)
178
- } catch {
179
- errors.push('Project not initialized')
180
- suggestions.push('Run /p:init first')
181
- }
182
-
183
- return {
184
- valid: errors.length === 0,
185
- errors,
186
- warnings,
187
- suggestions,
188
- }
189
- },
190
-
191
- /**
192
- * Validate /p:feature can execute
193
- */
194
- async feature(context): Promise<ValidationResult> {
195
- const errors: string[] = []
196
- const warnings: string[] = []
197
- const suggestions: string[] = []
198
-
199
- // Check if project is initialized
200
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
201
- try {
202
- await fs.access(configPath)
203
- } catch {
204
- errors.push('Project not initialized')
205
- suggestions.push('Run /p:init first')
206
- }
207
-
208
- // Check if description provided
209
- if (!context.params.description && !context.params.feature) {
210
- warnings.push('No feature description provided')
211
- suggestions.push('Provide a feature description')
212
- }
213
-
214
- return {
215
- valid: errors.length === 0,
216
- errors,
217
- warnings,
218
- suggestions,
219
- }
220
- },
221
-
222
- /**
223
- * Validate /p:idea can execute
224
- */
225
- async idea(context): Promise<ValidationResult> {
226
- const errors: string[] = []
227
- const warnings: string[] = []
228
- const suggestions: string[] = []
229
-
230
- // Check if project is initialized
231
- const configPath = path.join(context.projectPath, '.prjct/prjct.config.json')
232
- try {
233
- await fs.access(configPath)
234
- } catch {
235
- errors.push('Project not initialized')
236
- suggestions.push('Run /p:init first')
237
- }
238
-
239
- // Check if idea text provided
240
- if (!context.params.idea && !context.params.text) {
241
- warnings.push('No idea text provided')
242
- suggestions.push('Provide idea text')
243
- }
244
-
245
- return {
246
- valid: errors.length === 0,
247
- errors,
248
- warnings,
249
- suggestions,
250
- }
251
- },
252
- }
253
-
254
- /**
255
- * Validate a command before execution
256
- */
257
- async function validate(commandName: string, context: Context): Promise<ValidationResult> {
258
- const validator = validators[commandName]
259
-
260
- if (!validator) {
261
- // No specific validation needed
262
- return {
263
- valid: true,
264
- errors: [],
265
- warnings: [],
266
- suggestions: [],
267
- }
268
- }
269
-
270
- try {
271
- return await validator(context)
272
- } catch (error) {
273
- return {
274
- valid: false,
275
- errors: [`Validation error: ${(error as Error).message}`],
276
- warnings: [],
277
- suggestions: ['Check file permissions and project configuration'],
278
- }
279
- }
280
- }
281
-
282
- /**
283
- * Format validation errors for display
284
- */
285
- function formatError(result: ValidationResult): string {
286
- let output = ''
287
-
288
- if (result.errors.length > 0) {
289
- output += '❌ Validation Failed:\n'
290
- result.errors.forEach((e) => {
291
- output += ` • ${e}\n`
292
- })
293
- }
294
-
295
- if (result.warnings.length > 0) {
296
- output += '\n⚠️ Warnings:\n'
297
- result.warnings.forEach((w) => {
298
- output += ` • ${w}\n`
299
- })
300
- }
301
-
302
- if (result.suggestions.length > 0) {
303
- output += '\n💡 Suggestions:\n'
304
- result.suggestions.forEach((s) => {
305
- output += ` → ${s}\n`
306
- })
307
- }
308
-
309
- return output
310
- }
311
-
312
- export { validate, formatError, validators }
313
- export default { validate, formatError, validators }
@@ -1,130 +0,0 @@
1
- /**
2
- * AgentMatcher - Orchestration Only
3
- *
4
- * AGENTIC: All matching decisions made by Claude via templates/agent-assignment.md
5
- * JS only orchestrates: format data, pass to Claude, return result
6
- *
7
- * NO scoring logic, NO algorithms, NO hardcoded weights
8
- *
9
- * @version 2.0.0
10
- */
11
-
12
- import fs from 'fs/promises'
13
- import path from 'path'
14
-
15
- interface Agent {
16
- name: string
17
- domain?: string
18
- content?: string
19
- }
20
-
21
- interface Task {
22
- description?: string
23
- type?: string
24
- }
25
-
26
- interface HistoryEntry {
27
- timestamp: string
28
- agent: string
29
- task: string
30
- }
31
-
32
- interface FormattedAgent {
33
- name: string
34
- domain: string
35
- hasContent: boolean
36
- }
37
-
38
- interface FormattedTask {
39
- description: string
40
- type: string
41
- }
42
-
43
- class AgentMatcher {
44
- historyPath: string | null = null
45
-
46
- /**
47
- * Set history path for logging
48
- * ORCHESTRATION: Path setup only
49
- */
50
- setHistoryPath(projectId: string): void {
51
- this.historyPath = path.join(
52
- process.env.HOME || '',
53
- '.prjct-cli',
54
- 'projects',
55
- projectId,
56
- 'agent-history.jsonl'
57
- )
58
- }
59
-
60
- /**
61
- * Format agents for Claude
62
- * ORCHESTRATION: Data formatting only
63
- */
64
- formatAgentsForTemplate(agents: Agent[]): FormattedAgent[] {
65
- return agents.map((a) => ({
66
- name: a.name,
67
- domain: a.domain || 'general',
68
- hasContent: !!a.content,
69
- }))
70
- }
71
-
72
- /**
73
- * Format task for Claude
74
- * ORCHESTRATION: Data formatting only
75
- */
76
- formatTaskForTemplate(task: string | Task): FormattedTask {
77
- return {
78
- description: typeof task === 'string' ? task : task.description || '',
79
- type: typeof task === 'string' ? 'unknown' : task.type || 'unknown',
80
- }
81
- }
82
-
83
- /**
84
- * Record agent usage
85
- * ORCHESTRATION: File I/O only
86
- */
87
- async recordUsage(agent: string | Agent, task: string | Task): Promise<void> {
88
- if (!this.historyPath) return
89
-
90
- try {
91
- const entry =
92
- JSON.stringify({
93
- timestamp: new Date().toISOString(),
94
- agent: typeof agent === 'string' ? agent : agent.name,
95
- task: typeof task === 'string' ? task : task.description,
96
- }) + '\n'
97
-
98
- await fs.appendFile(this.historyPath, entry)
99
- } catch {
100
- // Silent fail
101
- }
102
- }
103
-
104
- /**
105
- * Load usage history
106
- * ORCHESTRATION: File I/O only
107
- */
108
- async loadHistory(): Promise<HistoryEntry[]> {
109
- if (!this.historyPath) return []
110
-
111
- try {
112
- const content = await fs.readFile(this.historyPath, 'utf-8')
113
- return content
114
- .split('\n')
115
- .filter(Boolean)
116
- .map((line) => {
117
- try {
118
- return JSON.parse(line) as HistoryEntry
119
- } catch {
120
- return null
121
- }
122
- })
123
- .filter((entry): entry is HistoryEntry => entry !== null)
124
- } catch {
125
- return []
126
- }
127
- }
128
- }
129
-
130
- export default AgentMatcher
@@ -1,250 +0,0 @@
1
- /**
2
- * AgentValidator - Validates agents before and after generation
3
- *
4
- * Ensures agents are useful and not generic
5
- * Compares with existing agents before generating
6
- *
7
- * @version 1.0.0
8
- */
9
-
10
- interface AgentConfig {
11
- expertise?: string
12
- domain?: string
13
- projectContext?: Record<string, unknown>
14
- }
15
-
16
- interface Agent {
17
- name: string
18
- content?: string
19
- skills?: string[]
20
- domain?: string
21
- role?: string
22
- }
23
-
24
- interface ValidationResult {
25
- valid: boolean
26
- issues: string[]
27
- warnings: string[]
28
- similarAgent?: Agent | null
29
- usefulness?: number
30
- }
31
-
32
- class AgentValidator {
33
- /**
34
- * Validate if agent should be generated
35
- */
36
- validateBeforeGeneration(
37
- agentName: string,
38
- config: AgentConfig,
39
- existingAgents: Agent[] = []
40
- ): ValidationResult {
41
- const issues: string[] = []
42
- const warnings: string[] = []
43
-
44
- // Check if similar agent exists
45
- const similar = this.findSimilarAgent(agentName, config, existingAgents)
46
- if (similar) {
47
- warnings.push(`Similar agent exists: ${similar.name}`)
48
- }
49
-
50
- // Check if agent has specific skills
51
- if (!config.expertise || config.expertise.length < 10) {
52
- issues.push('Agent expertise is too generic')
53
- }
54
-
55
- // Check if agent has project context
56
- if (!config.projectContext || Object.keys(config.projectContext).length === 0) {
57
- warnings.push('Agent has no project-specific context')
58
- }
59
-
60
- // Check if agent name is descriptive
61
- if (agentName.includes('specialist') && !config.expertise) {
62
- issues.push('Agent name suggests specialization but has no expertise defined')
63
- }
64
-
65
- return {
66
- valid: issues.length === 0,
67
- issues,
68
- warnings,
69
- similarAgent: similar,
70
- }
71
- }
72
-
73
- /**
74
- * Validate agent after generation
75
- */
76
- validateAfterGeneration(agent: Agent): ValidationResult {
77
- const issues: string[] = []
78
- const warnings: string[] = []
79
-
80
- // Check if agent has content
81
- if (!agent.content || agent.content.length < 100) {
82
- issues.push('Agent content is too short or missing')
83
- }
84
-
85
- // Check if agent has skills extracted
86
- if (!agent.skills || agent.skills.length === 0) {
87
- warnings.push('Agent has no skills detected')
88
- }
89
-
90
- // Check if agent has domain
91
- if (!agent.domain || agent.domain === 'general') {
92
- warnings.push('Agent domain is generic')
93
- }
94
-
95
- // Check if agent is useful (not too generic)
96
- const usefulness = this.calculateUsefulness(agent)
97
- if (usefulness < 0.5) {
98
- issues.push('Agent is too generic to be useful')
99
- }
100
-
101
- return {
102
- valid: issues.length === 0,
103
- issues,
104
- warnings,
105
- usefulness,
106
- }
107
- }
108
-
109
- /**
110
- * Find similar existing agent
111
- */
112
- findSimilarAgent(agentName: string, config: AgentConfig, existingAgents: Agent[]): Agent | null {
113
- for (const existing of existingAgents) {
114
- // Check name similarity
115
- if (this.namesSimilar(agentName, existing.name)) {
116
- return existing
117
- }
118
-
119
- // Check domain similarity
120
- if (config.domain && existing.domain && config.domain === existing.domain) {
121
- // Check if skills overlap significantly
122
- const skillOverlap = this.calculateSkillOverlap(config, existing)
123
- if (skillOverlap > 0.7) {
124
- return existing
125
- }
126
- }
127
- }
128
-
129
- return null
130
- }
131
-
132
- /**
133
- * Check if agent names are similar
134
- */
135
- namesSimilar(name1: string, name2: string): boolean {
136
- const n1 = name1.toLowerCase()
137
- const n2 = name2.toLowerCase()
138
-
139
- // Exact match
140
- if (n1 === n2) return true
141
-
142
- // One contains the other
143
- if (n1.includes(n2) || n2.includes(n1)) return true
144
-
145
- // Check word overlap
146
- const words1 = new Set(n1.split('-'))
147
- const words2 = new Set(n2.split('-'))
148
- const intersection = new Set([...words1].filter((w) => words2.has(w)))
149
- const union = new Set([...words1, ...words2])
150
-
151
- return intersection.size / union.size > 0.5
152
- }
153
-
154
- /**
155
- * Calculate skill overlap between config and existing agent
156
- */
157
- calculateSkillOverlap(config: AgentConfig, existingAgent: Agent): number {
158
- if (!existingAgent.skills || existingAgent.skills.length === 0) {
159
- return 0
160
- }
161
-
162
- // Extract skills from config expertise
163
- const configSkills = this.extractSkillsFromText(config.expertise || '')
164
- if (configSkills.length === 0) {
165
- return 0
166
- }
167
-
168
- // Calculate overlap
169
- const existingSet = new Set(existingAgent.skills.map((s) => s.toLowerCase()))
170
- const configSet = new Set(configSkills.map((s) => s.toLowerCase()))
171
-
172
- const intersection = new Set([...existingSet].filter((s) => configSet.has(s)))
173
- const union = new Set([...existingSet, ...configSet])
174
-
175
- return intersection.size / union.size
176
- }
177
-
178
- /**
179
- * Extract skills from text
180
- */
181
- extractSkillsFromText(text: string): string[] {
182
- // Common technology keywords
183
- const techKeywords = [
184
- 'React',
185
- 'Vue',
186
- 'Angular',
187
- 'Svelte',
188
- 'Next.js',
189
- 'Nuxt',
190
- 'SvelteKit',
191
- 'TypeScript',
192
- 'JavaScript',
193
- 'Node.js',
194
- 'Express',
195
- 'Fastify',
196
- 'Python',
197
- 'Django',
198
- 'Flask',
199
- 'FastAPI',
200
- 'Go',
201
- 'Rust',
202
- 'Ruby',
203
- 'Rails',
204
- 'PostgreSQL',
205
- 'MySQL',
206
- 'MongoDB',
207
- ]
208
-
209
- return techKeywords.filter((tech) => text.toLowerCase().includes(tech.toLowerCase()))
210
- }
211
-
212
- /**
213
- * Calculate agent usefulness score
214
- */
215
- calculateUsefulness(agent: Agent): number {
216
- let score = 0
217
-
218
- // Has skills
219
- if (agent.skills && agent.skills.length > 0) {
220
- score += 0.3
221
- if (agent.skills.length > 3) {
222
- score += 0.1 // Bonus for multiple skills
223
- }
224
- }
225
-
226
- // Has specific domain
227
- if (agent.domain && agent.domain !== 'general') {
228
- score += 0.2
229
- }
230
-
231
- // Has content
232
- if (agent.content && agent.content.length > 200) {
233
- score += 0.2
234
- }
235
-
236
- // Has role
237
- if (agent.role && agent.role.length > 10) {
238
- score += 0.1
239
- }
240
-
241
- // Not generic name
242
- if (agent.name && !agent.name.includes('generalist')) {
243
- score += 0.1
244
- }
245
-
246
- return Math.min(score, 1.0)
247
- }
248
- }
249
-
250
- export default AgentValidator