prjct-cli 0.10.0 → 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 (43) hide show
  1. package/CHANGELOG.md +31 -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/chain-of-thought.js +578 -0
  5. package/core/agentic/command-executor.js +238 -4
  6. package/core/agentic/context-builder.js +208 -8
  7. package/core/agentic/ground-truth.js +591 -0
  8. package/core/agentic/loop-detector.js +406 -0
  9. package/core/agentic/memory-system.js +850 -0
  10. package/core/agentic/parallel-tools.js +366 -0
  11. package/core/agentic/plan-mode.js +572 -0
  12. package/core/agentic/prompt-builder.js +76 -1
  13. package/core/agentic/response-templates.js +290 -0
  14. package/core/agentic/semantic-compression.js +517 -0
  15. package/core/agentic/think-blocks.js +657 -0
  16. package/core/agentic/tool-registry.js +32 -0
  17. package/core/agentic/validation-rules.js +380 -0
  18. package/core/command-registry.js +48 -0
  19. package/core/commands.js +43 -1
  20. package/core/context-sync.js +183 -0
  21. package/package.json +7 -15
  22. package/templates/commands/done.md +7 -0
  23. package/templates/commands/feature.md +8 -0
  24. package/templates/commands/ship.md +8 -0
  25. package/templates/commands/spec.md +128 -0
  26. package/templates/global/CLAUDE.md +17 -0
  27. package/core/__tests__/agentic/agent-router.test.js +0 -398
  28. package/core/__tests__/agentic/command-executor.test.js +0 -223
  29. package/core/__tests__/agentic/context-builder.test.js +0 -160
  30. package/core/__tests__/agentic/context-filter.test.js +0 -494
  31. package/core/__tests__/agentic/prompt-builder.test.js +0 -204
  32. package/core/__tests__/agentic/template-loader.test.js +0 -164
  33. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  34. package/core/__tests__/domain/agent-generator.test.js +0 -289
  35. package/core/__tests__/domain/agent-loader.test.js +0 -179
  36. package/core/__tests__/domain/analyzer.test.js +0 -324
  37. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  38. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  39. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  40. package/core/__tests__/setup.test.js +0 -15
  41. package/core/__tests__/utils/date-helper.test.js +0 -169
  42. package/core/__tests__/utils/file-helper.test.js +0 -258
  43. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
@@ -0,0 +1,572 @@
1
+ /**
2
+ * Plan Mode System
3
+ *
4
+ * P3.4: Plan Mode + Approval Flow
5
+ * Separates planning from execution for better user confidence.
6
+ *
7
+ * Pattern from: Devin AI, Windsurf, Kiro
8
+ *
9
+ * ```
10
+ * // Planning Mode:
11
+ * // 1. Gather information (read-only tools)
12
+ * // 2. Analyze and understand codebase
13
+ * // 3. Ask clarifying questions
14
+ * // 4. Generate plan with <suggest_plan/>
15
+ * // 5. Wait for user approval
16
+ *
17
+ * // Execution Mode:
18
+ * // 1. Execute approved plan
19
+ * // 2. Show progress
20
+ * // 3. User can pause/abort
21
+ * ```
22
+ */
23
+
24
+ /**
25
+ * Commands that require planning mode
26
+ */
27
+ const PLAN_REQUIRED_COMMANDS = [
28
+ 'feature', // New features need planning
29
+ 'spec', // Specs are planning by definition
30
+ 'design', // Architecture needs planning
31
+ 'refactor', // Refactoring needs impact analysis
32
+ 'migrate' // Migrations are high-risk
33
+ ]
34
+
35
+ /**
36
+ * Commands that are destructive and need approval
37
+ */
38
+ const DESTRUCTIVE_COMMANDS = [
39
+ 'ship', // Commits and pushes
40
+ 'cleanup', // Deletes files/code
41
+ 'git', // Git operations
42
+ 'migrate' // Database/schema changes
43
+ ]
44
+
45
+ /**
46
+ * Read-only tools allowed in planning mode
47
+ */
48
+ const PLANNING_TOOLS = [
49
+ 'Read',
50
+ 'Glob',
51
+ 'Grep',
52
+ 'GetTimestamp',
53
+ 'GetDate',
54
+ 'GetDateTime'
55
+ ]
56
+
57
+ /**
58
+ * Plan status enum
59
+ */
60
+ const PLAN_STATUS = {
61
+ GATHERING: 'gathering', // Collecting information
62
+ ANALYZING: 'analyzing', // Understanding context
63
+ PROPOSING: 'proposing', // Generating plan
64
+ PENDING_APPROVAL: 'pending', // Waiting for user
65
+ APPROVED: 'approved', // User approved
66
+ REJECTED: 'rejected', // User rejected
67
+ EXECUTING: 'executing', // Running the plan
68
+ COMPLETED: 'completed', // Done
69
+ ABORTED: 'aborted' // User stopped mid-execution
70
+ }
71
+
72
+ class PlanMode {
73
+ constructor() {
74
+ this.activePlans = new Map() // projectId -> plan state
75
+ }
76
+
77
+ /**
78
+ * Check if command requires planning mode
79
+ * @param {string} commandName
80
+ * @returns {boolean}
81
+ */
82
+ requiresPlanning(commandName) {
83
+ return PLAN_REQUIRED_COMMANDS.includes(commandName)
84
+ }
85
+
86
+ /**
87
+ * Check if command is destructive and needs approval
88
+ * @param {string} commandName
89
+ * @returns {boolean}
90
+ */
91
+ isDestructive(commandName) {
92
+ return DESTRUCTIVE_COMMANDS.includes(commandName)
93
+ }
94
+
95
+ /**
96
+ * Check if tool is allowed in planning mode
97
+ * @param {string} toolName
98
+ * @returns {boolean}
99
+ */
100
+ isToolAllowedInPlanning(toolName) {
101
+ return PLANNING_TOOLS.includes(toolName)
102
+ }
103
+
104
+ /**
105
+ * Get allowed tools for current mode
106
+ * @param {boolean} inPlanningMode
107
+ * @param {string[]} templateTools - Tools from template
108
+ * @returns {string[]}
109
+ */
110
+ getAllowedTools(inPlanningMode, templateTools) {
111
+ if (!inPlanningMode) {
112
+ return templateTools
113
+ }
114
+ // In planning mode, only allow read-only tools
115
+ return templateTools.filter(tool => PLANNING_TOOLS.includes(tool))
116
+ }
117
+
118
+ /**
119
+ * Start planning mode for a command
120
+ * @param {string} projectId
121
+ * @param {string} commandName
122
+ * @param {Object} params
123
+ * @returns {Object} Plan state
124
+ */
125
+ startPlanning(projectId, commandName, params) {
126
+ const plan = {
127
+ id: `plan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
128
+ projectId,
129
+ command: commandName,
130
+ params,
131
+ status: PLAN_STATUS.GATHERING,
132
+ startedAt: new Date().toISOString(),
133
+ gatheredInfo: [],
134
+ analysis: null,
135
+ proposedPlan: null,
136
+ userFeedback: null,
137
+ approvedAt: null,
138
+ executionStartedAt: null,
139
+ completedAt: null,
140
+ steps: [],
141
+ currentStep: 0
142
+ }
143
+
144
+ this.activePlans.set(projectId, plan)
145
+ return plan
146
+ }
147
+
148
+ /**
149
+ * Get active plan for project
150
+ * @param {string} projectId
151
+ * @returns {Object|null}
152
+ */
153
+ getActivePlan(projectId) {
154
+ return this.activePlans.get(projectId) || null
155
+ }
156
+
157
+ /**
158
+ * Check if project is in planning mode
159
+ * @param {string} projectId
160
+ * @returns {boolean}
161
+ */
162
+ isInPlanningMode(projectId) {
163
+ const plan = this.getActivePlan(projectId)
164
+ if (!plan) return false
165
+ return [
166
+ PLAN_STATUS.GATHERING,
167
+ PLAN_STATUS.ANALYZING,
168
+ PLAN_STATUS.PROPOSING,
169
+ PLAN_STATUS.PENDING_APPROVAL
170
+ ].includes(plan.status)
171
+ }
172
+
173
+ /**
174
+ * Record gathered information
175
+ * @param {string} projectId
176
+ * @param {Object} info - { type, source, data }
177
+ */
178
+ recordGatheredInfo(projectId, info) {
179
+ const plan = this.getActivePlan(projectId)
180
+ if (!plan) return
181
+
182
+ plan.gatheredInfo.push({
183
+ ...info,
184
+ gatheredAt: new Date().toISOString()
185
+ })
186
+ }
187
+
188
+ /**
189
+ * Update plan status
190
+ * @param {string} projectId
191
+ * @param {string} status
192
+ */
193
+ updateStatus(projectId, status) {
194
+ const plan = this.getActivePlan(projectId)
195
+ if (!plan) return
196
+
197
+ plan.status = status
198
+
199
+ // Track timestamps for key transitions
200
+ if (status === PLAN_STATUS.APPROVED) {
201
+ plan.approvedAt = new Date().toISOString()
202
+ } else if (status === PLAN_STATUS.EXECUTING) {
203
+ plan.executionStartedAt = new Date().toISOString()
204
+ } else if (status === PLAN_STATUS.COMPLETED || status === PLAN_STATUS.ABORTED) {
205
+ plan.completedAt = new Date().toISOString()
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Set analysis results
211
+ * @param {string} projectId
212
+ * @param {Object} analysis
213
+ */
214
+ setAnalysis(projectId, analysis) {
215
+ const plan = this.getActivePlan(projectId)
216
+ if (!plan) return
217
+
218
+ plan.analysis = analysis
219
+ plan.status = PLAN_STATUS.ANALYZING
220
+ }
221
+
222
+ /**
223
+ * Propose a plan for user approval
224
+ * @param {string} projectId
225
+ * @param {Object} proposedPlan
226
+ * @returns {Object} Formatted plan for display
227
+ */
228
+ proposePlan(projectId, proposedPlan) {
229
+ const plan = this.getActivePlan(projectId)
230
+ if (!plan) return null
231
+
232
+ plan.proposedPlan = proposedPlan
233
+ plan.status = PLAN_STATUS.PENDING_APPROVAL
234
+
235
+ return this.formatPlanForApproval(plan)
236
+ }
237
+
238
+ /**
239
+ * Format plan for user approval display
240
+ * @param {Object} plan
241
+ * @returns {Object}
242
+ */
243
+ formatPlanForApproval(plan) {
244
+ const proposed = plan.proposedPlan
245
+
246
+ return {
247
+ summary: proposed.summary || `Plan for: ${plan.command}`,
248
+ approach: proposed.approach,
249
+ steps: proposed.steps || [],
250
+ risks: proposed.risks || [],
251
+ alternatives: proposed.alternatives || [],
252
+ estimatedTime: proposed.estimatedTime,
253
+ affectedFiles: proposed.affectedFiles || [],
254
+ requiresConfirmation: true,
255
+ planId: plan.id
256
+ }
257
+ }
258
+
259
+ /**
260
+ * User approves the plan
261
+ * @param {string} projectId
262
+ * @param {string} feedback - Optional user feedback
263
+ * @returns {Object} Approved plan ready for execution
264
+ */
265
+ approvePlan(projectId, feedback = null) {
266
+ const plan = this.getActivePlan(projectId)
267
+ if (!plan || plan.status !== PLAN_STATUS.PENDING_APPROVAL) {
268
+ return null
269
+ }
270
+
271
+ plan.userFeedback = feedback
272
+ plan.status = PLAN_STATUS.APPROVED
273
+ plan.approvedAt = new Date().toISOString()
274
+
275
+ // Convert proposed plan to executable steps
276
+ plan.steps = (plan.proposedPlan.steps || []).map((step, index) => ({
277
+ index,
278
+ description: step.description || step,
279
+ status: 'pending',
280
+ tool: step.tool,
281
+ args: step.args
282
+ }))
283
+
284
+ return {
285
+ approved: true,
286
+ planId: plan.id,
287
+ steps: plan.steps,
288
+ message: `Plan approved. ${plan.steps.length} steps to execute.`
289
+ }
290
+ }
291
+
292
+ /**
293
+ * User rejects the plan
294
+ * @param {string} projectId
295
+ * @param {string} reason
296
+ * @returns {Object}
297
+ */
298
+ rejectPlan(projectId, reason = null) {
299
+ const plan = this.getActivePlan(projectId)
300
+ if (!plan) return null
301
+
302
+ plan.status = PLAN_STATUS.REJECTED
303
+ plan.userFeedback = reason
304
+ plan.completedAt = new Date().toISOString()
305
+
306
+ // Clear active plan
307
+ this.activePlans.delete(projectId)
308
+
309
+ return {
310
+ rejected: true,
311
+ planId: plan.id,
312
+ reason,
313
+ message: 'Plan rejected. No changes made.'
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Start executing approved plan
319
+ * @param {string} projectId
320
+ * @returns {Object} First step to execute
321
+ */
322
+ startExecution(projectId) {
323
+ const plan = this.getActivePlan(projectId)
324
+ if (!plan || plan.status !== PLAN_STATUS.APPROVED) {
325
+ return null
326
+ }
327
+
328
+ plan.status = PLAN_STATUS.EXECUTING
329
+ plan.executionStartedAt = new Date().toISOString()
330
+ plan.currentStep = 0
331
+
332
+ return this.getNextStep(projectId)
333
+ }
334
+
335
+ /**
336
+ * Get next step to execute
337
+ * @param {string} projectId
338
+ * @returns {Object|null}
339
+ */
340
+ getNextStep(projectId) {
341
+ const plan = this.getActivePlan(projectId)
342
+ if (!plan || plan.status !== PLAN_STATUS.EXECUTING) {
343
+ return null
344
+ }
345
+
346
+ const step = plan.steps[plan.currentStep]
347
+ if (!step) {
348
+ // All steps complete
349
+ this.completePlan(projectId)
350
+ return null
351
+ }
352
+
353
+ return {
354
+ stepNumber: plan.currentStep + 1,
355
+ totalSteps: plan.steps.length,
356
+ step,
357
+ progress: Math.round((plan.currentStep / plan.steps.length) * 100)
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Mark current step as complete
363
+ * @param {string} projectId
364
+ * @param {Object} result - Step execution result
365
+ * @returns {Object} Next step or completion status
366
+ */
367
+ completeStep(projectId, result = {}) {
368
+ const plan = this.getActivePlan(projectId)
369
+ if (!plan || plan.status !== PLAN_STATUS.EXECUTING) {
370
+ return null
371
+ }
372
+
373
+ // Update current step
374
+ plan.steps[plan.currentStep].status = 'completed'
375
+ plan.steps[plan.currentStep].result = result
376
+ plan.steps[plan.currentStep].completedAt = new Date().toISOString()
377
+
378
+ // Move to next step
379
+ plan.currentStep++
380
+
381
+ return this.getNextStep(projectId)
382
+ }
383
+
384
+ /**
385
+ * Mark step as failed
386
+ * @param {string} projectId
387
+ * @param {string} error
388
+ * @returns {Object}
389
+ */
390
+ failStep(projectId, error) {
391
+ const plan = this.getActivePlan(projectId)
392
+ if (!plan) return null
393
+
394
+ plan.steps[plan.currentStep].status = 'failed'
395
+ plan.steps[plan.currentStep].error = error
396
+
397
+ return {
398
+ failed: true,
399
+ step: plan.currentStep + 1,
400
+ error,
401
+ options: ['retry', 'skip', 'abort']
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Complete the plan
407
+ * @param {string} projectId
408
+ * @returns {Object} Completion summary
409
+ */
410
+ completePlan(projectId) {
411
+ const plan = this.getActivePlan(projectId)
412
+ if (!plan) return null
413
+
414
+ plan.status = PLAN_STATUS.COMPLETED
415
+ plan.completedAt = new Date().toISOString()
416
+
417
+ const summary = {
418
+ planId: plan.id,
419
+ command: plan.command,
420
+ totalSteps: plan.steps.length,
421
+ completedSteps: plan.steps.filter(s => s.status === 'completed').length,
422
+ failedSteps: plan.steps.filter(s => s.status === 'failed').length,
423
+ duration: this._calculateDuration(plan.executionStartedAt, plan.completedAt)
424
+ }
425
+
426
+ // Clear active plan
427
+ this.activePlans.delete(projectId)
428
+
429
+ return summary
430
+ }
431
+
432
+ /**
433
+ * Abort plan execution
434
+ * @param {string} projectId
435
+ * @param {string} reason
436
+ * @returns {Object}
437
+ */
438
+ abortPlan(projectId, reason = 'User requested') {
439
+ const plan = this.getActivePlan(projectId)
440
+ if (!plan) return null
441
+
442
+ plan.status = PLAN_STATUS.ABORTED
443
+ plan.completedAt = new Date().toISOString()
444
+ plan.abortReason = reason
445
+
446
+ const summary = {
447
+ aborted: true,
448
+ planId: plan.id,
449
+ reason,
450
+ completedSteps: plan.steps.filter(s => s.status === 'completed').length,
451
+ totalSteps: plan.steps.length
452
+ }
453
+
454
+ // Clear active plan
455
+ this.activePlans.delete(projectId)
456
+
457
+ return summary
458
+ }
459
+
460
+ /**
461
+ * Generate approval prompt for destructive commands
462
+ * @param {string} commandName
463
+ * @param {Object} context
464
+ * @returns {Object}
465
+ */
466
+ generateApprovalPrompt(commandName, context) {
467
+ const prompts = {
468
+ ship: {
469
+ title: 'Ship Confirmation',
470
+ message: 'Ready to commit and push changes?',
471
+ details: [
472
+ `Branch: ${context.branch || 'current'}`,
473
+ `Files: ${context.changedFiles?.length || 0} changed`,
474
+ `Commit: "${context.commitMessage || 'No message'}"`
475
+ ],
476
+ options: [
477
+ { key: 'y', label: 'Yes, ship it', action: 'approve' },
478
+ { key: 'n', label: 'No, cancel', action: 'reject' },
479
+ { key: 'e', label: 'Edit message', action: 'edit' }
480
+ ]
481
+ },
482
+ cleanup: {
483
+ title: 'Cleanup Confirmation',
484
+ message: 'This will delete files/code. Continue?',
485
+ details: [
486
+ `Files to delete: ${context.filesToDelete?.length || 0}`,
487
+ `Code to remove: ${context.linesOfCode || 0} lines`
488
+ ],
489
+ options: [
490
+ { key: 'y', label: 'Yes, cleanup', action: 'approve' },
491
+ { key: 'n', label: 'No, cancel', action: 'reject' },
492
+ { key: 'l', label: 'List files first', action: 'list' }
493
+ ]
494
+ },
495
+ git: {
496
+ title: 'Git Operation Confirmation',
497
+ message: `Execute: ${context.operation || 'git operation'}?`,
498
+ details: context.warnings || [],
499
+ options: [
500
+ { key: 'y', label: 'Yes, execute', action: 'approve' },
501
+ { key: 'n', label: 'No, cancel', action: 'reject' }
502
+ ]
503
+ }
504
+ }
505
+
506
+ return prompts[commandName] || {
507
+ title: 'Confirmation Required',
508
+ message: `Execute ${commandName}?`,
509
+ options: [
510
+ { key: 'y', label: 'Yes', action: 'approve' },
511
+ { key: 'n', label: 'No', action: 'reject' }
512
+ ]
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Format plan status for display
518
+ * @param {string} projectId
519
+ * @returns {string}
520
+ */
521
+ formatStatus(projectId) {
522
+ const plan = this.getActivePlan(projectId)
523
+ if (!plan) return 'No active plan'
524
+
525
+ const statusEmoji = {
526
+ [PLAN_STATUS.GATHERING]: '🔍',
527
+ [PLAN_STATUS.ANALYZING]: '🧠',
528
+ [PLAN_STATUS.PROPOSING]: '📝',
529
+ [PLAN_STATUS.PENDING_APPROVAL]: '⏳',
530
+ [PLAN_STATUS.APPROVED]: '✅',
531
+ [PLAN_STATUS.EXECUTING]: '⚡',
532
+ [PLAN_STATUS.COMPLETED]: '🎉',
533
+ [PLAN_STATUS.REJECTED]: '❌',
534
+ [PLAN_STATUS.ABORTED]: '🛑'
535
+ }
536
+
537
+ const lines = [
538
+ `${statusEmoji[plan.status] || '📋'} Plan: ${plan.command}`,
539
+ `Status: ${plan.status}`
540
+ ]
541
+
542
+ if (plan.status === PLAN_STATUS.EXECUTING) {
543
+ const progress = Math.round((plan.currentStep / plan.steps.length) * 100)
544
+ lines.push(`Progress: ${plan.currentStep}/${plan.steps.length} (${progress}%)`)
545
+ }
546
+
547
+ return lines.join('\n')
548
+ }
549
+
550
+ /**
551
+ * Calculate duration between two timestamps
552
+ * @private
553
+ */
554
+ _calculateDuration(start, end) {
555
+ if (!start || !end) return null
556
+ const ms = new Date(end) - new Date(start)
557
+ const seconds = Math.floor(ms / 1000)
558
+ const minutes = Math.floor(seconds / 60)
559
+ const hours = Math.floor(minutes / 60)
560
+
561
+ if (hours > 0) return `${hours}h ${minutes % 60}m`
562
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`
563
+ return `${seconds}s`
564
+ }
565
+ }
566
+
567
+ // Export singleton and constants
568
+ module.exports = new PlanMode()
569
+ module.exports.PLAN_STATUS = PLAN_STATUS
570
+ module.exports.PLAN_REQUIRED_COMMANDS = PLAN_REQUIRED_COMMANDS
571
+ module.exports.DESTRUCTIVE_COMMANDS = DESTRUCTIVE_COMMANDS
572
+ module.exports.PLANNING_TOOLS = PLANNING_TOOLS
@@ -2,14 +2,23 @@
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
10
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
11
20
  */
12
- build(template, context, state, agent = null) {
21
+ build(template, context, state, agent = null, learnedPatterns = null, thinkBlock = null, relevantMemories = null, planInfo = null) {
13
22
  const parts = []
14
23
 
15
24
  // Agent assignment (if applicable)
@@ -84,6 +93,72 @@ class PromptBuilder {
84
93
  // Enforcement (Strict Mode)
85
94
  parts.push(this.buildEnforcement());
86
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
+
87
162
  // Simple execution directive
88
163
  parts.push('\nEXECUTE: Follow flow. Use tools. Decide.\n')
89
164