prjct-cli 0.44.1 → 0.45.3

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 (207) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/bin/prjct.ts +131 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +287 -29
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +49 -8
  43. package/core/commands/commands.ts +60 -23
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +14 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -12
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +143 -0
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +170 -329
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -2,28 +2,32 @@
2
2
  * Analysis Commands: analyze, sync, and related helpers
3
3
  */
4
4
 
5
- import path from 'path'
6
-
7
- import type { CommandResult, AnalyzeOptions, ProjectContext } from '../types'
8
- import {
9
- PrjctCommandsBase,
10
- contextBuilder,
11
- toolRegistry,
12
- pathManager,
13
- configManager,
14
- dateHelper
15
- } from './base'
16
- import analyzer from '../domain/analyzer'
5
+ import path from 'node:path'
17
6
  import { generateContext } from '../context/generator'
7
+ import analyzer from '../domain/analyzer'
18
8
  import commandInstaller from '../infrastructure/command-installer'
9
+ import { formatCost } from '../schemas/metrics'
19
10
  import { syncService } from '../services'
11
+ import { metricsStorage } from '../storage/metrics-storage'
12
+ import type { AnalyzeOptions, CommandResult, ProjectContext } from '../types'
20
13
  import { showNextSteps } from '../utils/next-steps'
14
+ import {
15
+ configManager,
16
+ contextBuilder,
17
+ dateHelper,
18
+ PrjctCommandsBase,
19
+ pathManager,
20
+ toolRegistry,
21
+ } from './base'
21
22
 
22
23
  export class AnalysisCommands extends PrjctCommandsBase {
23
24
  /**
24
25
  * /p:analyze - Analyze repository and generate summary
25
26
  */
26
- async analyze(options: AnalyzeOptions = {}, projectPath: string = process.cwd()): Promise<CommandResult> {
27
+ async analyze(
28
+ options: AnalyzeOptions = {},
29
+ projectPath: string = process.cwd()
30
+ ): Promise<CommandResult> {
27
31
  try {
28
32
  await this.initializeAgent()
29
33
 
@@ -31,7 +35,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
31
35
 
32
36
  analyzer.init(projectPath)
33
37
 
34
- const context = await contextBuilder.build(projectPath, options) as ProjectContext
38
+ const context = (await contextBuilder.build(projectPath, options)) as ProjectContext
35
39
 
36
40
  const analysisData = {
37
41
  packageJson: await analyzer.readPackageJson(),
@@ -58,12 +62,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
58
62
 
59
63
  const projectId = await configManager.getProjectId(projectPath)
60
64
  const summaryPath =
61
- context.paths.analysis ||
62
- pathManager.getFilePath(
63
- projectId!,
64
- 'analysis',
65
- 'repo-summary.md'
66
- )
65
+ context.paths.analysis || pathManager.getFilePath(projectId!, 'analysis', 'repo-summary.md')
67
66
 
68
67
  await toolRegistry.get('Write')!(summaryPath, summary)
69
68
 
@@ -163,7 +162,9 @@ export class AnalysisCommands extends PrjctCommandsBase {
163
162
  if (data.hasReadme) lines.push('- **Documentation**: README.md found')
164
163
  lines.push('')
165
164
 
166
- const gitStats = data.gitStats as { totalCommits?: number; contributors?: number; age?: string } | undefined
165
+ const gitStats = data.gitStats as
166
+ | { totalCommits?: number; contributors?: number; age?: string }
167
+ | undefined
167
168
  lines.push('## Git Statistics\n')
168
169
  lines.push(`- **Total Commits**: ${gitStats?.totalCommits || 0}`)
169
170
  lines.push(`- **Contributors**: ${gitStats?.contributors || 0}`)
@@ -206,7 +207,10 @@ export class AnalysisCommands extends PrjctCommandsBase {
206
207
  *
207
208
  * This eliminates the need for Claude to make 50+ individual tool calls.
208
209
  */
209
- async sync(projectPath: string = process.cwd(), options: { aiTools?: string[] } = {}): Promise<CommandResult> {
210
+ async sync(
211
+ projectPath: string = process.cwd(),
212
+ options: { aiTools?: string[] } = {}
213
+ ): Promise<CommandResult> {
210
214
  try {
211
215
  const initResult = await this.ensureProjectInit(projectPath)
212
216
  if (!initResult.success) return initResult
@@ -250,7 +254,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
250
254
 
251
255
  // Show AI Tools generated (multi-agent output)
252
256
  if (result.aiTools && result.aiTools.length > 0) {
253
- const successTools = result.aiTools.filter(t => t.success)
257
+ const successTools = result.aiTools.filter((t) => t.success)
254
258
  console.log(`🤖 AI Tools Context (${successTools.length})`)
255
259
  for (const tool of result.aiTools) {
256
260
  const status = tool.success ? '✓' : '✗'
@@ -259,8 +263,8 @@ export class AnalysisCommands extends PrjctCommandsBase {
259
263
  console.log('')
260
264
  }
261
265
 
262
- const workflowAgents = result.agents.filter(a => a.type === 'workflow').map(a => a.name)
263
- const domainAgents = result.agents.filter(a => a.type === 'domain').map(a => a.name)
266
+ const workflowAgents = result.agents.filter((a) => a.type === 'workflow').map((a) => a.name)
267
+ const domainAgents = result.agents.filter((a) => a.type === 'domain').map((a) => a.name)
264
268
 
265
269
  console.log(`🤖 Agents Regenerated (${result.agents.length})`)
266
270
  console.log(`├── Workflow: ${workflowAgents.join(', ')}`)
@@ -284,14 +288,21 @@ export class AnalysisCommands extends PrjctCommandsBase {
284
288
 
285
289
  // Summary metrics
286
290
  const elapsed = Date.now() - startTime
287
- const contextFilesCount = result.contextFiles.length + (result.aiTools?.filter(t => t.success).length || 0)
291
+ const contextFilesCount =
292
+ result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0)
288
293
  const agentCount = result.agents.length
289
294
 
290
295
  console.log('─'.repeat(45))
291
296
  console.log(`📊 Sync Summary`)
292
- console.log(` Stack: ${result.stats.ecosystem} (${result.stats.frameworks.join(', ') || 'no frameworks'})`)
293
- console.log(` Files: ${result.stats.fileCount} analyzed → ${contextFilesCount} context files`)
294
- console.log(` Agents: ${agentCount} (${result.agents.filter(a => a.type === 'domain').length} domain)`)
297
+ console.log(
298
+ ` Stack: ${result.stats.ecosystem} (${result.stats.frameworks.join(', ') || 'no frameworks'})`
299
+ )
300
+ console.log(
301
+ ` Files: ${result.stats.fileCount} analyzed → ${contextFilesCount} context files`
302
+ )
303
+ console.log(
304
+ ` Agents: ${agentCount} (${result.agents.filter((a) => a.type === 'domain').length} domain)`
305
+ )
295
306
  console.log(` Time: ${(elapsed / 1000).toFixed(1)}s`)
296
307
  console.log('')
297
308
 
@@ -311,4 +322,251 @@ export class AnalysisCommands extends PrjctCommandsBase {
311
322
  }
312
323
  }
313
324
 
325
+ /**
326
+ * /p:stats - Value dashboard showing accumulated savings and impact
327
+ *
328
+ * Displays:
329
+ * - Token savings (total, compression rate, estimated cost)
330
+ * - Performance metrics (sync count, avg duration)
331
+ * - Agent usage breakdown
332
+ * - 30-day trend visualization
333
+ */
334
+ async stats(
335
+ projectPath: string = process.cwd(),
336
+ options: { json?: boolean; export?: boolean } = {}
337
+ ): Promise<CommandResult> {
338
+ try {
339
+ const initResult = await this.ensureProjectInit(projectPath)
340
+ if (!initResult.success) return initResult
341
+
342
+ const projectId = await configManager.getProjectId(projectPath)
343
+ if (!projectId) {
344
+ return { success: false, error: 'No project ID found' }
345
+ }
346
+
347
+ // Get metrics summary
348
+ const summary = await metricsStorage.getSummary(projectId)
349
+ const dailyStats = await metricsStorage.getDailyStats(projectId, 30)
350
+
351
+ // JSON output mode
352
+ if (options.json) {
353
+ const jsonOutput = {
354
+ totalTokensSaved: summary.totalTokensSaved,
355
+ estimatedCostSaved: summary.estimatedCostSaved,
356
+ compressionRate: summary.compressionRate,
357
+ syncCount: summary.syncCount,
358
+ avgSyncDuration: summary.avgSyncDuration,
359
+ topAgents: summary.topAgents,
360
+ last30DaysTokens: summary.last30DaysTokens,
361
+ trend: summary.trend,
362
+ dailyStats,
363
+ }
364
+ console.log(JSON.stringify(jsonOutput, null, 2))
365
+ return { success: true, data: jsonOutput }
366
+ }
367
+
368
+ // Get project info for header
369
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
370
+ let projectName = 'Unknown'
371
+ try {
372
+ const fs = require('node:fs/promises')
373
+ const path = require('node:path')
374
+ const projectJson = JSON.parse(
375
+ await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
376
+ )
377
+ projectName = projectJson.name || 'Unknown'
378
+ } catch {
379
+ // Use fallback
380
+ }
381
+
382
+ // Determine first sync date
383
+ const metricsData = await metricsStorage.read(projectId)
384
+ const firstSyncDate = metricsData.firstSync
385
+ ? new Date(metricsData.firstSync).toLocaleDateString('en-US', {
386
+ month: 'short',
387
+ day: 'numeric',
388
+ year: 'numeric',
389
+ })
390
+ : 'N/A'
391
+
392
+ // ASCII Dashboard
393
+ console.log('')
394
+ console.log('╭─────────────────────────────────────────────────╮')
395
+ console.log('│ 📊 prjct-cli Value Dashboard │')
396
+ console.log(
397
+ `│ Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} │`
398
+ )
399
+ console.log('╰─────────────────────────────────────────────────╯')
400
+ console.log('')
401
+
402
+ // Token Savings Section
403
+ console.log('💰 TOKEN SAVINGS')
404
+ console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`)
405
+ console.log(
406
+ ` Compression: ${(summary.compressionRate * 100).toFixed(0)}% average reduction`
407
+ )
408
+ console.log(` Estimated cost: ${formatCost(summary.estimatedCostSaved)} saved`)
409
+ console.log('')
410
+
411
+ // Performance Section
412
+ console.log('⚡ PERFORMANCE')
413
+ console.log(` Syncs completed: ${summary.syncCount.toLocaleString()}`)
414
+ console.log(` Avg sync time: ${this._formatDuration(summary.avgSyncDuration)}`)
415
+ console.log('')
416
+
417
+ // Agent Usage Section
418
+ if (summary.topAgents.length > 0) {
419
+ console.log('🤖 AGENT USAGE')
420
+ const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
421
+ for (const agent of summary.topAgents) {
422
+ const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
423
+ console.log(` ${agent.agentName.padEnd(12)}: ${pct}% (${agent.usageCount} uses)`)
424
+ }
425
+ console.log('')
426
+ }
427
+
428
+ // 30-Day Trend Section
429
+ if (dailyStats.length > 0) {
430
+ console.log('📈 TREND (last 30 days)')
431
+ const sparkline = this._generateSparkline(dailyStats)
432
+ console.log(` ${sparkline} ${this._formatTokens(summary.last30DaysTokens)} tokens saved`)
433
+
434
+ if (summary.trend !== 0) {
435
+ const trendIcon = summary.trend > 0 ? '↑' : '↓'
436
+ const trendSign = summary.trend > 0 ? '+' : ''
437
+ console.log(
438
+ ` ${trendIcon} ${trendSign}${summary.trend.toFixed(0)}% vs previous 30 days`
439
+ )
440
+ }
441
+ console.log('')
442
+ }
443
+
444
+ // Footer
445
+ console.log('───────────────────────────────────────────────────')
446
+ console.log(`Export: prjct stats --export > stats.md`)
447
+ console.log('')
448
+
449
+ // Export mode - return markdown
450
+ if (options.export) {
451
+ const markdown = this._generateStatsMarkdown(
452
+ summary,
453
+ dailyStats,
454
+ projectName,
455
+ firstSyncDate
456
+ )
457
+ console.log(markdown)
458
+ return { success: true, data: { markdown } }
459
+ }
460
+
461
+ return {
462
+ success: true,
463
+ data: summary,
464
+ }
465
+ } catch (error) {
466
+ console.error('❌ Error:', (error as Error).message)
467
+ return { success: false, error: (error as Error).message }
468
+ }
469
+ }
470
+
471
+ // =========== Stats Helper Methods ===========
472
+
473
+ private _formatTokens(tokens: number): string {
474
+ if (tokens >= 1_000_000) {
475
+ return `${(tokens / 1_000_000).toFixed(1)}M`
476
+ }
477
+ if (tokens >= 1_000) {
478
+ return `${(tokens / 1_000).toFixed(1)}K`
479
+ }
480
+ return tokens.toLocaleString()
481
+ }
482
+
483
+ private _formatDuration(ms: number): string {
484
+ if (ms < 1000) {
485
+ return `${Math.round(ms)}ms`
486
+ }
487
+ return `${(ms / 1000).toFixed(1)}s`
488
+ }
489
+
490
+ private _generateSparkline(dailyStats: { tokensSaved: number }[]): string {
491
+ if (dailyStats.length === 0) return ''
492
+
493
+ const chars = '▁▂▃▄▅▆▇█'
494
+ const values = dailyStats.map((d) => d.tokensSaved)
495
+ const max = Math.max(...values, 1)
496
+
497
+ return values
498
+ .map((v) => {
499
+ const idx = Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)
500
+ return chars[idx]
501
+ })
502
+ .join('')
503
+ }
504
+
505
+ private _generateStatsMarkdown(
506
+ summary: {
507
+ totalTokensSaved: number
508
+ estimatedCostSaved: number
509
+ compressionRate: number
510
+ syncCount: number
511
+ avgSyncDuration: number
512
+ topAgents: { agentName: string; usageCount: number }[]
513
+ last30DaysTokens: number
514
+ trend: number
515
+ },
516
+ _dailyStats: { date: string; tokensSaved: number; syncs: number }[],
517
+ projectName: string,
518
+ firstSyncDate: string
519
+ ): string {
520
+ const lines: string[] = []
521
+
522
+ lines.push(`# ${projectName} - Value Dashboard`)
523
+ lines.push('')
524
+ lines.push(`_Generated: ${new Date().toLocaleString()} | Tracking since: ${firstSyncDate}_`)
525
+ lines.push('')
526
+
527
+ lines.push('## 💰 Token Savings')
528
+ lines.push('')
529
+ lines.push(`| Metric | Value |`)
530
+ lines.push(`|--------|-------|`)
531
+ lines.push(`| Total saved | ${this._formatTokens(summary.totalTokensSaved)} tokens |`)
532
+ lines.push(`| Compression | ${(summary.compressionRate * 100).toFixed(0)}% |`)
533
+ lines.push(`| Cost saved | ${formatCost(summary.estimatedCostSaved)} |`)
534
+ lines.push('')
535
+
536
+ lines.push('## ⚡ Performance')
537
+ lines.push('')
538
+ lines.push(`| Metric | Value |`)
539
+ lines.push(`|--------|-------|`)
540
+ lines.push(`| Syncs | ${summary.syncCount} |`)
541
+ lines.push(`| Avg time | ${this._formatDuration(summary.avgSyncDuration)} |`)
542
+ lines.push('')
543
+
544
+ if (summary.topAgents.length > 0) {
545
+ lines.push('## 🤖 Agent Usage')
546
+ lines.push('')
547
+ lines.push(`| Agent | Usage |`)
548
+ lines.push(`|-------|-------|`)
549
+ const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
550
+ for (const agent of summary.topAgents) {
551
+ const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
552
+ lines.push(`| ${agent.agentName} | ${pct}% (${agent.usageCount}) |`)
553
+ }
554
+ lines.push('')
555
+ }
556
+
557
+ lines.push('## 📈 30-Day Trend')
558
+ lines.push('')
559
+ lines.push(`- Tokens saved: ${this._formatTokens(summary.last30DaysTokens)}`)
560
+ if (summary.trend !== 0) {
561
+ const trendSign = summary.trend > 0 ? '+' : ''
562
+ lines.push(`- Trend: ${trendSign}${summary.trend.toFixed(0)}% vs previous period`)
563
+ }
564
+ lines.push('')
565
+
566
+ lines.push('---')
567
+ lines.push('')
568
+ lines.push('_Generated with [prjct-cli](https://prjct.app)_')
569
+
570
+ return lines.join('\n')
571
+ }
314
572
  }
@@ -3,21 +3,20 @@
3
3
  * Unified dashboard and contextual help - MD-First Architecture
4
4
  */
5
5
 
6
- import path from 'path'
7
-
8
- import { commandRegistry } from './registry'
6
+ import path from 'node:path'
7
+ import { ideasStorage, queueStorage, shippedStorage, stateStorage } from '../storage'
9
8
  import type { CommandResult, ProjectContext } from '../types'
10
9
  import {
11
- PrjctCommandsBase,
12
- contextBuilder,
13
- toolRegistry,
14
- pathManager,
15
10
  configManager,
16
- jsonlHelper,
11
+ contextBuilder,
17
12
  dateHelper,
18
- out
13
+ jsonlHelper,
14
+ out,
15
+ PrjctCommandsBase,
16
+ pathManager,
17
+ toolRegistry,
19
18
  } from './base'
20
- import { stateStorage, queueStorage, shippedStorage, ideasStorage } from '../storage'
19
+ import { commandRegistry } from './registry'
21
20
 
22
21
  interface MemoryEntry {
23
22
  timestamp: string
@@ -30,7 +29,10 @@ export class AnalyticsCommands extends PrjctCommandsBase {
30
29
  * /p:dash - Unified dashboard
31
30
  * Views: default, week, month, roadmap, compact
32
31
  */
33
- async dash(view: string = 'default', projectPath: string = process.cwd()): Promise<CommandResult> {
32
+ async dash(
33
+ view: string = 'default',
34
+ projectPath: string = process.cwd()
35
+ ): Promise<CommandResult> {
34
36
  try {
35
37
  const initResult = await this.ensureProjectInit(projectPath)
36
38
  if (!initResult.success) return initResult
@@ -72,9 +74,11 @@ export class AnalyticsCommands extends PrjctCommandsBase {
72
74
  const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
73
75
  let entries: MemoryEntry[] = []
74
76
  try {
75
- const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
77
+ const allEntries = (await jsonlHelper.readJsonLines(memoryPath)) as MemoryEntry[]
76
78
  entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
77
- } catch { entries = [] }
79
+ } catch {
80
+ entries = []
81
+ }
78
82
 
79
83
  const metrics = {
80
84
  tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
@@ -98,8 +102,10 @@ export class AnalyticsCommands extends PrjctCommandsBase {
98
102
 
99
103
  if (view === 'roadmap') {
100
104
  // Roadmap view
101
- const context = await contextBuilder.build(projectPath) as ProjectContext
102
- const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as string | null
105
+ const context = (await contextBuilder.build(projectPath)) as ProjectContext
106
+ const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as
107
+ | string
108
+ | null
103
109
 
104
110
  console.log(`\n🗺️ ROADMAP - ${projectName}\n`)
105
111
  console.log('═'.repeat(50))
@@ -109,7 +115,9 @@ export class AnalyticsCommands extends PrjctCommandsBase {
109
115
  console.log(' Use /p:feature to add features.\n')
110
116
  } else {
111
117
  // Parse and display roadmap
112
- const features = roadmapContent.split('##').filter(s => s.trim() && !s.includes('ROADMAP'))
118
+ const features = roadmapContent
119
+ .split('##')
120
+ .filter((s) => s.trim() && !s.includes('ROADMAP'))
113
121
  features.slice(0, 5).forEach((f, i) => {
114
122
  const name = f.split('\n')[0].trim()
115
123
  console.log(` ${i + 1}. ${name}`)
@@ -118,7 +126,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
118
126
  console.log(` ... and ${features.length - 5} more`)
119
127
  }
120
128
  }
121
- console.log('═'.repeat(50) + '\n')
129
+ console.log(`${'═'.repeat(50)}\n`)
122
130
 
123
131
  return { success: true, view: 'roadmap' }
124
132
  }
@@ -168,7 +176,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
168
176
  console.log('\n💡 IDEAS')
169
177
  console.log(` ${ideas.length} pending ideas`)
170
178
 
171
- console.log('\n' + '═'.repeat(50))
179
+ console.log(`\n${'═'.repeat(50)}`)
172
180
  console.log('💡 /p:work to start | /p:done to complete | /p:ship to ship\n')
173
181
 
174
182
  await this.logToMemory(projectPath, 'dash_viewed', {
@@ -183,8 +191,8 @@ export class AnalyticsCommands extends PrjctCommandsBase {
183
191
  currentTask: currentTask?.description || null,
184
192
  queueCount: queueTasks.length,
185
193
  shippedCount: shipped.length,
186
- ideasCount: ideas.length
187
- }
194
+ ideasCount: ideas.length,
195
+ },
188
196
  }
189
197
  } catch (error) {
190
198
  out.fail((error as Error).message)
@@ -195,7 +203,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
195
203
  /**
196
204
  * /p:help - Contextual help and guidance
197
205
  */
198
- async help(topic: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
206
+ async help(topic: string = '', _projectPath: string = process.cwd()): Promise<CommandResult> {
199
207
  try {
200
208
  if (!topic) {
201
209
  // Show command overview
@@ -207,7 +215,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
207
215
 
208
216
  // Group by category
209
217
  const byCategory: Record<string, typeof commands> = {}
210
- commands.forEach(cmd => {
218
+ commands.forEach((cmd) => {
211
219
  if (cmd.deprecated) return
212
220
  if (!byCategory[cmd.group]) byCategory[cmd.group] = []
213
221
  byCategory[cmd.group].push(cmd)
@@ -216,14 +224,14 @@ export class AnalyticsCommands extends PrjctCommandsBase {
216
224
  Object.entries(byCategory).forEach(([cat, cmds]) => {
217
225
  const catInfo = categories.get(cat)
218
226
  console.log(`\n${catInfo?.title || cat}:`)
219
- cmds.forEach(cmd => {
227
+ cmds.forEach((cmd) => {
220
228
  const params = cmd.params ? ` ${cmd.params}` : ''
221
229
  console.log(` ${cmd.name}${params}`)
222
230
  console.log(` ${cmd.description}`)
223
231
  })
224
232
  })
225
233
 
226
- console.log('\n' + '═'.repeat(50))
234
+ console.log(`\n${'═'.repeat(50)}`)
227
235
  console.log('💡 Use /p:help <command> for detailed help\n')
228
236
 
229
237
  return { success: true, topic: 'overview' }
@@ -248,31 +256,31 @@ export class AnalyticsCommands extends PrjctCommandsBase {
248
256
 
249
257
  if (command.features) {
250
258
  console.log('\nFeatures:')
251
- command.features.forEach(f => console.log(` • ${f}`))
259
+ command.features.forEach((f) => console.log(` • ${f}`))
252
260
  }
253
261
 
254
- console.log('\n' + '═'.repeat(50) + '\n')
262
+ console.log(`\n${'═'.repeat(50)}\n`)
255
263
  return { success: true, topic, command }
256
264
  }
257
265
 
258
266
  // Intent translation (like old /p:ask)
259
267
  const intents: Record<string, { command: string; hint: string }> = {
260
- 'start': { command: 'work', hint: 'Start working on a task' },
261
- 'begin': { command: 'work', hint: 'Start working on a task' },
262
- 'finish': { command: 'done', hint: 'Mark current task complete' },
263
- 'complete': { command: 'done', hint: 'Mark current task complete' },
264
- 'deploy': { command: 'ship', hint: 'Ship a feature' },
265
- 'release': { command: 'ship', hint: 'Ship a feature' },
266
- 'status': { command: 'dash', hint: 'View project dashboard' },
267
- 'overview': { command: 'dash', hint: 'View project dashboard' },
268
- 'queue': { command: 'next', hint: 'View task queue' },
269
- 'tasks': { command: 'next', hint: 'View task queue' },
270
- 'add': { command: 'feature', hint: 'Add a new feature' },
271
- 'new': { command: 'feature', hint: 'Add a new feature' },
272
- 'break': { command: 'pause', hint: 'Pause current task' },
273
- 'stop': { command: 'pause', hint: 'Pause current task' },
274
- 'continue': { command: 'resume', hint: 'Resume paused task' },
275
- 'back': { command: 'resume', hint: 'Resume paused task' },
268
+ start: { command: 'work', hint: 'Start working on a task' },
269
+ begin: { command: 'work', hint: 'Start working on a task' },
270
+ finish: { command: 'done', hint: 'Mark current task complete' },
271
+ complete: { command: 'done', hint: 'Mark current task complete' },
272
+ deploy: { command: 'ship', hint: 'Ship a feature' },
273
+ release: { command: 'ship', hint: 'Ship a feature' },
274
+ status: { command: 'dash', hint: 'View project dashboard' },
275
+ overview: { command: 'dash', hint: 'View project dashboard' },
276
+ queue: { command: 'next', hint: 'View task queue' },
277
+ tasks: { command: 'next', hint: 'View task queue' },
278
+ add: { command: 'feature', hint: 'Add a new feature' },
279
+ new: { command: 'feature', hint: 'Add a new feature' },
280
+ break: { command: 'pause', hint: 'Pause current task' },
281
+ stop: { command: 'pause', hint: 'Pause current task' },
282
+ continue: { command: 'resume', hint: 'Resume paused task' },
283
+ back: { command: 'resume', hint: 'Resume paused task' },
276
284
  }
277
285
 
278
286
  const lowerTopic = topic.toLowerCase()
@@ -308,7 +316,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
308
316
  const dayStart = new Date(date.setHours(0, 0, 0, 0))
309
317
  const dayEnd = new Date(date.setHours(23, 59, 59, 999))
310
318
 
311
- const count = entries.filter(e => {
319
+ const count = entries.filter((e) => {
312
320
  const ts = new Date(e.timestamp)
313
321
  return ts >= dayStart && ts <= dayEnd
314
322
  }).length
@@ -317,6 +325,6 @@ export class AnalyticsCommands extends PrjctCommandsBase {
317
325
  }
318
326
 
319
327
  const max = Math.max(...counts, 1)
320
- return counts.map(c => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
328
+ return counts.map((c) => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
321
329
  }
322
330
  }
@@ -8,24 +8,22 @@
8
8
  import commandExecutor from '../agentic/command-executor'
9
9
  import contextBuilder from '../agentic/context-builder'
10
10
  import toolRegistry from '../agentic/tool-registry'
11
- import pathManager from '../infrastructure/path-manager'
12
11
  import configManager from '../infrastructure/config-manager'
12
+ import pathManager from '../infrastructure/path-manager'
13
13
  import UpdateChecker from '../infrastructure/update-checker'
14
- import dateHelper from '../utils/date-helper'
15
- import jsonlHelper from '../utils/jsonl-helper'
16
- import * as fileHelper from '../utils/file-helper'
17
- import out from '../utils/output'
18
-
19
14
  // Services
20
- import { agentService, projectService, memoryService, breakdownService } from '../services'
21
-
15
+ import { agentService, breakdownService, memoryService, projectService } from '../services'
22
16
  import type {
23
- CommandResult,
17
+ AgentAssignmentResult,
24
18
  AgentInfo,
25
19
  Author,
26
- AgentAssignmentResult,
27
- ProjectContext
20
+ CommandResult,
21
+ ProjectContext,
28
22
  } from '../types'
23
+ import dateHelper from '../utils/date-helper'
24
+ import * as fileHelper from '../utils/file-helper'
25
+ import jsonlHelper from '../utils/jsonl-helper'
26
+ import out from '../utils/output'
29
27
 
30
28
  /**
31
29
  * Base class with shared state and utilities
@@ -73,7 +71,11 @@ export class PrjctCommandsBase {
73
71
  return projectService.getGlobalPath(projectPath)
74
72
  }
75
73
 
76
- async logToMemory(projectPath: string, action: string, data: Record<string, unknown>): Promise<void> {
74
+ async logToMemory(
75
+ projectPath: string,
76
+ action: string,
77
+ data: Record<string, unknown>
78
+ ): Promise<void> {
77
79
  const author = await this.ensureAuthor()
78
80
  return memoryService.log(projectPath, action, data, author.name)
79
81
  }
@@ -112,5 +114,5 @@ export {
112
114
  fileHelper,
113
115
  jsonlHelper,
114
116
  dateHelper,
115
- out
117
+ out,
116
118
  }
@@ -4,19 +4,12 @@
4
4
  * Memory and project file cleanup operations.
5
5
  */
6
6
 
7
- import path from 'path'
8
-
9
- import { isNotFoundError } from '../types/fs'
10
- import type { CommandResult, CleanupOptions } from '../types'
11
- import {
12
- pathManager,
13
- configManager,
14
- jsonlHelper,
15
- dateHelper,
16
- out
17
- } from './base'
18
- import { ideasStorage, queueStorage } from '../storage'
7
+ import path from 'node:path'
19
8
  import { memoryService } from '../services'
9
+ import { ideasStorage, queueStorage } from '../storage'
10
+ import type { CleanupOptions, CommandResult } from '../types'
11
+ import { isNotFoundError } from '../types/fs'
12
+ import { configManager, dateHelper, jsonlHelper, out, pathManager } from './base'
20
13
 
21
14
  /**
22
15
  * Memory cleanup helper
@@ -131,7 +124,7 @@ export async function cleanup(
131
124
  // Check queue for completed tasks using queueStorage
132
125
  try {
133
126
  const tasks = await queueStorage.getActiveTasks(projectId)
134
- const completedTasks = tasks.filter(t => t.completed).length
127
+ const completedTasks = tasks.filter((t) => t.completed).length
135
128
 
136
129
  if (completedTasks > 0) {
137
130
  cleaned.push(