prjct-cli 0.9.2 → 0.10.2

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 (53) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/core/__tests__/agentic/memory-system.test.js +263 -0
  3. package/core/__tests__/agentic/plan-mode.test.js +336 -0
  4. package/core/agentic/agent-router.js +253 -186
  5. package/core/agentic/chain-of-thought.js +578 -0
  6. package/core/agentic/command-executor.js +299 -17
  7. package/core/agentic/context-builder.js +208 -8
  8. package/core/agentic/context-filter.js +83 -83
  9. package/core/agentic/ground-truth.js +591 -0
  10. package/core/agentic/loop-detector.js +406 -0
  11. package/core/agentic/memory-system.js +850 -0
  12. package/core/agentic/parallel-tools.js +366 -0
  13. package/core/agentic/plan-mode.js +572 -0
  14. package/core/agentic/prompt-builder.js +127 -2
  15. package/core/agentic/response-templates.js +290 -0
  16. package/core/agentic/semantic-compression.js +517 -0
  17. package/core/agentic/think-blocks.js +657 -0
  18. package/core/agentic/tool-registry.js +32 -0
  19. package/core/agentic/validation-rules.js +380 -0
  20. package/core/command-registry.js +48 -0
  21. package/core/commands.js +128 -60
  22. package/core/context-sync.js +183 -0
  23. package/core/domain/agent-generator.js +77 -46
  24. package/core/domain/agent-loader.js +183 -0
  25. package/core/domain/agent-matcher.js +217 -0
  26. package/core/domain/agent-validator.js +217 -0
  27. package/core/domain/context-estimator.js +175 -0
  28. package/core/domain/product-standards.js +92 -0
  29. package/core/domain/smart-cache.js +157 -0
  30. package/core/domain/task-analyzer.js +353 -0
  31. package/core/domain/tech-detector.js +365 -0
  32. package/package.json +8 -16
  33. package/templates/commands/done.md +7 -0
  34. package/templates/commands/feature.md +8 -0
  35. package/templates/commands/ship.md +8 -0
  36. package/templates/commands/spec.md +128 -0
  37. package/templates/global/CLAUDE.md +17 -0
  38. package/core/__tests__/agentic/agent-router.test.js +0 -398
  39. package/core/__tests__/agentic/command-executor.test.js +0 -223
  40. package/core/__tests__/agentic/context-builder.test.js +0 -160
  41. package/core/__tests__/agentic/context-filter.test.js +0 -494
  42. package/core/__tests__/agentic/prompt-builder.test.js +0 -212
  43. package/core/__tests__/agentic/template-loader.test.js +0 -164
  44. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  45. package/core/__tests__/domain/agent-generator.test.js +0 -296
  46. package/core/__tests__/domain/analyzer.test.js +0 -324
  47. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  48. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  49. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  50. package/core/__tests__/setup.test.js +0 -15
  51. package/core/__tests__/utils/date-helper.test.js +0 -169
  52. package/core/__tests__/utils/file-helper.test.js +0 -258
  53. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
@@ -2,18 +2,59 @@
2
2
  * Prompt Builder
3
3
  * Builds prompts for Claude based on templates and context
4
4
  * Claude decides what to do - NO if/else logic here
5
+ *
6
+ * P1.1: Includes learned patterns from memory system
7
+ * P3.1: Includes think blocks for anti-hallucination
8
+ * P3.3: Includes relevant memories from semantic database
9
+ * P3.4: Includes plan mode instructions
5
10
  */
6
11
 
7
12
  class PromptBuilder {
8
13
  /**
9
14
  * Build concise prompt - only essentials
15
+ * CRITICAL: Includes full agent content if agent is provided
16
+ * P1.1: Includes learned patterns to avoid repetitive questions
17
+ * P3.1: Includes think blocks for critical decisions
18
+ * P3.3: Includes relevant memories from semantic database
19
+ * P3.4: Includes plan mode status and constraints
10
20
  */
11
- build(template, context, state, agent = null) {
21
+ build(template, context, state, agent = null, learnedPatterns = null, thinkBlock = null, relevantMemories = null, planInfo = null) {
12
22
  const parts = []
13
23
 
14
24
  // Agent assignment (if applicable)
25
+ // CRITICAL: Include full agent content, not just name
15
26
  if (agent) {
16
- parts.push(`AGENT: ${agent.name}\n`)
27
+ parts.push(`# AGENT ASSIGNMENT\n`)
28
+ parts.push(`Agent: ${agent.name}\n`)
29
+
30
+ // Include role if available
31
+ if (agent.role) {
32
+ parts.push(`Role: ${agent.role}\n`)
33
+ }
34
+
35
+ // Include domain if available
36
+ if (agent.domain) {
37
+ parts.push(`Domain: ${agent.domain}\n`)
38
+ }
39
+
40
+ // Include skills if available
41
+ if (agent.skills && agent.skills.length > 0) {
42
+ parts.push(`Skills: ${agent.skills.join(', ')}\n`)
43
+ }
44
+
45
+ parts.push(`\n## AGENT INSTRUCTIONS\n`)
46
+
47
+ // CRITICAL: Include full agent content
48
+ // This is the specialized knowledge for this project
49
+ if (agent.content) {
50
+ parts.push(agent.content)
51
+ parts.push(`\n`)
52
+ } else if (agent.name) {
53
+ // Fallback if content not loaded
54
+ parts.push(`You are the ${agent.name} agent for this project.\n`)
55
+ parts.push(`Apply your specialized expertise to complete the task.\n\n`)
56
+ }
57
+
17
58
  parts.push(`CONTEXT: ${context.filteredSize || 'all'} files (${context.reduction || 0}% reduced)\n\n`)
18
59
  }
19
60
 
@@ -49,6 +90,75 @@ class PromptBuilder {
49
90
  parts.push('\n')
50
91
  }
51
92
 
93
+ // Enforcement (Strict Mode)
94
+ parts.push(this.buildEnforcement());
95
+
96
+ // P1.1: Learned Patterns (avoid asking user questions we already know)
97
+ if (learnedPatterns && Object.keys(learnedPatterns).some(k => learnedPatterns[k])) {
98
+ parts.push('\n## LEARNED PATTERNS (use these, do NOT ask user)\n')
99
+ for (const [key, value] of Object.entries(learnedPatterns)) {
100
+ if (value) {
101
+ parts.push(`- ${key}: ${value}\n`)
102
+ }
103
+ }
104
+ }
105
+
106
+ // P3.1: Think Block (reasoning before action)
107
+ if (thinkBlock && thinkBlock.plan && thinkBlock.plan.length > 0) {
108
+ parts.push('\n## THINK FIRST (reasoning from analysis)\n')
109
+ if (thinkBlock.conclusions && thinkBlock.conclusions.length > 0) {
110
+ parts.push('Conclusions:\n')
111
+ thinkBlock.conclusions.forEach(c => parts.push(` → ${c}\n`))
112
+ }
113
+ parts.push('Plan:\n')
114
+ thinkBlock.plan.forEach((p, i) => parts.push(` ${i + 1}. ${p}\n`))
115
+ parts.push(`Confidence: ${Math.round((thinkBlock.confidence || 0.5) * 100)}%\n`)
116
+ }
117
+
118
+ // P3.3: Relevant Memories (context from past decisions)
119
+ if (relevantMemories && relevantMemories.length > 0) {
120
+ parts.push('\n## RELEVANT MEMORIES (apply these learnings)\n')
121
+ for (const memory of relevantMemories) {
122
+ parts.push(`- **${memory.title}**: ${memory.content}\n`)
123
+ if (memory.tags && memory.tags.length > 0) {
124
+ parts.push(` Tags: ${memory.tags.join(', ')}\n`)
125
+ }
126
+ }
127
+ }
128
+
129
+ // P3.4: Plan Mode instructions
130
+ if (planInfo) {
131
+ if (planInfo.isPlanning) {
132
+ parts.push('\n## PLAN MODE ACTIVE\n')
133
+ parts.push('You are in PLANNING mode. Follow these constraints:\n')
134
+ parts.push('1. **READ-ONLY**: Only use read tools (Read, Glob, Grep)\n')
135
+ parts.push('2. **GATHER INFO**: Collect all necessary information\n')
136
+ parts.push('3. **ANALYZE**: Understand the context and implications\n')
137
+ parts.push('4. **PROPOSE**: Generate a plan with clear steps\n')
138
+ parts.push('5. **WAIT FOR APPROVAL**: Do NOT execute until user approves\n')
139
+
140
+ if (planInfo.active) {
141
+ parts.push(`\nCurrent Status: ${planInfo.active.status}\n`)
142
+ if (planInfo.active.gatheredInfo?.length > 0) {
143
+ parts.push(`Info gathered: ${planInfo.active.gatheredInfo.length} items\n`)
144
+ }
145
+ }
146
+ }
147
+
148
+ if (planInfo.requiresApproval) {
149
+ parts.push('\n## APPROVAL REQUIRED\n')
150
+ parts.push('This command is DESTRUCTIVE. You MUST:\n')
151
+ parts.push('1. Show exactly what will change\n')
152
+ parts.push('2. List affected files/resources\n')
153
+ parts.push('3. Ask for explicit user confirmation (y/n)\n')
154
+ parts.push('4. Only proceed after approval\n')
155
+ }
156
+
157
+ if (planInfo.allowedTools) {
158
+ parts.push(`\nAllowed tools: ${planInfo.allowedTools.join(', ')}\n`)
159
+ }
160
+ }
161
+
52
162
  // Simple execution directive
53
163
  parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
54
164
 
@@ -91,6 +201,21 @@ class PromptBuilder {
91
201
 
92
202
  return parts.join('')
93
203
  }
204
+
205
+ /**
206
+ * Build enforcement section
207
+ * Forces Claude to follow the process strictly
208
+ */
209
+ buildEnforcement() {
210
+ return `
211
+ ## PROCESS ENFORCEMENT
212
+ 1. FOLLOW the Flow strictly. Do not skip steps.
213
+ 2. USE the allowed tools only.
214
+ 3. IF you are stuck, use the "Ask" tool or stop.
215
+ 4. DO NOT hallucinate files or commands.
216
+ 5. ALWAYS verify your changes.
217
+ `;
218
+ }
94
219
  }
95
220
 
96
221
  module.exports = new PromptBuilder()
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Response Templates
3
+ * Minimal output templates for all commands
4
+ * Rule: < 4 lines, always actionable
5
+ *
6
+ * OPTIMIZATION (P0.3): Minimal Output
7
+ * - Concise responses (< 4 lines)
8
+ * - Always suggest next action
9
+ * - Use symbols for status, not words
10
+ *
11
+ * Source: Claude Code, Kiro patterns
12
+ */
13
+
14
+ /**
15
+ * Format duration from milliseconds or ISO strings
16
+ * @param {number|string|Date} start - Start time
17
+ * @param {number|string|Date} end - End time (defaults to now)
18
+ * @returns {string} Human-readable duration
19
+ */
20
+ function formatDuration(start, end = new Date()) {
21
+ const startMs = new Date(start).getTime()
22
+ const endMs = new Date(end).getTime()
23
+ const diffMs = endMs - startMs
24
+
25
+ const hours = Math.floor(diffMs / (1000 * 60 * 60))
26
+ const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
27
+
28
+ if (hours > 0) {
29
+ return `${hours}h ${minutes}m`
30
+ }
31
+ return `${minutes}m`
32
+ }
33
+
34
+ /**
35
+ * Truncate text to max length with ellipsis
36
+ * @param {string} text - Text to truncate
37
+ * @param {number} maxLength - Maximum length
38
+ * @returns {string}
39
+ */
40
+ function truncate(text, maxLength = 40) {
41
+ if (!text) return ''
42
+ if (text.length <= maxLength) return text
43
+ return text.substring(0, maxLength - 3) + '...'
44
+ }
45
+
46
+ /**
47
+ * Response templates for each command
48
+ * Each template is a function that returns minimal formatted output
49
+ */
50
+ const templates = {
51
+ /**
52
+ * /p:done - Task completed
53
+ */
54
+ done: ({ task, duration, nextTask }) => {
55
+ let output = `✓ '${truncate(task)}' (${duration})`
56
+ if (nextTask) {
57
+ output += `\n→ Next: '${truncate(nextTask)}'`
58
+ }
59
+ output += '\n\n/p:ship to release'
60
+ return output
61
+ },
62
+
63
+ /**
64
+ * /p:now - Current task set/shown
65
+ */
66
+ now: ({ task, started, isNew }) => {
67
+ if (!task) {
68
+ return `No active task\n→ /p:now "task" to start`
69
+ }
70
+ if (isNew) {
71
+ return `🎯 Started: '${truncate(task)}'\n→ /p:done when complete`
72
+ }
73
+ return `🎯 Working on: '${truncate(task)}'\n⏱️ ${started || 'just now'}\n→ /p:done when complete`
74
+ },
75
+
76
+ /**
77
+ * /p:next - Priority queue
78
+ */
79
+ next: ({ tasks, total }) => {
80
+ if (!tasks || tasks.length === 0) {
81
+ return `Queue empty\n→ /p:feature or /p:idea to add`
82
+ }
83
+ const top3 = tasks.slice(0, 3).map((t, i) =>
84
+ `${i + 1}. ${truncate(t.name, 35)}`
85
+ ).join('\n')
86
+ const more = total > 3 ? `\n+${total - 3} more` : ''
87
+ return `${top3}${more}\n\n/p:now 1 to start`
88
+ },
89
+
90
+ /**
91
+ * /p:ship - Feature shipped
92
+ */
93
+ ship: ({ feature, agent, duration, version }) => {
94
+ let output = `🚀 Shipped: '${truncate(feature)}'`
95
+ if (agent) output += ` (${agent})`
96
+ if (duration) output += ` | ${duration}`
97
+ if (version) output += ` | v${version}`
98
+ output += '\n→ /compact recommended'
99
+ return output
100
+ },
101
+
102
+ /**
103
+ * /p:idea - Idea captured
104
+ */
105
+ idea: ({ idea, addedToQueue }) => {
106
+ let output = `💡 Captured: '${truncate(idea)}'`
107
+ if (addedToQueue) {
108
+ output += '\n→ Added to queue'
109
+ }
110
+ output += '\n\n/p:next to see queue'
111
+ return output
112
+ },
113
+
114
+ /**
115
+ * /p:feature - Feature added
116
+ */
117
+ feature: ({ feature, tasks, impact, effort }) => {
118
+ let output = `📋 Feature: '${truncate(feature)}'`
119
+ if (tasks) output += ` (${tasks} tasks)`
120
+ if (impact) output += `\nImpact: ${impact}`
121
+ if (effort) output += ` | Effort: ${effort}`
122
+ output += '\n\n/p:now to start'
123
+ return output
124
+ },
125
+
126
+ /**
127
+ * /p:bug - Bug reported
128
+ */
129
+ bug: ({ description, priority, addedAt }) => {
130
+ const priorityIcon = {
131
+ 'critical': '🔴',
132
+ 'high': '🟠',
133
+ 'medium': '🟡',
134
+ 'low': '🟢'
135
+ }[priority] || '🟡'
136
+
137
+ let output = `${priorityIcon} Bug: '${truncate(description)}'\nPriority: ${priority}`
138
+ if (addedAt) {
139
+ output += ` | Added: ${addedAt}`
140
+ }
141
+ output += '\n\n/p:now to fix'
142
+ return output
143
+ },
144
+
145
+ /**
146
+ * /p:pause - Task paused
147
+ */
148
+ pause: ({ task, duration }) => {
149
+ return `⏸️ Paused: '${truncate(task)}' (${duration})\n→ /p:resume to continue`
150
+ },
151
+
152
+ /**
153
+ * /p:resume - Task resumed
154
+ */
155
+ resume: ({ task, pausedFor }) => {
156
+ return `▶️ Resumed: '${truncate(task)}'\nPaused for: ${pausedFor}\n→ /p:done when complete`
157
+ },
158
+
159
+ /**
160
+ * /p:recap - Project overview
161
+ */
162
+ recap: ({ shipped, inProgress, queued, momentum }) => {
163
+ const momentumIcon = {
164
+ 'high': '🔥',
165
+ 'medium': '✨',
166
+ 'low': '💤'
167
+ }[momentum] || '✨'
168
+
169
+ return `${momentumIcon} ${shipped} shipped | ${inProgress ? '1 active' : '0 active'} | ${queued} queued`
170
+ },
171
+
172
+ /**
173
+ * /p:progress - Progress metrics
174
+ */
175
+ progress: ({ period, shipped, velocity, trend }) => {
176
+ const trendIcon = trend > 0 ? '↑' : trend < 0 ? '↓' : '→'
177
+ return `📊 ${period}: ${shipped} shipped\nVelocity: ${velocity}/week ${trendIcon}`
178
+ },
179
+
180
+ /**
181
+ * /p:analyze - Analysis complete
182
+ */
183
+ analyze: ({ stack, files, agents }) => {
184
+ return `🔍 Analyzed: ${stack}\n${files} files | ${agents} agents generated\n\n/p:sync to update`
185
+ },
186
+
187
+ /**
188
+ * /p:sync - Sync complete
189
+ */
190
+ sync: ({ updated, agents }) => {
191
+ return `🔄 Synced: ${updated} files updated\n${agents} agents refreshed`
192
+ },
193
+
194
+ /**
195
+ * /p:help - Help shown
196
+ */
197
+ help: ({ context, suggestions }) => {
198
+ const sugs = suggestions.slice(0, 3).map(s => `• ${s}`).join('\n')
199
+ return `📚 ${context}\n\n${sugs}`
200
+ },
201
+
202
+ /**
203
+ * /p:suggest - Suggestions
204
+ */
205
+ suggest: ({ urgency, suggestion, command }) => {
206
+ const urgencyIcon = {
207
+ 'high': '🔥',
208
+ 'medium': '💡',
209
+ 'low': '✨'
210
+ }[urgency] || '💡'
211
+
212
+ return `${urgencyIcon} ${suggestion}\n→ ${command}`
213
+ },
214
+
215
+ /**
216
+ * /p:spec - Spec created/updated
217
+ */
218
+ spec: ({ name, status, tasks, requirements, isNew }) => {
219
+ let output = isNew
220
+ ? `📋 Created spec: '${truncate(name)}'`
221
+ : `📋 Updated spec: '${truncate(name)}'`
222
+
223
+ if (requirements) output += `\n${requirements} requirements`
224
+ if (tasks) output += ` | ${tasks} tasks`
225
+ if (status) output += ` | Status: ${status}`
226
+
227
+ output += '\n\n→ Review and approve to start'
228
+ return output
229
+ },
230
+
231
+ /**
232
+ * Generic success response
233
+ */
234
+ success: ({ message, nextAction }) => {
235
+ let output = `✓ ${message}`
236
+ if (nextAction) {
237
+ output += `\n→ ${nextAction}`
238
+ }
239
+ return output
240
+ },
241
+
242
+ /**
243
+ * Generic error response
244
+ */
245
+ error: ({ error, suggestion }) => {
246
+ let output = `❌ ${error}`
247
+ if (suggestion) {
248
+ output += `\n→ ${suggestion}`
249
+ }
250
+ return output
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Format a response using the appropriate template
256
+ *
257
+ * @param {string} commandName - Command name
258
+ * @param {Object} data - Data for the template
259
+ * @returns {string} Formatted response
260
+ */
261
+ function format(commandName, data) {
262
+ const template = templates[commandName]
263
+ if (!template) {
264
+ // Fallback to generic success/error
265
+ if (data.error) {
266
+ return templates.error(data)
267
+ }
268
+ return templates.success(data)
269
+ }
270
+
271
+ return template(data)
272
+ }
273
+
274
+ /**
275
+ * Check if response exceeds recommended length
276
+ * @param {string} response - Response text
277
+ * @returns {boolean} True if too long
278
+ */
279
+ function isTooLong(response) {
280
+ const lines = response.split('\n').filter(l => l.trim())
281
+ return lines.length > 4
282
+ }
283
+
284
+ module.exports = {
285
+ format,
286
+ templates,
287
+ formatDuration,
288
+ truncate,
289
+ isTooLong
290
+ }