prjct-cli 0.11.4 → 0.11.5

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 (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/serve.js +22 -6
  3. package/core/__tests__/utils/date-helper.test.js +416 -0
  4. package/core/agentic/agent-router.js +30 -18
  5. package/core/agentic/command-executor.js +20 -24
  6. package/core/agentic/context-builder.js +7 -8
  7. package/core/agentic/memory-system.js +14 -19
  8. package/core/agentic/prompt-builder.js +41 -27
  9. package/core/agentic/template-loader.js +8 -2
  10. package/core/infrastructure/agent-detector.js +7 -4
  11. package/core/infrastructure/migrator.js +10 -13
  12. package/core/infrastructure/session-manager.js +10 -10
  13. package/package.json +1 -1
  14. package/packages/web/app/project/[id]/stats/page.tsx +102 -343
  15. package/packages/web/components/stats/ActivityTimeline.tsx +201 -0
  16. package/packages/web/components/stats/AgentsCard.tsx +56 -0
  17. package/packages/web/components/stats/BentoCard.tsx +88 -0
  18. package/packages/web/components/stats/BentoGrid.tsx +22 -0
  19. package/packages/web/components/stats/EmptyState.tsx +67 -0
  20. package/packages/web/components/stats/HeroSection.tsx +172 -0
  21. package/packages/web/components/stats/IdeasCard.tsx +59 -0
  22. package/packages/web/components/stats/NowCard.tsx +71 -0
  23. package/packages/web/components/stats/ProgressRing.tsx +74 -0
  24. package/packages/web/components/stats/QueueCard.tsx +58 -0
  25. package/packages/web/components/stats/RoadmapCard.tsx +97 -0
  26. package/packages/web/components/stats/ShipsCard.tsx +70 -0
  27. package/packages/web/components/stats/SparklineChart.tsx +44 -0
  28. package/packages/web/components/stats/StreakCard.tsx +59 -0
  29. package/packages/web/components/stats/VelocityCard.tsx +60 -0
  30. package/packages/web/components/stats/index.ts +17 -0
  31. package/packages/web/components/ui/tooltip.tsx +2 -2
  32. package/packages/web/next-env.d.ts +1 -1
  33. package/packages/web/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.5] - 2025-12-09
4
+
5
+ ### Fixed - Production Server Mode
6
+
7
+ Critical bug fix: `prjct serve` now runs in production mode instead of development mode.
8
+
9
+ - **Server Mode** - Changed from dev to production
10
+ - Default port: 3000 → 9472 (avoids conflicts)
11
+ - Mode: `npm run dev` → `npm run start:prod` with `NODE_ENV=production`
12
+ - Auto-build on first run if `.next/` doesn't exist
13
+ - All features now work correctly (were broken in dev mode)
14
+
15
+ - **packages/web/package.json** - Added `start:prod` script
16
+
3
17
  ## [0.11.0] - 2025-12-08
4
18
 
5
19
  ### Added - Web Application & Server Components
package/bin/serve.js CHANGED
@@ -20,7 +20,7 @@ const webDir = path.join(packagesDir, 'web')
20
20
  // Parse arguments
21
21
  const args = process.argv.slice(2)
22
22
  const portArg = args.find((a) => a.startsWith('--port='))
23
- const port = portArg ? portArg.split('=')[1] : '3000'
23
+ const port = portArg ? portArg.split('=')[1] : '9472'
24
24
 
25
25
  // Check if web package exists
26
26
  if (!fs.existsSync(webDir)) {
@@ -144,21 +144,37 @@ console.log(`
144
144
  ║ ║
145
145
  ║ ⚡ prjct - Developer Momentum ║
146
146
  ║ ║
147
- Starting web server...
147
+ Production server ready
148
148
  ║ ║
149
- ║ Web: http://localhost:${port}
149
+ ║ Web: http://localhost:${port}
150
150
  ║ ║
151
151
  ║ Using your Claude subscription - $0 API costs ║
152
152
  ║ ║
153
153
  ╚═══════════════════════════════════════════════════════════╝
154
154
  `)
155
155
 
156
- // Start web dev server
157
- const web = spawn('npm', ['run', 'dev'], {
156
+ // Build for production if needed (first run)
157
+ const nextDir = path.join(webDir, '.next')
158
+ if (!fs.existsSync(nextDir)) {
159
+ console.log('🔨 Building for production (first run)...\n')
160
+ const buildResult = spawnSync('npm', ['run', 'build'], {
161
+ cwd: webDir,
162
+ stdio: 'inherit',
163
+ shell: true,
164
+ })
165
+ if (buildResult.status !== 0) {
166
+ console.error('❌ Build failed')
167
+ process.exit(1)
168
+ }
169
+ console.log('✅ Build complete!\n')
170
+ }
171
+
172
+ // Start web server in production mode
173
+ const web = spawn('npm', ['run', 'start:prod'], {
158
174
  cwd: webDir,
159
175
  stdio: 'inherit',
160
176
  shell: true,
161
- env: { ...process.env, PORT: port },
177
+ env: { ...process.env, PORT: port, NODE_ENV: 'production' },
162
178
  })
163
179
 
164
180
  // Open browser after a short delay
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Date Helper Tests
3
+ * Tests for centralized date operations and formatting
4
+ */
5
+
6
+ const {
7
+ formatDate,
8
+ formatMonth,
9
+ getTodayKey,
10
+ getDateKey,
11
+ getYearMonthDay,
12
+ parseDate,
13
+ getTimestamp,
14
+ getDaysAgo,
15
+ getDaysFromNow,
16
+ getDateRange,
17
+ isToday,
18
+ isWithinLastDays,
19
+ formatDuration,
20
+ calculateDuration,
21
+ getStartOfDay,
22
+ getEndOfDay,
23
+ } = require('../../utils/date-helper')
24
+
25
+ describe('DateHelper', () => {
26
+ describe('formatDate', () => {
27
+ it('should format date to YYYY-MM-DD', () => {
28
+ const date = new Date(2025, 9, 4) // Oct 4, 2025
29
+ expect(formatDate(date)).toBe('2025-10-04')
30
+ })
31
+
32
+ it('should pad single digit months', () => {
33
+ const date = new Date(2025, 0, 15) // Jan 15, 2025
34
+ expect(formatDate(date)).toBe('2025-01-15')
35
+ })
36
+
37
+ it('should pad single digit days', () => {
38
+ const date = new Date(2025, 11, 5) // Dec 5, 2025
39
+ expect(formatDate(date)).toBe('2025-12-05')
40
+ })
41
+
42
+ it('should handle year boundaries', () => {
43
+ const date = new Date(2024, 11, 31) // Dec 31, 2024
44
+ expect(formatDate(date)).toBe('2024-12-31')
45
+ })
46
+ })
47
+
48
+ describe('formatMonth', () => {
49
+ it('should format date to YYYY-MM', () => {
50
+ const date = new Date(2025, 9, 15) // Oct 15, 2025
51
+ expect(formatMonth(date)).toBe('2025-10')
52
+ })
53
+
54
+ it('should pad single digit months', () => {
55
+ const date = new Date(2025, 2, 1) // Mar 1, 2025
56
+ expect(formatMonth(date)).toBe('2025-03')
57
+ })
58
+
59
+ it('should handle December', () => {
60
+ const date = new Date(2025, 11, 25) // Dec 25, 2025
61
+ expect(formatMonth(date)).toBe('2025-12')
62
+ })
63
+ })
64
+
65
+ describe('getTodayKey', () => {
66
+ it('should return today in YYYY-MM-DD format', () => {
67
+ vi.useFakeTimers()
68
+ vi.setSystemTime(new Date(2025, 5, 15)) // June 15, 2025
69
+
70
+ expect(getTodayKey()).toBe('2025-06-15')
71
+
72
+ vi.useRealTimers()
73
+ })
74
+ })
75
+
76
+ describe('getDateKey', () => {
77
+ it('should return date in YYYY-MM-DD format (alias for formatDate)', () => {
78
+ const date = new Date(2025, 7, 20) // Aug 20, 2025
79
+ expect(getDateKey(date)).toBe('2025-08-20')
80
+ })
81
+ })
82
+
83
+ describe('getYearMonthDay', () => {
84
+ it('should return separate year, month, day strings', () => {
85
+ const date = new Date(2025, 9, 4) // Oct 4, 2025
86
+ const result = getYearMonthDay(date)
87
+
88
+ expect(result.year).toBe('2025')
89
+ expect(result.month).toBe('10')
90
+ expect(result.day).toBe('04')
91
+ })
92
+
93
+ it('should pad month values', () => {
94
+ const date = new Date(2025, 0, 15) // Jan 15, 2025
95
+ const result = getYearMonthDay(date)
96
+
97
+ expect(result.month).toBe('01')
98
+ })
99
+
100
+ it('should pad day values', () => {
101
+ const date = new Date(2025, 5, 7) // June 7, 2025
102
+ const result = getYearMonthDay(date)
103
+
104
+ expect(result.day).toBe('07')
105
+ })
106
+ })
107
+
108
+ describe('parseDate', () => {
109
+ it('should parse YYYY-MM-DD format', () => {
110
+ const result = parseDate('2025-10-04')
111
+ expect(result.getFullYear()).toBe(2025)
112
+ expect(result.getMonth()).toBe(9) // 0-indexed
113
+ expect(result.getDate()).toBe(4)
114
+ })
115
+
116
+ it('should parse ISO strings', () => {
117
+ const result = parseDate('2025-10-04T14:30:00.000Z')
118
+ expect(result.getFullYear()).toBe(2025)
119
+ expect(result.getMonth()).toBe(9)
120
+ })
121
+ })
122
+
123
+ describe('getTimestamp', () => {
124
+ it('should return ISO timestamp', () => {
125
+ vi.useFakeTimers()
126
+ vi.setSystemTime(new Date('2025-10-04T14:30:00.000Z'))
127
+
128
+ expect(getTimestamp()).toBe('2025-10-04T14:30:00.000Z')
129
+
130
+ vi.useRealTimers()
131
+ })
132
+
133
+ it('should include milliseconds', () => {
134
+ const timestamp = getTimestamp()
135
+ expect(timestamp).toMatch(/\.\d{3}Z$/)
136
+ })
137
+ })
138
+
139
+ describe('getDaysAgo', () => {
140
+ beforeEach(() => {
141
+ vi.useFakeTimers()
142
+ vi.setSystemTime(new Date(2025, 9, 15)) // Oct 15, 2025
143
+ })
144
+
145
+ afterEach(() => {
146
+ vi.useRealTimers()
147
+ })
148
+
149
+ it('should calculate past dates correctly', () => {
150
+ const result = getDaysAgo(5)
151
+ expect(formatDate(result)).toBe('2025-10-10')
152
+ })
153
+
154
+ it('should handle month boundaries', () => {
155
+ const result = getDaysAgo(20)
156
+ expect(formatDate(result)).toBe('2025-09-25')
157
+ })
158
+
159
+ it('should return today for 0 days ago', () => {
160
+ const result = getDaysAgo(0)
161
+ expect(formatDate(result)).toBe('2025-10-15')
162
+ })
163
+ })
164
+
165
+ describe('getDaysFromNow', () => {
166
+ beforeEach(() => {
167
+ vi.useFakeTimers()
168
+ vi.setSystemTime(new Date(2025, 9, 15)) // Oct 15, 2025
169
+ })
170
+
171
+ afterEach(() => {
172
+ vi.useRealTimers()
173
+ })
174
+
175
+ it('should calculate future dates correctly', () => {
176
+ const result = getDaysFromNow(5)
177
+ expect(formatDate(result)).toBe('2025-10-20')
178
+ })
179
+
180
+ it('should handle month boundaries', () => {
181
+ const result = getDaysFromNow(20)
182
+ expect(formatDate(result)).toBe('2025-11-04')
183
+ })
184
+
185
+ it('should return today for 0 days from now', () => {
186
+ const result = getDaysFromNow(0)
187
+ expect(formatDate(result)).toBe('2025-10-15')
188
+ })
189
+ })
190
+
191
+ describe('getDateRange', () => {
192
+ it('should return array of dates in range', () => {
193
+ const from = new Date(2025, 9, 1) // Oct 1
194
+ const to = new Date(2025, 9, 5) // Oct 5
195
+
196
+ const result = getDateRange(from, to)
197
+
198
+ expect(result.length).toBe(5)
199
+ expect(formatDate(result[0])).toBe('2025-10-01')
200
+ expect(formatDate(result[4])).toBe('2025-10-05')
201
+ })
202
+
203
+ it('should include start and end dates', () => {
204
+ const from = new Date(2025, 9, 10)
205
+ const to = new Date(2025, 9, 12)
206
+
207
+ const result = getDateRange(from, to)
208
+
209
+ expect(formatDate(result[0])).toBe('2025-10-10')
210
+ expect(formatDate(result[result.length - 1])).toBe('2025-10-12')
211
+ })
212
+
213
+ it('should return single date if from equals to', () => {
214
+ const date = new Date(2025, 9, 15)
215
+ const result = getDateRange(date, date)
216
+
217
+ expect(result.length).toBe(1)
218
+ expect(formatDate(result[0])).toBe('2025-10-15')
219
+ })
220
+
221
+ it('should handle month boundaries', () => {
222
+ const from = new Date(2025, 9, 30) // Oct 30
223
+ const to = new Date(2025, 10, 2) // Nov 2
224
+
225
+ const result = getDateRange(from, to)
226
+
227
+ expect(result.length).toBe(4)
228
+ expect(formatDate(result[0])).toBe('2025-10-30')
229
+ expect(formatDate(result[3])).toBe('2025-11-02')
230
+ })
231
+
232
+ it('should return empty array if from is after to', () => {
233
+ const from = new Date(2025, 9, 15)
234
+ const to = new Date(2025, 9, 10)
235
+
236
+ const result = getDateRange(from, to)
237
+
238
+ expect(result.length).toBe(0)
239
+ })
240
+ })
241
+
242
+ describe('isToday', () => {
243
+ beforeEach(() => {
244
+ vi.useFakeTimers()
245
+ vi.setSystemTime(new Date(2025, 9, 15)) // Oct 15, 2025
246
+ })
247
+
248
+ afterEach(() => {
249
+ vi.useRealTimers()
250
+ })
251
+
252
+ it('should return true for today', () => {
253
+ const today = new Date(2025, 9, 15)
254
+ expect(isToday(today)).toBe(true)
255
+ })
256
+
257
+ it('should return false for yesterday', () => {
258
+ const yesterday = new Date(2025, 9, 14)
259
+ expect(isToday(yesterday)).toBe(false)
260
+ })
261
+
262
+ it('should return false for tomorrow', () => {
263
+ const tomorrow = new Date(2025, 9, 16)
264
+ expect(isToday(tomorrow)).toBe(false)
265
+ })
266
+
267
+ it('should ignore time component', () => {
268
+ const todayLate = new Date(2025, 9, 15, 23, 59, 59)
269
+ expect(isToday(todayLate)).toBe(true)
270
+ })
271
+ })
272
+
273
+ describe('isWithinLastDays', () => {
274
+ beforeEach(() => {
275
+ vi.useFakeTimers()
276
+ vi.setSystemTime(new Date(2025, 9, 15, 12, 0, 0)) // Oct 15, 2025 at noon
277
+ })
278
+
279
+ afterEach(() => {
280
+ vi.useRealTimers()
281
+ })
282
+
283
+ it('should return true for dates within range', () => {
284
+ const recent = new Date(2025, 9, 12) // 3 days ago
285
+ expect(isWithinLastDays(recent, 7)).toBe(true)
286
+ })
287
+
288
+ it('should return false for dates outside range', () => {
289
+ const old = new Date(2025, 9, 1) // 14 days ago
290
+ expect(isWithinLastDays(old, 7)).toBe(false)
291
+ })
292
+
293
+ it('should include today', () => {
294
+ const today = new Date(2025, 9, 15)
295
+ expect(isWithinLastDays(today, 7)).toBe(true)
296
+ })
297
+
298
+ it('should include boundary date', () => {
299
+ const boundary = new Date(2025, 9, 8) // exactly 7 days ago
300
+ expect(isWithinLastDays(boundary, 7)).toBe(true)
301
+ })
302
+ })
303
+
304
+ describe('formatDuration', () => {
305
+ it('should format seconds', () => {
306
+ expect(formatDuration(5000)).toBe('5s')
307
+ expect(formatDuration(45000)).toBe('45s')
308
+ })
309
+
310
+ it('should format minutes', () => {
311
+ expect(formatDuration(60000)).toBe('1m')
312
+ expect(formatDuration(120000)).toBe('2m')
313
+ expect(formatDuration(90000)).toBe('1m') // 1.5 min rounds down
314
+ })
315
+
316
+ it('should format hours and minutes', () => {
317
+ expect(formatDuration(3600000)).toBe('1h 0m')
318
+ expect(formatDuration(5400000)).toBe('1h 30m')
319
+ expect(formatDuration(7200000)).toBe('2h 0m')
320
+ })
321
+
322
+ it('should format days and hours', () => {
323
+ expect(formatDuration(86400000)).toBe('1d 0h')
324
+ expect(formatDuration(90000000)).toBe('1d 1h')
325
+ expect(formatDuration(172800000)).toBe('2d 0h')
326
+ })
327
+
328
+ it('should handle zero', () => {
329
+ expect(formatDuration(0)).toBe('0s')
330
+ })
331
+ })
332
+
333
+ describe('calculateDuration', () => {
334
+ it('should calculate duration between two dates', () => {
335
+ const start = new Date('2025-10-15T10:00:00.000Z')
336
+ const end = new Date('2025-10-15T12:30:00.000Z')
337
+
338
+ expect(calculateDuration(start, end)).toBe('2h 30m')
339
+ })
340
+
341
+ it('should default to now if no end date', () => {
342
+ vi.useFakeTimers()
343
+ vi.setSystemTime(new Date('2025-10-15T12:00:00.000Z'))
344
+
345
+ const start = new Date('2025-10-15T10:00:00.000Z')
346
+ expect(calculateDuration(start)).toBe('2h 0m')
347
+
348
+ vi.useRealTimers()
349
+ })
350
+
351
+ it('should handle short durations', () => {
352
+ const start = new Date('2025-10-15T10:00:00.000Z')
353
+ const end = new Date('2025-10-15T10:00:30.000Z')
354
+
355
+ expect(calculateDuration(start, end)).toBe('30s')
356
+ })
357
+ })
358
+
359
+ describe('getStartOfDay', () => {
360
+ it('should set time to 00:00:00.000', () => {
361
+ const date = new Date(2025, 9, 15, 14, 30, 45, 500)
362
+ const result = getStartOfDay(date)
363
+
364
+ expect(result.getHours()).toBe(0)
365
+ expect(result.getMinutes()).toBe(0)
366
+ expect(result.getSeconds()).toBe(0)
367
+ expect(result.getMilliseconds()).toBe(0)
368
+ })
369
+
370
+ it('should preserve the date', () => {
371
+ const date = new Date(2025, 9, 15, 23, 59, 59)
372
+ const result = getStartOfDay(date)
373
+
374
+ expect(result.getFullYear()).toBe(2025)
375
+ expect(result.getMonth()).toBe(9)
376
+ expect(result.getDate()).toBe(15)
377
+ })
378
+
379
+ it('should not mutate original date', () => {
380
+ const original = new Date(2025, 9, 15, 14, 30)
381
+ getStartOfDay(original)
382
+
383
+ expect(original.getHours()).toBe(14)
384
+ expect(original.getMinutes()).toBe(30)
385
+ })
386
+ })
387
+
388
+ describe('getEndOfDay', () => {
389
+ it('should set time to 23:59:59.999', () => {
390
+ const date = new Date(2025, 9, 15, 10, 0, 0, 0)
391
+ const result = getEndOfDay(date)
392
+
393
+ expect(result.getHours()).toBe(23)
394
+ expect(result.getMinutes()).toBe(59)
395
+ expect(result.getSeconds()).toBe(59)
396
+ expect(result.getMilliseconds()).toBe(999)
397
+ })
398
+
399
+ it('should preserve the date', () => {
400
+ const date = new Date(2025, 9, 15, 0, 0, 0)
401
+ const result = getEndOfDay(date)
402
+
403
+ expect(result.getFullYear()).toBe(2025)
404
+ expect(result.getMonth()).toBe(9)
405
+ expect(result.getDate()).toBe(15)
406
+ })
407
+
408
+ it('should not mutate original date', () => {
409
+ const original = new Date(2025, 9, 15, 10, 30)
410
+ getEndOfDay(original)
411
+
412
+ expect(original.getHours()).toBe(10)
413
+ expect(original.getMinutes()).toBe(30)
414
+ })
415
+ })
416
+ })
@@ -1,11 +1,8 @@
1
1
  /**
2
- * Agent Router - Orchestration Only
3
- *
4
- * AGENTIC: All decisions made by Claude via templates/agent-assignment.md
5
- * JS only orchestrates: load agents, build context, delegate to Claude
6
- *
7
- * NO scoring logic, NO matching algorithms, NO hardcoded mappings
2
+ * Agent Router
3
+ * Orchestrates agent loading and context building for Claude delegation.
8
4
  *
5
+ * @module agentic/agent-router
9
6
  * @version 2.0.0
10
7
  */
11
8
 
@@ -14,15 +11,22 @@ const path = require('path')
14
11
  const configManager = require('../infrastructure/config-manager')
15
12
  const pathManager = require('../infrastructure/path-manager')
16
13
 
14
+ /**
15
+ * Routes tasks to specialized agents based on Claude's decisions.
16
+ * Handles agent loading, context building, and usage logging.
17
+ */
17
18
  class AgentRouter {
18
19
  constructor() {
20
+ /** @type {string|null} */
19
21
  this.projectId = null
22
+ /** @type {string|null} */
20
23
  this.agentsPath = null
21
24
  }
22
25
 
23
26
  /**
24
- * Initialize with project context
25
- * ORCHESTRATION: Just sets up paths
27
+ * Initialize router with project context
28
+ *
29
+ * @param {string} projectPath - Path to the project
26
30
  */
27
31
  async initialize(projectPath) {
28
32
  this.projectId = await configManager.getProjectId(projectPath)
@@ -31,7 +35,8 @@ class AgentRouter {
31
35
 
32
36
  /**
33
37
  * Load all available agents from project
34
- * ORCHESTRATION: File I/O only, no logic
38
+ *
39
+ * @returns {Promise<Array<{name: string, content: string}>>} Available agents
35
40
  */
36
41
  async loadAvailableAgents() {
37
42
  try {
@@ -56,8 +61,9 @@ class AgentRouter {
56
61
  }
57
62
 
58
63
  /**
59
- * Get agent names list
60
- * ORCHESTRATION: Simple extraction
64
+ * Get list of available agent names
65
+ *
66
+ * @returns {Promise<string[]>} Agent names
61
67
  */
62
68
  async getAgentNames() {
63
69
  const agents = await this.loadAvailableAgents()
@@ -65,8 +71,10 @@ class AgentRouter {
65
71
  }
66
72
 
67
73
  /**
68
- * Load specific agent by name
69
- * ORCHESTRATION: File I/O only
74
+ * Load a specific agent by name
75
+ *
76
+ * @param {string} name - Agent name (without .md extension)
77
+ * @returns {Promise<{name: string, content: string}|null>} Agent or null
70
78
  */
71
79
  async loadAgent(name) {
72
80
  try {
@@ -79,10 +87,11 @@ class AgentRouter {
79
87
  }
80
88
 
81
89
  /**
82
- * Build context for agent assignment
83
- * ORCHESTRATION: Data gathering only
90
+ * Build context for Claude to decide agent assignment
84
91
  *
85
- * Claude uses this context + templates/agent-assignment.md to decide
92
+ * @param {string|Object} task - Task description or object
93
+ * @param {string} projectPath - Project path
94
+ * @returns {Promise<Object>} Assignment context for Claude
86
95
  */
87
96
  async buildAssignmentContext(task, projectPath) {
88
97
  const agents = await this.getAgentNames()
@@ -98,8 +107,11 @@ class AgentRouter {
98
107
  }
99
108
 
100
109
  /**
101
- * Log agent usage
102
- * ORCHESTRATION: File I/O only
110
+ * Log agent usage to JSONL file
111
+ *
112
+ * @param {string|Object} task - Task description
113
+ * @param {string|Object} agent - Agent used
114
+ * @param {string} projectPath - Project path (unused, kept for API compat)
103
115
  */
104
116
  async logUsage(task, agent, projectPath) {
105
117
  try {
@@ -1,21 +1,9 @@
1
1
  /**
2
2
  * Command Executor
3
- * 100% AGENTIC - Claude decides agent assignment via Task tool
3
+ * Orchestrates command execution with agentic delegation.
4
4
  *
5
- * NO if/else logic for agent selection here.
6
- * Claude reads templates/agentic/agent-routing.md and delegates via Task tool.
7
- *
8
- * JS only:
9
- * - Loads templates
10
- * - Builds context
11
- * - Returns prompt for Claude
12
- *
13
- * Claude:
14
- * - Reads agent-routing.md
15
- * - Decides best agent for task
16
- * - Delegates via Task(subagent_type='general-purpose', prompt='Read: path/to/agent.md...')
17
- *
18
- * Source: Claude Code, Devin, Augment Code patterns
5
+ * @module agentic/command-executor
6
+ * @version 3.4
19
7
  */
20
8
 
21
9
  const fs = require('fs')
@@ -45,14 +33,17 @@ const RUNNING_FILE = path.join(os.homedir(), '.prjct-cli', '.running')
45
33
  // - code-intelligence → Claude Code has native LSP integration
46
34
  // - browser-preview → Claude Code can use Bash directly
47
35
 
36
+ /**
37
+ * Orchestrates prjct command execution.
38
+ * Handles template loading, context building, validation, and agentic delegation.
39
+ */
48
40
  class CommandExecutor {
49
- constructor() {
50
- // 100% AGENTIC: No agent router here
51
- // Claude decides agent assignment via templates and Task tool
52
- }
41
+ constructor() {}
53
42
 
54
43
  /**
55
44
  * Signal that a command is running (for status line)
45
+ *
46
+ * @param {string} commandName - Name of the running command
56
47
  */
57
48
  signalStart(commandName) {
58
49
  try {
@@ -80,7 +71,12 @@ class CommandExecutor {
80
71
  }
81
72
 
82
73
  /**
83
- * Execute command with MANDATORY agent assignment
74
+ * Execute a prjct command with full agentic delegation
75
+ *
76
+ * @param {string} commandName - Command to execute (e.g., 'now', 'ship')
77
+ * @param {Object} params - Command parameters
78
+ * @param {string} projectPath - Path to the project
79
+ * @returns {Promise<Object>} Execution result with prompt, context, helpers
84
80
  */
85
81
  async execute(commandName, params, projectPath) {
86
82
  // Signal start for status line
@@ -378,12 +374,12 @@ class CommandExecutor {
378
374
  }
379
375
 
380
376
  /**
381
- * Simple execution for direct tool access
382
- * Used by legacy commands during migration
377
+ * Simple execution for direct tool access (legacy migration helper)
378
+ *
383
379
  * @param {string} commandName - Command name
384
- * @param {Function} executionFn - Function that uses tools
380
+ * @param {Function} executionFn - Function receiving (tools, context)
385
381
  * @param {string} projectPath - Project path
386
- * @returns {Promise<Object>}
382
+ * @returns {Promise<Object>} Result with success flag
387
383
  */
388
384
  async executeSimple(commandName, executionFn, projectPath) {
389
385
  try {
@@ -1,20 +1,19 @@
1
1
  /**
2
2
  * Context Builder
3
- * Builds project context for Claude to make decisions
4
- * NO if/else logic - just data collection
3
+ * Builds project context for Claude with smart caching.
5
4
  *
6
- * OPTIMIZATION (P0.1): Smart Context Caching
7
- * - Parallel file reads with Promise.all()
8
- * - Session-based caching to avoid redundant reads
9
- * - Selective loading based on command needs
10
- *
11
- * Source: Windsurf, Cursor patterns
5
+ * @module agentic/context-builder
6
+ * @version 0.1
12
7
  */
13
8
 
14
9
  const fs = require('fs').promises
15
10
  const pathManager = require('../infrastructure/path-manager')
16
11
  const configManager = require('../infrastructure/config-manager')
17
12
 
13
+ /**
14
+ * Builds and caches project context for Claude decisions.
15
+ * Features parallel reads, selective loading, and anti-hallucination mtime checks.
16
+ */
18
17
  class ContextBuilder {
19
18
  constructor() {
20
19
  // Session cache - cleared between commands or after timeout