prjct-cli 0.13.3 → 0.15.1

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 (195) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/bin/prjct +10 -13
  3. package/core/agentic/memory-system/semantic-memories.ts +2 -1
  4. package/core/agentic/plan-mode/plan-mode.ts +2 -1
  5. package/core/agentic/prompt-builder.ts +22 -43
  6. package/core/agentic/services.ts +5 -5
  7. package/core/agentic/smart-context.ts +7 -2
  8. package/core/command-registry/core-commands.ts +54 -29
  9. package/core/command-registry/optional-commands.ts +64 -0
  10. package/core/command-registry/setup-commands.ts +18 -3
  11. package/core/commands/analysis.ts +21 -68
  12. package/core/commands/analytics.ts +247 -213
  13. package/core/commands/base.ts +1 -1
  14. package/core/commands/index.ts +41 -36
  15. package/core/commands/maintenance.ts +300 -31
  16. package/core/commands/planning.ts +233 -22
  17. package/core/commands/setup.ts +3 -8
  18. package/core/commands/shipping.ts +14 -18
  19. package/core/commands/types.ts +8 -6
  20. package/core/commands/workflow.ts +105 -100
  21. package/core/context/generator.ts +317 -0
  22. package/core/context-sync.ts +7 -350
  23. package/core/data/index.ts +13 -32
  24. package/core/data/md-ideas-manager.ts +155 -0
  25. package/core/data/md-queue-manager.ts +4 -3
  26. package/core/data/md-shipped-manager.ts +90 -0
  27. package/core/data/md-state-manager.ts +11 -7
  28. package/core/domain/agent-generator.ts +23 -63
  29. package/core/events/index.ts +143 -0
  30. package/core/index.ts +17 -14
  31. package/core/infrastructure/capability-installer.ts +13 -149
  32. package/core/infrastructure/migrator/project-scanner.ts +2 -1
  33. package/core/infrastructure/path-manager.ts +4 -6
  34. package/core/infrastructure/setup.ts +3 -0
  35. package/core/infrastructure/uuid-migration.ts +750 -0
  36. package/core/outcomes/recorder.ts +2 -1
  37. package/core/plugin/loader.ts +4 -7
  38. package/core/plugin/registry.ts +3 -3
  39. package/core/schemas/index.ts +23 -25
  40. package/core/schemas/state.ts +1 -0
  41. package/core/serializers/ideas-serializer.ts +187 -0
  42. package/core/serializers/index.ts +16 -0
  43. package/core/serializers/shipped-serializer.ts +108 -0
  44. package/core/session/utils.ts +3 -9
  45. package/core/storage/ideas-storage.ts +273 -0
  46. package/core/storage/index.ts +204 -0
  47. package/core/storage/queue-storage.ts +297 -0
  48. package/core/storage/shipped-storage.ts +223 -0
  49. package/core/storage/state-storage.ts +235 -0
  50. package/core/storage/storage-manager.ts +175 -0
  51. package/package.json +1 -1
  52. package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
  53. package/packages/web/app/api/sessions/current/route.ts +132 -0
  54. package/packages/web/app/api/sessions/history/route.ts +96 -14
  55. package/packages/web/app/globals.css +5 -0
  56. package/packages/web/app/layout.tsx +2 -0
  57. package/packages/web/app/project/[id]/code/layout.tsx +18 -0
  58. package/packages/web/app/project/[id]/code/page.tsx +408 -0
  59. package/packages/web/app/project/[id]/page.tsx +359 -389
  60. package/packages/web/app/project/[id]/reports/page.tsx +59 -0
  61. package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
  62. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
  63. package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
  64. package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
  65. package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
  66. package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
  67. package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
  68. package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
  69. package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
  70. package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
  71. package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
  72. package/packages/web/components/CommandBar/index.ts +1 -0
  73. package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
  74. package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
  75. package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
  76. package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
  77. package/packages/web/components/EventRow/EventRow.tsx +4 -4
  78. package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
  79. package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
  80. package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
  81. package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
  82. package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
  83. package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
  84. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
  85. package/packages/web/components/MasonryGrid/index.ts +1 -0
  86. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
  87. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
  88. package/packages/web/components/MomentumWidget/index.ts +2 -0
  89. package/packages/web/components/NowCard/NowCard.tsx +81 -56
  90. package/packages/web/components/NowCard/NowCard.types.ts +1 -0
  91. package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
  92. package/packages/web/components/PageHeader/index.ts +1 -0
  93. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
  94. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
  95. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
  96. package/packages/web/components/ProjectColorDot/index.ts +1 -0
  97. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
  98. package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
  99. package/packages/web/components/Providers/Providers.tsx +4 -1
  100. package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
  101. package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
  102. package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
  103. package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
  104. package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
  105. package/packages/web/components/RecoverCard/index.ts +2 -0
  106. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
  107. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
  108. package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
  109. package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
  110. package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
  111. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
  112. package/packages/web/components/StatsMasonry/index.ts +1 -0
  113. package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
  114. package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
  115. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
  116. package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
  117. package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
  118. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
  119. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
  120. package/packages/web/components/TerminalDock/index.ts +2 -0
  121. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
  122. package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
  123. package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
  124. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
  125. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
  126. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
  127. package/packages/web/components/WeeklyReports/index.ts +4 -0
  128. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
  129. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
  130. package/packages/web/components/charts/SessionsChart.tsx +6 -3
  131. package/packages/web/components/ui/dialog.tsx +143 -0
  132. package/packages/web/components/ui/drawer.tsx +135 -0
  133. package/packages/web/components/ui/select.tsx +187 -0
  134. package/packages/web/context/GlobalTerminalContext.tsx +538 -0
  135. package/packages/web/lib/commands.ts +81 -0
  136. package/packages/web/lib/generate-week-report.ts +285 -0
  137. package/packages/web/lib/parse-prjct-files.ts +56 -55
  138. package/packages/web/lib/project-colors.ts +58 -0
  139. package/packages/web/lib/projects.ts +58 -5
  140. package/packages/web/lib/services/projects.server.ts +11 -1
  141. package/packages/web/next-env.d.ts +1 -1
  142. package/packages/web/package.json +5 -1
  143. package/templates/commands/analyze.md +39 -3
  144. package/templates/commands/ask.md +58 -3
  145. package/templates/commands/bug.md +117 -26
  146. package/templates/commands/dash.md +95 -158
  147. package/templates/commands/done.md +130 -148
  148. package/templates/commands/feature.md +125 -103
  149. package/templates/commands/git.md +18 -3
  150. package/templates/commands/idea.md +121 -38
  151. package/templates/commands/init.md +124 -20
  152. package/templates/commands/migrate-all.md +63 -28
  153. package/templates/commands/migrate.md +140 -0
  154. package/templates/commands/next.md +115 -5
  155. package/templates/commands/now.md +146 -82
  156. package/templates/commands/pause.md +89 -74
  157. package/templates/commands/redo.md +6 -4
  158. package/templates/commands/resume.md +141 -59
  159. package/templates/commands/setup.md +18 -3
  160. package/templates/commands/ship.md +103 -231
  161. package/templates/commands/spec.md +98 -8
  162. package/templates/commands/suggest.md +22 -2
  163. package/templates/commands/sync.md +192 -203
  164. package/templates/commands/undo.md +6 -4
  165. package/templates/mcp-config.json +20 -1
  166. package/core/data/agents-manager.ts +0 -76
  167. package/core/data/analysis-manager.ts +0 -83
  168. package/core/data/base-manager.ts +0 -156
  169. package/core/data/ideas-manager.ts +0 -81
  170. package/core/data/outcomes-manager.ts +0 -96
  171. package/core/data/project-manager.ts +0 -75
  172. package/core/data/roadmap-manager.ts +0 -118
  173. package/core/data/shipped-manager.ts +0 -65
  174. package/core/data/state-manager.ts +0 -214
  175. package/core/state/index.ts +0 -25
  176. package/core/state/manager.ts +0 -376
  177. package/core/state/types.ts +0 -185
  178. package/core/utils/project-capabilities.ts +0 -156
  179. package/core/view-generator.ts +0 -536
  180. package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
  181. package/packages/web/app/project/[id]/stats/page.tsx +0 -253
  182. package/templates/agent-assignment.md +0 -72
  183. package/templates/analysis/project-analysis.md +0 -78
  184. package/templates/checklists/accessibility.md +0 -33
  185. package/templates/commands/build.md +0 -17
  186. package/templates/commands/decision.md +0 -226
  187. package/templates/commands/fix.md +0 -79
  188. package/templates/commands/help.md +0 -61
  189. package/templates/commands/progress.md +0 -14
  190. package/templates/commands/recap.md +0 -14
  191. package/templates/commands/roadmap.md +0 -52
  192. package/templates/commands/status.md +0 -17
  193. package/templates/commands/task.md +0 -63
  194. package/templates/commands/work.md +0 -44
  195. package/templates/commands/workflow.md +0 -12
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Analytics Commands: context, recap, progress, status, roadmap, stuck
2
+ * Analytics Commands: dash, help
3
+ * Unified dashboard and contextual help - MD-First Architecture
3
4
  */
4
5
 
5
6
  import path from 'path'
7
+ import registry from '../command-registry'
6
8
 
7
9
  import type { CommandResult, Context } from './types'
8
10
  import {
@@ -15,6 +17,7 @@ import {
15
17
  dateHelper,
16
18
  out
17
19
  } from './base'
20
+ import { stateStorage, queueStorage, shippedStorage, ideasStorage } from '../storage'
18
21
 
19
22
  interface MemoryEntry {
20
23
  timestamp: string
@@ -24,142 +27,165 @@ interface MemoryEntry {
24
27
 
25
28
  export class AnalyticsCommands extends PrjctCommandsBase {
26
29
  /**
27
- * /p:context - Show project context and recent activity
30
+ * /p:dash - Unified dashboard
31
+ * Views: default, week, month, roadmap, compact
28
32
  */
29
- async context(projectPath: string = process.cwd()): Promise<CommandResult> {
33
+ async dash(view: string = 'default', projectPath: string = process.cwd()): Promise<CommandResult> {
30
34
  try {
31
35
  const initResult = await this.ensureProjectInit(projectPath)
32
36
  if (!initResult.success) return initResult
33
37
 
34
- out.spin('loading context...')
35
- const context = await contextBuilder.build(projectPath) as Context
36
-
37
- const nowContent = (await toolRegistry.get('Read')!(context.paths.now)) as string | null
38
- const nextContent = (await toolRegistry.get('Read')!(context.paths.next)) as string | null
39
-
40
- let task = 'none'
41
- if (nowContent && !nowContent.includes('No current task')) {
42
- const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
43
- task = taskMatch ? taskMatch[1] : 'active'
38
+ const projectId = await configManager.getProjectId(projectPath)
39
+ if (!projectId) {
40
+ out.fail('no project ID')
41
+ return { success: false, error: 'No project ID found' }
44
42
  }
45
43
 
46
- const nextLines = nextContent?.split('\n').filter((line) => line.trim() && !line.startsWith('#')) || []
47
- const queueCount = nextLines.length
44
+ const projectName = path.basename(projectPath)
48
45
 
49
- await this.logToMemory(projectPath, 'context_viewed', { timestamp: dateHelper.getTimestamp() })
50
-
51
- out.done(`task: ${task} | queue: ${queueCount}`)
52
- return { success: true }
53
- } catch (error) {
54
- out.fail((error as Error).message)
55
- return { success: false, error: (error as Error).message }
56
- }
57
- }
46
+ // Get current task (from storage layer - JSON source of truth)
47
+ const currentTask = await stateStorage.getCurrentTask(projectId)
58
48
 
59
- /**
60
- * /p:recap - Show project overview with progress
61
- */
62
- async recap(projectPath: string = process.cwd()): Promise<CommandResult> {
63
- try {
64
- const initResult = await this.ensureProjectInit(projectPath)
65
- if (!initResult.success) return initResult
49
+ // Get queue
50
+ const queueTasks = await queueStorage.getActiveTasks(projectId)
66
51
 
67
- out.spin('loading recap...')
68
- const context = await contextBuilder.build(projectPath) as Context
52
+ // Get shipped (recent)
53
+ const shipped = await shippedStorage.getRecent(projectId, 5)
69
54
 
70
- const shippedContent = (await toolRegistry.get('Read')!(context.paths.shipped)) as string | null
71
- const shippedFeatures = shippedContent?.split('##').filter((s) => s.trim() && !s.includes('SHIPPED')) || []
55
+ // Get ideas
56
+ const ideas = await ideasStorage.getPending(projectId)
72
57
 
73
- const nextContent = (await toolRegistry.get('Read')!(context.paths.next)) as string | null
74
- const nextTasks = nextContent?.split('\n').filter((l) => l.match(/^\d+\./) || l.includes('[ ]')).length || 0
58
+ if (view === 'compact') {
59
+ // One-liner status
60
+ const taskStatus = currentTask ? `šŸŽÆ ${currentTask.description.slice(0, 30)}` : 'šŸ’¤ idle'
61
+ const queueStatus = `šŸ“‹ ${queueTasks.length}`
62
+ const shippedStatus = `šŸš€ ${shipped.length}`
63
+ out.done(`${taskStatus} | ${queueStatus} | ${shippedStatus}`)
64
+ return { success: true, view: 'compact' }
65
+ }
75
66
 
76
- const ideasContent = (await toolRegistry.get('Read')!(context.paths.ideas)) as string | null
77
- const ideas = ideasContent?.split('##').filter((s) => s.trim() && !s.includes('IDEAS') && !s.includes('Brain')).length || 0
67
+ if (view === 'week' || view === 'month') {
68
+ // Period-based metrics
69
+ const days = view === 'week' ? 7 : 30
70
+ const startDate = dateHelper.getDaysAgo(days)
71
+
72
+ const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
73
+ let entries: MemoryEntry[] = []
74
+ try {
75
+ const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
76
+ entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
77
+ } catch { entries = [] }
78
+
79
+ const metrics = {
80
+ tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
81
+ featuresShipped: entries.filter((e) => e.action === 'feature_shipped').length,
82
+ totalActions: entries.length,
83
+ }
84
+
85
+ console.log(`\nšŸ“Š ${view.toUpperCase()} PROGRESS - ${projectName}\n`)
86
+ console.log('═'.repeat(50))
87
+ console.log(` Tasks completed: ${metrics.tasksCompleted}`)
88
+ console.log(` Features shipped: ${metrics.featuresShipped}`)
89
+ console.log(` Total actions: ${metrics.totalActions}`)
90
+ console.log('═'.repeat(50))
91
+
92
+ // ASCII sparkline
93
+ const sparkline = this._generateSparkline(entries, days)
94
+ console.log(`\n Activity: ${sparkline}\n`)
95
+
96
+ return { success: true, view, metrics }
97
+ }
78
98
 
79
- await this.logToMemory(projectPath, 'recap_viewed', {
80
- shipped: shippedFeatures.length,
81
- tasks: nextTasks,
82
- timestamp: dateHelper.getTimestamp(),
83
- })
99
+ if (view === 'roadmap') {
100
+ // Roadmap view
101
+ const context = await contextBuilder.build(projectPath) as Context
102
+ const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as string | null
103
+
104
+ console.log(`\nšŸ—ŗļø ROADMAP - ${projectName}\n`)
105
+ console.log('═'.repeat(50))
106
+
107
+ if (!roadmapContent || roadmapContent.trim() === '# ROADMAP') {
108
+ console.log(' No features planned yet.')
109
+ console.log(' Use /p:feature to add features.\n')
110
+ } else {
111
+ // Parse and display roadmap
112
+ const features = roadmapContent.split('##').filter(s => s.trim() && !s.includes('ROADMAP'))
113
+ features.slice(0, 5).forEach((f, i) => {
114
+ const name = f.split('\n')[0].trim()
115
+ console.log(` ${i + 1}. ${name}`)
116
+ })
117
+ if (features.length > 5) {
118
+ console.log(` ... and ${features.length - 5} more`)
119
+ }
120
+ }
121
+ console.log('═'.repeat(50) + '\n')
122
+
123
+ return { success: true, view: 'roadmap' }
124
+ }
84
125
 
85
- out.done(`shipped: ${shippedFeatures.length} | queue: ${nextTasks} | ideas: ${ideas}`)
86
- return { success: true, stats: { shipped: shippedFeatures.length, tasks: nextTasks, ideas } }
87
- } catch (error) {
88
- out.fail((error as Error).message)
89
- return { success: false, error: (error as Error).message }
90
- }
91
- }
126
+ // Default view - project overview
127
+ console.log(`\nšŸ“Š DASHBOARD - ${projectName}\n`)
128
+ console.log('═'.repeat(50))
129
+
130
+ // Current task
131
+ console.log('\nšŸŽÆ CURRENT FOCUS')
132
+ if (currentTask) {
133
+ console.log(` ${currentTask.description}`)
134
+ if (currentTask.startedAt) {
135
+ const elapsed = dateHelper.calculateDuration(new Date(currentTask.startedAt))
136
+ console.log(` Started: ${elapsed} ago`)
137
+ }
138
+ } else {
139
+ console.log(' No active task. Use /p:work to start.')
140
+ }
92
141
 
93
- /**
94
- * /p:stuck - Get contextual help with problems
95
- */
96
- async stuck(issue: string, projectPath: string = process.cwd()): Promise<CommandResult> {
97
- try {
98
- const initResult = await this.ensureProjectInit(projectPath)
99
- if (!initResult.success) return initResult
142
+ // Queue
143
+ console.log('\nšŸ“‹ QUEUE')
144
+ if (queueTasks.length === 0) {
145
+ console.log(' Queue is empty')
146
+ } else {
147
+ queueTasks.slice(0, 3).forEach((t, i) => {
148
+ const priority = t.priority ? `[${t.priority}]` : ''
149
+ console.log(` ${i + 1}. ${t.description.slice(0, 40)} ${priority}`)
150
+ })
151
+ if (queueTasks.length > 3) {
152
+ console.log(` ... and ${queueTasks.length - 3} more`)
153
+ }
154
+ }
100
155
 
101
- if (!issue) {
102
- out.fail('issue description required')
103
- return { success: false, error: 'Issue description required' }
156
+ // Recent ships
157
+ console.log('\nšŸš€ RECENT SHIPS')
158
+ if (shipped.length === 0) {
159
+ console.log(' Nothing shipped yet')
160
+ } else {
161
+ shipped.slice(0, 3).forEach((s) => {
162
+ const date = s.shippedAt ? new Date(s.shippedAt).toLocaleDateString() : ''
163
+ console.log(` • ${s.name} ${date ? `(${date})` : ''}`)
164
+ })
104
165
  }
105
166
 
106
- out.spin('logging issue...')
167
+ // Ideas
168
+ console.log('\nšŸ’” IDEAS')
169
+ console.log(` ${ideas.length} pending ideas`)
107
170
 
108
- const analyzer = require('../domain/analyzer')
109
- analyzer.init(projectPath)
110
- const packageJson = await analyzer.readPackageJson()
111
- const detectedStack = packageJson?.name || 'project'
171
+ console.log('\n' + '═'.repeat(50))
172
+ console.log('šŸ’” /p:work to start | /p:done to complete | /p:ship to ship\n')
112
173
 
113
- await this.logToMemory(projectPath, 'help_requested', {
114
- issue,
115
- stack: detectedStack,
174
+ await this.logToMemory(projectPath, 'dash_viewed', {
175
+ view,
116
176
  timestamp: dateHelper.getTimestamp(),
117
177
  })
118
178
 
119
- out.done(`issue logged: ${issue.slice(0, 40)}`)
120
- return { success: true, issue, stack: detectedStack }
121
- } catch (error) {
122
- out.fail((error as Error).message)
123
- return { success: false, error: (error as Error).message }
124
- }
125
- }
126
-
127
- /**
128
- * /p:progress - Show metrics for period
129
- */
130
- async progress(period: string = 'week', projectPath: string = process.cwd()): Promise<CommandResult> {
131
- try {
132
- const initResult = await this.ensureProjectInit(projectPath)
133
- if (!initResult.success) return initResult
134
-
135
- const validPeriods = ['day', 'week', 'month', 'all']
136
- if (!validPeriods.includes(period)) period = 'week'
137
-
138
- out.spin(`loading ${period} progress...`)
139
-
140
- const projectId = await configManager.getProjectId(projectPath)
141
- const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
142
-
143
- const startDate = period === 'day' ? dateHelper.getDaysAgo(1) :
144
- period === 'week' ? dateHelper.getDaysAgo(7) :
145
- period === 'month' ? dateHelper.getDaysAgo(30) : new Date(0)
146
-
147
- let entries: MemoryEntry[] = []
148
- try {
149
- const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
150
- entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
151
- } catch { entries = [] }
152
-
153
- const metrics = {
154
- tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
155
- featuresShipped: entries.filter((e) => e.action === 'feature_shipped').length,
156
- totalActions: entries.length,
179
+ return {
180
+ success: true,
181
+ view: 'default',
182
+ stats: {
183
+ currentTask: currentTask?.description || null,
184
+ queueCount: queueTasks.length,
185
+ shippedCount: shipped.length,
186
+ ideasCount: ideas.length
187
+ }
157
188
  }
158
-
159
- await this.logToMemory(projectPath, 'progress_viewed', { period, metrics, timestamp: dateHelper.getTimestamp() })
160
-
161
- out.done(`${period}: ${metrics.tasksCompleted} tasks | ${metrics.featuresShipped} shipped`)
162
- return { success: true, period, metrics }
163
189
  } catch (error) {
164
190
  out.fail((error as Error).message)
165
191
  return { success: false, error: (error as Error).message }
@@ -167,28 +193,100 @@ export class AnalyticsCommands extends PrjctCommandsBase {
167
193
  }
168
194
 
169
195
  /**
170
- * /p:roadmap - Show roadmap with ASCII logic maps
196
+ * /p:help - Contextual help and guidance
171
197
  */
172
- async roadmap(projectPath: string = process.cwd()): Promise<CommandResult> {
198
+ async help(topic: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
173
199
  try {
174
- const initResult = await this.ensureProjectInit(projectPath)
175
- if (!initResult.success) return initResult
176
-
177
- out.spin('loading roadmap...')
178
- const context = await contextBuilder.build(projectPath) as Context
179
- const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as string | null
200
+ if (!topic) {
201
+ // Show command overview
202
+ console.log('\nšŸ”§ PRJCT COMMANDS\n')
203
+ console.log('═'.repeat(50))
204
+
205
+ const categories = registry.getCategories()
206
+ const commands = registry.getAll()
207
+
208
+ // Group by category
209
+ const byCategory: Record<string, typeof commands> = {}
210
+ commands.forEach(cmd => {
211
+ if (cmd.deprecated) return
212
+ if (!byCategory[cmd.category]) byCategory[cmd.category] = []
213
+ byCategory[cmd.category].push(cmd)
214
+ })
215
+
216
+ Object.entries(byCategory).forEach(([cat, cmds]) => {
217
+ const catInfo = categories[cat]
218
+ console.log(`\n${catInfo?.title || cat}:`)
219
+ cmds.forEach(cmd => {
220
+ const params = cmd.params ? ` ${cmd.params}` : ''
221
+ console.log(` ${cmd.name}${params}`)
222
+ console.log(` ${cmd.description}`)
223
+ })
224
+ })
225
+
226
+ console.log('\n' + '═'.repeat(50))
227
+ console.log('šŸ’” Use /p:help <command> for detailed help\n')
228
+
229
+ return { success: true, topic: 'overview' }
230
+ }
180
231
 
181
- if (!roadmapContent || roadmapContent.trim() === '# ROADMAP') {
182
- out.warn('no roadmap yet')
183
- return { success: true, message: 'No roadmap' }
232
+ // Topic-specific help
233
+ const command = registry.getByName(topic)
234
+ if (command) {
235
+ console.log(`\nšŸ“š HELP: /p:${command.name}\n`)
236
+ console.log('═'.repeat(50))
237
+ console.log(`Description: ${command.description}`)
238
+
239
+ if (command.params) {
240
+ console.log(`Parameters: ${command.params}`)
241
+ }
242
+
243
+ if (command.usage) {
244
+ console.log('\nUsage:')
245
+ if (command.usage.claude) console.log(` Claude: ${command.usage.claude}`)
246
+ if (command.usage.terminal) console.log(` Terminal: ${command.usage.terminal}`)
247
+ }
248
+
249
+ if (command.features) {
250
+ console.log('\nFeatures:')
251
+ command.features.forEach(f => console.log(` • ${f}`))
252
+ }
253
+
254
+ console.log('\n' + '═'.repeat(50) + '\n')
255
+ return { success: true, topic, command }
184
256
  }
185
257
 
186
- const features = (roadmapContent.match(/##/g) || []).length
258
+ // Intent translation (like old /p:ask)
259
+ 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' },
276
+ }
187
277
 
188
- await this.logToMemory(projectPath, 'roadmap_viewed', { timestamp: dateHelper.getTimestamp() })
278
+ const lowerTopic = topic.toLowerCase()
279
+ for (const [intent, info] of Object.entries(intents)) {
280
+ if (lowerTopic.includes(intent)) {
281
+ console.log(`\nšŸ’” Did you mean /p:${info.command}?`)
282
+ console.log(` ${info.hint}\n`)
283
+ return { success: true, topic, suggestion: info.command }
284
+ }
285
+ }
189
286
 
190
- out.done(`${features} features in roadmap`)
191
- return { success: true, content: roadmapContent }
287
+ console.log(`\nā“ Unknown topic: ${topic}`)
288
+ console.log(' Use /p:help to see all commands\n')
289
+ return { success: false, error: `Unknown topic: ${topic}` }
192
290
  } catch (error) {
193
291
  out.fail((error as Error).message)
194
292
  return { success: false, error: (error as Error).message }
@@ -196,93 +294,29 @@ export class AnalyticsCommands extends PrjctCommandsBase {
196
294
  }
197
295
 
198
296
  /**
199
- * /p:status - KPI dashboard with ASCII graphics
297
+ * Generate ASCII sparkline for activity
200
298
  */
201
- async status(projectPath: string = process.cwd()): Promise<CommandResult> {
202
- try {
203
- const initResult = await this.ensureProjectInit(projectPath)
204
- if (!initResult.success) return initResult
205
-
206
- console.log('šŸ“Š Project Status Dashboard\n')
207
-
208
- const context = await contextBuilder.build(projectPath) as Context
209
-
210
- const nowContent = (await toolRegistry.get('Read')!(context.paths.now)) as string | null
211
- const nextContent = (await toolRegistry.get('Read')!(context.paths.next)) as string | null
212
- const shippedContent = (await toolRegistry.get('Read')!(context.paths.shipped)) as string | null
213
- const ideasContent = (await toolRegistry.get('Read')!(context.paths.ideas)) as string | null
214
-
215
- const stats = {
216
- activeTask: !!(nowContent && !nowContent.includes('No current task')),
217
- tasksInQueue:
218
- nextContent
219
- ?.split('\n')
220
- .filter((line) => line.trim().match(/^\d+\./) || line.includes('[ ]')).length || 0,
221
- featuresShipped:
222
- shippedContent
223
- ?.split('##')
224
- .filter((section) => section.trim() && !section.includes('SHIPPED šŸš€')).length || 0,
225
- ideasCaptured:
226
- ideasContent
227
- ?.split('##')
228
- .filter(
229
- (section) =>
230
- section.trim() && !section.includes('IDEAS šŸ’”') && !section.includes('Brain Dump')
231
- ).length || 0,
232
- }
233
-
234
- console.log('═══════════════════════════════════════════════════')
235
- console.log(` ${path.basename(projectPath)} - Status Overview`)
236
- console.log('═══════════════════════════════════════════════════\n')
237
-
238
- console.log('## šŸŽÆ Current Focus\n')
239
- if (stats.activeTask && nowContent) {
240
- const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
241
- const task = taskMatch ? taskMatch[1] : 'Active task'
242
- const startedMatch = nowContent.match(/Started: (.+)/)
243
- const started = startedMatch ? startedMatch[1] : 'Unknown'
244
- console.log(` šŸ“Œ ${task}`)
245
- console.log(` ā±ļø Started: ${started}\n`)
246
- } else {
247
- console.log(' No active task\n')
248
- }
249
-
250
- console.log('## šŸ“‹ Queue Status\n')
251
- console.log(` Tasks in Queue: ${stats.tasksInQueue}`)
252
- this._renderProgressBar('Queue Load', stats.tasksInQueue, 20)
253
- console.log('')
254
-
255
- console.log('## šŸš€ Shipped Features\n')
256
- console.log(` Features Shipped: ${stats.featuresShipped}`)
257
- this._renderProgressBar('Progress', stats.featuresShipped, 10)
258
- console.log('')
259
-
260
- console.log('## šŸ’” Ideas Backlog\n')
261
- console.log(` Ideas Captured: ${stats.ideasCaptured}`)
262
- this._renderProgressBar('Backlog', stats.ideasCaptured, 15)
263
- console.log('')
264
-
265
- console.log('## šŸ’š Overall Health\n')
266
- const health = this._calculateHealth(stats)
267
- console.log(` Health Score: ${health.score}/100`)
268
- this._renderProgressBar('Health', health.score, 100)
269
- console.log(` ${health.message}\n`)
270
-
271
- console.log('šŸ’” Next steps:')
272
- console.log('• /p:now → Start working on a task')
273
- console.log('• /p:feature → Add new feature')
274
- console.log('• /p:ship → Ship completed work')
275
-
276
- await this.logToMemory(projectPath, 'status_viewed', {
277
- stats,
278
- health: health.score,
279
- timestamp: dateHelper.getTimestamp(),
280
- })
281
-
282
- return { success: true, stats, health }
283
- } catch (error) {
284
- console.error('āŒ Error:', (error as Error).message)
285
- return { success: false, error: (error as Error).message }
299
+ private _generateSparkline(entries: MemoryEntry[], days: number): string {
300
+ const bars = ['▁', 'ā–‚', 'ā–ƒ', 'ā–„', 'ā–…', 'ā–†', 'ā–‡', 'ā–ˆ']
301
+ const now = new Date()
302
+ const counts: number[] = []
303
+
304
+ // Count entries per day
305
+ for (let i = days - 1; i >= 0; i--) {
306
+ const date = new Date(now)
307
+ date.setDate(date.getDate() - i)
308
+ const dayStart = new Date(date.setHours(0, 0, 0, 0))
309
+ const dayEnd = new Date(date.setHours(23, 59, 59, 999))
310
+
311
+ const count = entries.filter(e => {
312
+ const ts = new Date(e.timestamp)
313
+ return ts >= dayStart && ts <= dayEnd
314
+ }).length
315
+
316
+ counts.push(count)
286
317
  }
318
+
319
+ const max = Math.max(...counts, 1)
320
+ return counts.map(c => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
287
321
  }
288
322
  }
@@ -65,7 +65,7 @@ export class PrjctCommandsBase {
65
65
  throw new Error('Unsupported agent. Please use Claude Code, Claude Desktop, or Terminal.')
66
66
  }
67
67
 
68
- const Agent = require(`../infrastructure/agents/${this.agentInfo.type}-agent`)
68
+ const { default: Agent } = await import(`../infrastructure/agents/${this.agentInfo.type}-agent`)
69
69
  this.agent = new Agent()
70
70
 
71
71
  return this.agent