prjct-cli 0.18.2 → 0.20.0

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 (246) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CLAUDE.md +74 -211
  3. package/core/agentic/prompt-builder.ts +3 -7
  4. package/core/command-registry/optional-commands.ts +0 -20
  5. package/core/infrastructure/command-installer/command-installer.ts +8 -1
  6. package/core/infrastructure/command-installer/global-config.ts +31 -1
  7. package/core/infrastructure/command-installer/index.ts +1 -1
  8. package/core/infrastructure/setup.ts +3 -0
  9. package/package.json +3 -17
  10. package/templates/agentic/agents/uxui.md +210 -0
  11. package/templates/commands/bug.md +219 -41
  12. package/templates/commands/done.md +57 -258
  13. package/templates/commands/feature.md +368 -80
  14. package/templates/commands/now.md +72 -277
  15. package/templates/commands/ship.md +167 -246
  16. package/templates/commands/sync.md +62 -3
  17. package/templates/commands/test.md +160 -20
  18. package/templates/global/CLAUDE.md +40 -205
  19. package/templates/global/docs/agents.md +88 -0
  20. package/templates/global/docs/architecture.md +103 -0
  21. package/templates/global/docs/commands.md +98 -0
  22. package/templates/global/docs/validation.md +95 -0
  23. package/bin/dev.js +0 -216
  24. package/bin/serve.js +0 -361
  25. package/packages/web/README.md +0 -36
  26. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  27. package/packages/web/app/api/claude/status/route.ts +0 -34
  28. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  29. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  30. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  31. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  32. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  33. package/packages/web/app/api/projects/route.ts +0 -16
  34. package/packages/web/app/api/sessions/current/route.ts +0 -132
  35. package/packages/web/app/api/sessions/history/route.ts +0 -204
  36. package/packages/web/app/error.tsx +0 -34
  37. package/packages/web/app/favicon.ico +0 -0
  38. package/packages/web/app/globals.css +0 -198
  39. package/packages/web/app/layout.tsx +0 -53
  40. package/packages/web/app/loading.tsx +0 -7
  41. package/packages/web/app/not-found.tsx +0 -25
  42. package/packages/web/app/page.tsx +0 -12
  43. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  44. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  45. package/packages/web/app/project/[id]/error.tsx +0 -41
  46. package/packages/web/app/project/[id]/loading.tsx +0 -9
  47. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  48. package/packages/web/app/project/[id]/page.tsx +0 -384
  49. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  50. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  51. package/packages/web/app/sessions/page.tsx +0 -165
  52. package/packages/web/app/settings/page.tsx +0 -151
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  55. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  56. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  57. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  58. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  59. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  60. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  61. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  62. package/packages/web/components/AgentsCard/index.ts +0 -2
  63. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  64. package/packages/web/components/AppSidebar/index.ts +0 -1
  65. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  66. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  67. package/packages/web/components/BackLink/index.ts +0 -2
  68. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  69. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  70. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  71. package/packages/web/components/BentoCard/index.ts +0 -2
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  74. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  75. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  76. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  77. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  78. package/packages/web/components/BentoGrid/index.ts +0 -2
  79. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  80. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  81. package/packages/web/components/BlockersCard/index.ts +0 -2
  82. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  83. package/packages/web/components/CommandBar/index.ts +0 -1
  84. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  85. package/packages/web/components/CommandButton/index.ts +0 -1
  86. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  87. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  88. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  89. package/packages/web/components/DashboardContent/index.ts +0 -1
  90. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  91. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  92. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  93. package/packages/web/components/DateGroup/index.ts +0 -2
  94. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  95. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  96. package/packages/web/components/EmptyState/index.ts +0 -2
  97. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  98. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  99. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  100. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  101. package/packages/web/components/EventRow/index.ts +0 -2
  102. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  103. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  104. package/packages/web/components/ExpandButton/index.ts +0 -2
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  107. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  108. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  109. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  110. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  111. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  112. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  113. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  114. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  115. package/packages/web/components/HeroSection/index.ts +0 -2
  116. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  117. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  118. package/packages/web/components/IdeasCard/index.ts +0 -2
  119. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  120. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  121. package/packages/web/components/InsightMessage/index.ts +0 -2
  122. package/packages/web/components/Logo/Logo.tsx +0 -65
  123. package/packages/web/components/Logo/index.ts +0 -1
  124. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  125. package/packages/web/components/MarkdownContent/index.ts +0 -1
  126. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  127. package/packages/web/components/MasonryGrid/index.ts +0 -1
  128. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  129. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  130. package/packages/web/components/MomentumWidget/index.ts +0 -2
  131. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  132. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  133. package/packages/web/components/NowCard/index.ts +0 -2
  134. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  135. package/packages/web/components/PageHeader/index.ts +0 -1
  136. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  137. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  138. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  139. package/packages/web/components/ProgressRing/index.ts +0 -2
  140. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  141. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  142. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  143. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  144. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  145. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  146. package/packages/web/components/Providers/Providers.tsx +0 -48
  147. package/packages/web/components/Providers/index.ts +0 -1
  148. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  149. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  150. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  151. package/packages/web/components/QueueCard/index.ts +0 -2
  152. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  153. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  154. package/packages/web/components/RecoverCard/index.ts +0 -2
  155. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  156. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  157. package/packages/web/components/RoadmapCard/index.ts +0 -2
  158. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  159. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  160. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  161. package/packages/web/components/ShipsCard/index.ts +0 -2
  162. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  163. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  164. package/packages/web/components/SparklineChart/index.ts +0 -2
  165. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  166. package/packages/web/components/StatsMasonry/index.ts +0 -1
  167. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  168. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  169. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  170. package/packages/web/components/StreakCard/index.ts +0 -2
  171. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  172. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  173. package/packages/web/components/TasksCounter/index.ts +0 -2
  174. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  175. package/packages/web/components/TechStackBadges/index.ts +0 -1
  176. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  177. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  178. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  179. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  180. package/packages/web/components/TerminalDock/index.ts +0 -2
  181. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  182. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  183. package/packages/web/components/TerminalTabs/index.ts +0 -1
  184. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  185. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  186. package/packages/web/components/VelocityBadge/index.ts +0 -2
  187. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  188. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  189. package/packages/web/components/VelocityCard/index.ts +0 -2
  190. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  191. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  192. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  193. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  194. package/packages/web/components/WeeklyReports/index.ts +0 -4
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  196. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  197. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  198. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  199. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  200. package/packages/web/components/ui/badge.tsx +0 -46
  201. package/packages/web/components/ui/button.tsx +0 -60
  202. package/packages/web/components/ui/card.tsx +0 -92
  203. package/packages/web/components/ui/chart.tsx +0 -385
  204. package/packages/web/components/ui/dialog.tsx +0 -143
  205. package/packages/web/components/ui/drawer.tsx +0 -135
  206. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  207. package/packages/web/components/ui/input.tsx +0 -21
  208. package/packages/web/components/ui/scroll-area.tsx +0 -58
  209. package/packages/web/components/ui/select.tsx +0 -187
  210. package/packages/web/components/ui/sheet.tsx +0 -139
  211. package/packages/web/components/ui/tabs.tsx +0 -66
  212. package/packages/web/components/ui/tooltip.tsx +0 -61
  213. package/packages/web/components.json +0 -22
  214. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  215. package/packages/web/context/TerminalContext.tsx +0 -45
  216. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  217. package/packages/web/eslint.config.mjs +0 -18
  218. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  219. package/packages/web/hooks/useProjectStats.ts +0 -93
  220. package/packages/web/hooks/useProjects.ts +0 -73
  221. package/packages/web/lib/actions/projects.ts +0 -15
  222. package/packages/web/lib/commands.ts +0 -81
  223. package/packages/web/lib/format.ts +0 -23
  224. package/packages/web/lib/generate-week-report.ts +0 -285
  225. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  226. package/packages/web/lib/project-colors.ts +0 -58
  227. package/packages/web/lib/projects.ts +0 -506
  228. package/packages/web/lib/pty.ts +0 -101
  229. package/packages/web/lib/query-config.ts +0 -44
  230. package/packages/web/lib/services/index.ts +0 -9
  231. package/packages/web/lib/services/projects.server.ts +0 -66
  232. package/packages/web/lib/services/stats.server.ts +0 -562
  233. package/packages/web/lib/unified-loader.ts +0 -396
  234. package/packages/web/lib/utils.ts +0 -6
  235. package/packages/web/next-env.d.ts +0 -6
  236. package/packages/web/next.config.ts +0 -7
  237. package/packages/web/package.json +0 -57
  238. package/packages/web/postcss.config.mjs +0 -7
  239. package/packages/web/public/file.svg +0 -1
  240. package/packages/web/public/globe.svg +0 -1
  241. package/packages/web/public/next.svg +0 -1
  242. package/packages/web/public/vercel.svg +0 -1
  243. package/packages/web/public/window.svg +0 -1
  244. package/packages/web/server.ts +0 -312
  245. package/packages/web/tsconfig.json +0 -34
  246. package/templates/commands/serve.md +0 -121
@@ -1,1123 +0,0 @@
1
- /**
2
- * Comprehensive parsers for prjct-cli data files
3
- * Extracts ALL rich data from the prjct storage system
4
- */
5
-
6
- import { readFile, readdir, stat } from 'fs/promises'
7
- import { join } from 'path'
8
- import { homedir } from 'os'
9
-
10
- // ============================================
11
- // TYPES - Full data model from prjct
12
- // ============================================
13
-
14
- export interface Author {
15
- name: string
16
- email: string
17
- username?: string
18
- }
19
-
20
- export interface CurrentTask {
21
- task: string
22
- started?: string
23
- duration?: string
24
- feature?: string
25
- phase?: string
26
- agent?: string
27
- estimate?: string
28
- priority?: string
29
- }
30
-
31
- export interface TaskEvent {
32
- ts: string
33
- type: 'task_start' | 'task_complete' | 'task_shipped'
34
- task: string
35
- feature?: string
36
- phase?: string
37
- agent?: string
38
- estimate?: string
39
- duration?: string
40
- started?: string
41
- completed?: string
42
- deliverables?: string[]
43
- files_modified?: number
44
- impact?: string
45
- progress?: string
46
- author?: Author
47
- }
48
-
49
- export interface ShipEvent {
50
- ts: string
51
- type: 'ship' | 'feature_ship' | 'feature_shipped'
52
- name: string
53
- feature?: string
54
- version?: string
55
- commit?: string
56
- tasks_done?: number
57
- duration?: string
58
- agent?: string
59
- impact?: string
60
- complexity?: string
61
- decision?: string
62
- details?: string
63
- author?: Author
64
- }
65
-
66
- export interface BugEvent {
67
- ts: string
68
- type: 'bug_report' | 'bug_fix'
69
- name?: string
70
- description: string
71
- severity: string
72
- files_modified?: number
73
- author?: Author
74
- }
75
-
76
- export interface SyncEvent {
77
- ts: string
78
- type: 'sync' | 'repository_analyzed' | 'agents_generated' | 'agents_removed'
79
- agents?: string[]
80
- stack?: string
81
- patterns?: number
82
- fileCount?: number
83
- gitCommits?: number
84
- reason?: string
85
- author?: Author
86
- }
87
-
88
- export interface CleanupEvent {
89
- ts: string
90
- type: 'cleanup'
91
- action?: string
92
- files_changed?: number
93
- lines_removed?: number
94
- lines_added?: number
95
- net_change?: number
96
- fixes?: Record<string, number>
97
- details?: string
98
- author?: Author
99
- }
100
-
101
- export interface RoadmapEvent {
102
- ts: string
103
- type: 'roadmap'
104
- action: string
105
- phases?: number
106
- features?: number
107
- estimated_weeks?: string
108
- author?: Author
109
- }
110
-
111
- export interface IdeaEvent {
112
- ts: string
113
- type: 'idea_captured' | 'feature_add'
114
- idea?: string
115
- name?: string
116
- priority?: string
117
- impact?: string
118
- effort?: string
119
- actionable?: boolean
120
- tasks_created?: number
121
- author?: Author
122
- }
123
-
124
- export interface StuckEvent {
125
- ts: string
126
- type: 'stuck'
127
- issue: string
128
- context?: string
129
- author?: Author
130
- }
131
-
132
- export type TimelineEvent =
133
- | TaskEvent
134
- | ShipEvent
135
- | BugEvent
136
- | SyncEvent
137
- | CleanupEvent
138
- | RoadmapEvent
139
- | IdeaEvent
140
- | StuckEvent
141
- | { ts: string; type: string; [key: string]: unknown }
142
-
143
- export interface Metrics {
144
- tasksStarted: number
145
- tasksCompleted: number
146
- inProgress: number
147
- totalTime: string
148
- daysActive: number
149
- velocity: {
150
- tasksPerDay: number
151
- featuresPerWeek?: number
152
- }
153
- testCoverage?: {
154
- total: number
155
- passing: number
156
- categories?: Record<string, { count: number; passing: number }>
157
- }
158
- codeQuality?: {
159
- linesAdded: number
160
- linesRemoved: number
161
- filesChanged: number
162
- }
163
- }
164
-
165
- export interface ShippedFeature {
166
- date: string
167
- name: string
168
- version?: string
169
- commit?: string
170
- type?: string
171
- agent?: string
172
- time?: string
173
- impact?: string
174
- filesChanged?: number
175
- linesAdded?: number
176
- linesRemoved?: number
177
- rootCause?: string
178
- solution?: string
179
- details?: string[]
180
- }
181
-
182
- export interface QueueItem {
183
- task: string
184
- priority: number
185
- feature?: string
186
- agent?: string
187
- estimate?: string
188
- status: 'pending' | 'in_progress'
189
- }
190
-
191
- export interface Idea {
192
- title: string
193
- status: string
194
- date?: string
195
- description?: string
196
- painPoints?: string[]
197
- solutions?: string[]
198
- impact?: string
199
- effort?: string
200
- }
201
-
202
- export interface RoadmapPhase {
203
- name: string
204
- status: 'completed' | 'in_progress' | 'queued'
205
- progress: number
206
- features?: RoadmapFeature[]
207
- }
208
-
209
- export interface RoadmapFeature {
210
- name: string
211
- status: 'completed' | 'in_progress' | 'queued'
212
- tasks: number
213
- tasksCompleted: number
214
- time?: string
215
- shippedDate?: string
216
- version?: string
217
- }
218
-
219
- export interface Agent {
220
- name: string
221
- role?: string
222
- responsibilities?: string[]
223
- whenToUse?: string[]
224
- }
225
-
226
- export interface SessionDay {
227
- date: string
228
- events: TimelineEvent[]
229
- tasksStarted: number
230
- tasksCompleted: number
231
- featuresShipped: number
232
- timeTracked?: string
233
- }
234
-
235
- export interface AnalysisData {
236
- fileCount: number
237
- commitCount: number
238
- stack: string
239
- techStack?: string[]
240
- structure?: string
241
- architecture?: string
242
- }
243
-
244
- export interface CodePatterns {
245
- moduleSystem?: Record<string, string>
246
- namingConventions?: Record<string, string>
247
- asyncPatterns?: Record<string, string>
248
- classStructure?: string
249
- }
250
-
251
- // Full project stats interface
252
- export interface ProjectStats {
253
- currentTask: CurrentTask | null
254
- metrics: Metrics
255
- shipped: ShippedFeature[]
256
- queue: QueueItem[]
257
- ideas: {
258
- pending: Idea[]
259
- archived: number
260
- implemented: number
261
- }
262
- roadmap: {
263
- phases: RoadmapPhase[]
264
- completedFeatures: number
265
- totalFeatures: number
266
- progress: number
267
- }
268
- agents: Agent[]
269
- timeline: TimelineEvent[]
270
- sessions: SessionDay[]
271
- analysis: AnalysisData
272
- patterns?: CodePatterns
273
- summary: {
274
- totalEvents: number
275
- firstActivity?: string
276
- lastActivity?: string
277
- activeDays: number
278
- totalTasksEver: number
279
- totalShipsEver: number
280
- totalBugsFixed: number
281
- totalCleanups: number
282
- }
283
- }
284
-
285
- // ============================================
286
- // HELPERS
287
- // ============================================
288
-
289
- async function safeReadFile(path: string): Promise<string> {
290
- try {
291
- return await readFile(path, 'utf-8')
292
- } catch {
293
- return ''
294
- }
295
- }
296
-
297
- async function safeReadDir(path: string): Promise<string[]> {
298
- try {
299
- return await readdir(path)
300
- } catch {
301
- return []
302
- }
303
- }
304
-
305
- async function fileExists(path: string): Promise<boolean> {
306
- try {
307
- await stat(path)
308
- return true
309
- } catch {
310
- return false
311
- }
312
- }
313
-
314
- export function getStoragePath(projectId: string): string {
315
- return join(homedir(), '.prjct-cli', 'projects', projectId)
316
- }
317
-
318
- // ============================================
319
- // PARSERS
320
- // ============================================
321
-
322
- // Parse now.md - Current task with full context
323
- export function parseNow(content: string): CurrentTask | null {
324
- if (!content || content.includes('No active task')) {
325
- return null
326
- }
327
-
328
- const lines = content.split('\n').filter(l => l.trim())
329
-
330
- // Get task line (first non-header, non-empty line)
331
- const taskLine = lines.find(l => !l.startsWith('#') && !l.startsWith('_') && l.trim())
332
- if (!taskLine) return null
333
-
334
- const task: CurrentTask = {
335
- task: taskLine.replace(/^[-*]\s*/, '').replace(/\*\*/g, '').trim()
336
- }
337
-
338
- // Extract metadata if present
339
- const startedMatch = content.match(/Started:\s*(.+)/i)
340
- const featureMatch = content.match(/Feature:\s*(.+)/i)
341
- const phaseMatch = content.match(/Phase:\s*(.+)/i)
342
- const agentMatch = content.match(/Agent:\s*(.+)/i)
343
- const estimateMatch = content.match(/Estimate:\s*(.+)/i)
344
- const priorityMatch = content.match(/Priority:\s*(.+)/i)
345
-
346
- if (startedMatch) task.started = startedMatch[1].trim()
347
- if (featureMatch) task.feature = featureMatch[1].trim()
348
- if (phaseMatch) task.phase = phaseMatch[1].trim()
349
- if (agentMatch) task.agent = agentMatch[1].trim()
350
- if (estimateMatch) task.estimate = estimateMatch[1].trim()
351
- if (priorityMatch) task.priority = priorityMatch[1].trim()
352
-
353
- return task
354
- }
355
-
356
- // Parse next.md - Priority queue with context
357
- export function parseQueue(content: string): QueueItem[] {
358
- const items: QueueItem[] = []
359
- if (!content) return items
360
-
361
- const lines = content.split('\n')
362
- let priority = 1
363
- let currentFeature: string | undefined
364
-
365
- for (const line of lines) {
366
- // Track feature headers
367
- const featureMatch = line.match(/^##\s+(.+)$/)
368
- if (featureMatch) {
369
- currentFeature = featureMatch[1].trim()
370
- continue
371
- }
372
-
373
- // Match items: 1. [ ] Task or - [ ] Task
374
- const itemMatch = line.match(/^(?:\d+\.|[-*])\s*\[([\sx])\]\s*(.+)$/i)
375
- if (itemMatch) {
376
- const isCompleted = itemMatch[1].toLowerCase() === 'x'
377
- if (isCompleted) continue // Skip completed
378
-
379
- const taskText = itemMatch[2].trim()
380
-
381
- // Extract agent if present: Task @agent
382
- const agentMatch = taskText.match(/@(\w+)$/)
383
- const estimateMatch = taskText.match(/\((\d+[hmd])\)/)
384
-
385
- items.push({
386
- task: taskText.replace(/@\w+$/, '').replace(/\(\d+[hmd]\)/, '').trim(),
387
- priority: priority++,
388
- feature: currentFeature,
389
- agent: agentMatch?.[1],
390
- estimate: estimateMatch?.[1],
391
- status: 'pending'
392
- })
393
- }
394
- }
395
-
396
- return items
397
- }
398
-
399
- // Parse metrics.md - Full metrics extraction
400
- export function parseMetrics(content: string): Metrics {
401
- const defaults: Metrics = {
402
- tasksStarted: 0,
403
- tasksCompleted: 0,
404
- inProgress: 0,
405
- totalTime: '0h',
406
- daysActive: 0,
407
- velocity: { tasksPerDay: 0 }
408
- }
409
-
410
- if (!content) return defaults
411
-
412
- // Basic metrics
413
- const started = content.match(/Tasks Started\*?\*?:\s*(\d+)/i)
414
- const completed = content.match(/(?:Tasks Completed|Total Tasks Shipped)\*?\*?:\s*(\d+)/i)
415
- const inProgress = content.match(/In Progress\*?\*?:\s*(\d+)/i)
416
- const totalTime = content.match(/(?:Total Time|Total Time Tracked)\*?\*?:\s*([^\n]+)/i)
417
- const daysActive = content.match(/Days Active\*?\*?:\s*(\d+)/i)
418
- const tasksPerDay = content.match(/(?:Tasks\/Day|Tasks per Day)\*?\*?:\s*([\d.]+)/i)
419
- const featuresPerWeek = content.match(/Features\/Week\*?\*?:\s*([\d.]+)/i)
420
-
421
- const metrics: Metrics = {
422
- tasksStarted: started ? parseInt(started[1]) : 0,
423
- tasksCompleted: completed ? parseInt(completed[1]) : 0,
424
- inProgress: inProgress ? parseInt(inProgress[1]) : 0,
425
- totalTime: totalTime ? totalTime[1].trim() : '0h',
426
- daysActive: daysActive ? parseInt(daysActive[1]) : 0,
427
- velocity: {
428
- tasksPerDay: tasksPerDay ? parseFloat(tasksPerDay[1]) : 0,
429
- featuresPerWeek: featuresPerWeek ? parseFloat(featuresPerWeek[1]) : undefined
430
- }
431
- }
432
-
433
- // Test coverage section
434
- const testSection = content.match(/##\s*Test Coverage([\s\S]*?)(?=##|$)/i)
435
- if (testSection) {
436
- const totalTests = testSection[1].match(/Total:\s*(\d+)/i)
437
- const passingTests = testSection[1].match(/(\d+)\s*passing/i)
438
- if (totalTests || passingTests) {
439
- metrics.testCoverage = {
440
- total: totalTests ? parseInt(totalTests[1]) : 0,
441
- passing: passingTests ? parseInt(passingTests[1]) : 0
442
- }
443
- }
444
- }
445
-
446
- // Code quality
447
- const linesAdded = content.match(/Lines Added\*?\*?:\s*(\d+)/i)
448
- const linesRemoved = content.match(/Lines Removed\*?\*?:\s*(\d+)/i)
449
- const filesChanged = content.match(/Files Changed\*?\*?:\s*(\d+)/i)
450
- if (linesAdded || linesRemoved || filesChanged) {
451
- metrics.codeQuality = {
452
- linesAdded: linesAdded ? parseInt(linesAdded[1]) : 0,
453
- linesRemoved: linesRemoved ? parseInt(linesRemoved[1]) : 0,
454
- filesChanged: filesChanged ? parseInt(filesChanged[1]) : 0
455
- }
456
- }
457
-
458
- return metrics
459
- }
460
-
461
- // Parse shipped.md - Full feature details
462
- export function parseShipped(content: string): ShippedFeature[] {
463
- const features: ShippedFeature[] = []
464
- if (!content) return features
465
-
466
- // Try ISO date format first: ## 2025-01-01
467
- const isoDateSections = content.split(/^##\s+(\d{4}-\d{2}-\d{2})/m)
468
-
469
- if (isoDateSections.length > 1) {
470
- // Process pairs: [before, date1, content1, date2, content2, ...]
471
- for (let i = 1; i < isoDateSections.length; i += 2) {
472
- const sectionDate = isoDateSections[i]
473
- const sectionContent = isoDateSections[i + 1] || ''
474
- parseShippedSection(sectionContent, sectionDate, features)
475
- }
476
- } else {
477
- // Try month format: ## November 2025 or ## December 2024
478
- const monthDateSections = content.split(/^##\s+(\w+\s+\d{4})/m)
479
-
480
- if (monthDateSections.length > 1) {
481
- for (let i = 1; i < monthDateSections.length; i += 2) {
482
- const sectionDate = monthDateSections[i]
483
- const sectionContent = monthDateSections[i + 1] || ''
484
- parseShippedSection(sectionContent, sectionDate, features)
485
- }
486
- } else {
487
- // No date headers - parse entire content with inline dates
488
- parseShippedSection(content, '', features)
489
- }
490
- }
491
-
492
- return features.slice(0, 30) // Last 30
493
- }
494
-
495
- function parseShippedSection(sectionContent: string, sectionDate: string, features: ShippedFeature[]) {
496
- // Parse bullet points: - **Name** (version) or - **Name** - 2025-01-01
497
- const bulletRegex = /^-\s+\*\*(.+?)\*\*(?:\s*\(([^)]+)\))?(?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/gm
498
- let match
499
- while ((match = bulletRegex.exec(sectionContent)) !== null) {
500
- const name = match[1].trim()
501
- const versionOrNote = match[2]?.trim()
502
- const inlineDate = match[3]
503
-
504
- // Extract version if it starts with 'v'
505
- const version = versionOrNote?.match(/^v[\d.]+/) ? versionOrNote : undefined
506
-
507
- features.push({
508
- date: inlineDate || sectionDate || new Date().toISOString().split('T')[0],
509
- name,
510
- version
511
- })
512
- }
513
-
514
- // Also check for ### Feature Name headers (alternative format)
515
- const headerRegex = /^###\s+(.+?)(?:\s+(v[\d.]+))?\s*$/gm
516
- while ((match = headerRegex.exec(sectionContent)) !== null) {
517
- const name = match[1].trim()
518
- const version = match[2]
519
-
520
- // Avoid duplicates if same feature in both formats
521
- if (!features.some(f => f.date === sectionDate && f.name === name)) {
522
- features.push({
523
- date: sectionDate || new Date().toISOString().split('T')[0],
524
- name,
525
- version
526
- })
527
- }
528
- }
529
- }
530
-
531
- // Parse ideas.md - Full idea structure
532
- export function parseIdeas(content: string): { pending: Idea[]; archived: number; implemented: number } {
533
- const pending: Idea[] = []
534
- let archived = 0
535
- let implemented = 0
536
-
537
- if (!content) return { pending, archived, implemented }
538
-
539
- const lines = content.split('\n')
540
- let inArchived = false
541
- let inImplemented = false
542
- let currentIdea: Partial<Idea> | null = null
543
- let collectingPainPoints = false
544
- let collectingSolutions = false
545
-
546
- for (const line of lines) {
547
- // Check section headers
548
- if (line.match(/^##.*Archived/i)) {
549
- inArchived = true
550
- inImplemented = false
551
- if (currentIdea?.title) pending.push(currentIdea as Idea)
552
- currentIdea = null
553
- continue
554
- }
555
- if (line.match(/^##.*Implemented/i)) {
556
- inImplemented = true
557
- inArchived = false
558
- if (currentIdea?.title) pending.push(currentIdea as Idea)
559
- currentIdea = null
560
- continue
561
- }
562
-
563
- // Date header: ## 2025-01-01
564
- const dateMatch = line.match(/^##\s*(\d{4}-\d{2}-\d{2})/)
565
- if (dateMatch) {
566
- if (currentIdea?.title && !inArchived && !inImplemented) {
567
- pending.push(currentIdea as Idea)
568
- }
569
- currentIdea = { date: dateMatch[1], status: 'PENDING' }
570
- inArchived = false
571
- inImplemented = false
572
- continue
573
- }
574
-
575
- // Idea title: ### Title
576
- const titleMatch = line.match(/^###\s+(.+)$/)
577
- if (titleMatch) {
578
- if (inArchived) {
579
- archived++
580
- continue
581
- }
582
- if (inImplemented) {
583
- implemented++
584
- continue
585
- }
586
-
587
- if (currentIdea?.title) {
588
- pending.push(currentIdea as Idea)
589
- }
590
- currentIdea = {
591
- title: titleMatch[1].trim(),
592
- status: 'PENDING',
593
- date: currentIdea?.date
594
- }
595
- collectingPainPoints = false
596
- collectingSolutions = false
597
- continue
598
- }
599
-
600
- if (!currentIdea || inArchived || inImplemented) continue
601
-
602
- // Status
603
- const statusMatch = line.match(/Status\*?\*?:\s*(\w+)/i)
604
- if (statusMatch) currentIdea.status = statusMatch[1]
605
-
606
- // Description
607
- const descMatch = line.match(/Description\*?\*?:\s*(.+)/i)
608
- if (descMatch) currentIdea.description = descMatch[1].trim()
609
-
610
- // Impact and effort
611
- const impactMatch = line.match(/Impact\*?\*?:\s*(\w+)/i)
612
- if (impactMatch) currentIdea.impact = impactMatch[1]
613
-
614
- const effortMatch = line.match(/Effort\*?\*?:\s*([^\n|]+)/i)
615
- if (effortMatch) currentIdea.effort = effortMatch[1].trim()
616
-
617
- // Pain points section
618
- if (line.match(/Pain Points/i)) {
619
- collectingPainPoints = true
620
- collectingSolutions = false
621
- currentIdea.painPoints = []
622
- continue
623
- }
624
- if (line.match(/Solutions/i)) {
625
- collectingSolutions = true
626
- collectingPainPoints = false
627
- currentIdea.solutions = []
628
- continue
629
- }
630
-
631
- // Collect list items
632
- const listMatch = line.match(/^[-*\d.]\s+(.+)$/)
633
- if (listMatch) {
634
- if (collectingPainPoints && currentIdea.painPoints) {
635
- currentIdea.painPoints.push(listMatch[1].trim())
636
- } else if (collectingSolutions && currentIdea.solutions) {
637
- currentIdea.solutions.push(listMatch[1].trim())
638
- }
639
- }
640
- }
641
-
642
- // Add last idea
643
- if (currentIdea?.title && !inArchived && !inImplemented) {
644
- pending.push(currentIdea as Idea)
645
- }
646
-
647
- return { pending, archived, implemented }
648
- }
649
-
650
- // Parse roadmap.md - Full roadmap structure
651
- export function parseRoadmap(content: string): { phases: RoadmapPhase[]; completedFeatures: number; totalFeatures: number; progress: number } {
652
- const phases: RoadmapPhase[] = []
653
- let completedFeatures = 0
654
- let totalFeatures = 0
655
-
656
- if (!content) return { phases, completedFeatures, totalFeatures, progress: 0 }
657
-
658
- // Split by phase headers
659
- const phaseSections = content.split(/^##\s+(?:Phase\s+)?/mi).filter(s => s.trim())
660
-
661
- for (const section of phaseSections) {
662
- const lines = section.split('\n')
663
- const header = lines[0]
664
-
665
- // Match: P1: Name or Phase 1: Name or just number
666
- const phaseMatch = header.match(/^(?:P)?(\d+)[:\s-]*(.*)$/i)
667
- if (!phaseMatch) continue
668
-
669
- const phaseNum = phaseMatch[1]
670
- const features: RoadmapFeature[] = []
671
- let phaseStatus: 'completed' | 'in_progress' | 'queued' = 'queued'
672
-
673
- // Check for status in section
674
- if (section.match(/Status\*?\*?:\s*(?:COMPLETED|Done)/i)) {
675
- phaseStatus = 'completed'
676
- } else if (section.match(/Status\*?\*?:\s*(?:IN.?PROGRESS|Active)/i)) {
677
- phaseStatus = 'in_progress'
678
- }
679
-
680
- // Parse features
681
- for (const line of lines) {
682
- // Feature line: - [x] Feature Name (tasks, time) - Shipped date
683
- const featureMatch = line.match(/^[-*]\s*\[([\sx])\]\s*(.+)/i)
684
- if (featureMatch) {
685
- totalFeatures++
686
- const isCompleted = featureMatch[1].toLowerCase() === 'x'
687
- if (isCompleted) completedFeatures++
688
-
689
- const featureText = featureMatch[2]
690
- const nameMatch = featureText.match(/^([^(]+)/)
691
- const tasksMatch = featureText.match(/\((\d+)\s*(?:tasks?|\/\d+)/)
692
- const shippedMatch = featureText.match(/Shipped\s+(\d{4}-\d{2}-\d{2})/i)
693
- const versionMatch = featureText.match(/\((v[\d.]+)\)/)
694
-
695
- if (nameMatch) {
696
- features.push({
697
- name: nameMatch[1].replace(/\*\*/g, '').trim(),
698
- status: isCompleted ? 'completed' : 'in_progress',
699
- tasks: tasksMatch ? parseInt(tasksMatch[1]) : 0,
700
- tasksCompleted: isCompleted ? (tasksMatch ? parseInt(tasksMatch[1]) : 0) : 0,
701
- shippedDate: shippedMatch?.[1],
702
- version: versionMatch?.[1]
703
- })
704
-
705
- // Update phase status based on features
706
- if (!isCompleted) phaseStatus = phaseStatus === 'completed' ? 'in_progress' : phaseStatus
707
- }
708
- }
709
- }
710
-
711
- // Calculate phase progress
712
- const completedInPhase = features.filter(f => f.status === 'completed').length
713
- const phaseProgress = features.length > 0
714
- ? Math.round((completedInPhase / features.length) * 100)
715
- : (phaseStatus === 'completed' ? 100 : 0)
716
-
717
- phases.push({
718
- name: `P${phaseNum}`,
719
- status: phaseStatus,
720
- progress: phaseProgress,
721
- features
722
- })
723
- }
724
-
725
- // Also check for progress table format
726
- const tableRows = content.match(/\|\s*P(\d+)[^|]*\|[^|]*\|\s*(\d+)%/gi)
727
- if (tableRows && phases.length === 0) {
728
- for (const row of tableRows) {
729
- const match = row.match(/P(\d+)[^|]*\|[^|]*\|\s*(\d+)%/)
730
- if (match) {
731
- const progress = parseInt(match[2])
732
- phases.push({
733
- name: `P${match[1]}`,
734
- status: progress === 100 ? 'completed' : progress > 0 ? 'in_progress' : 'queued',
735
- progress
736
- })
737
- }
738
- }
739
- }
740
-
741
- const overallProgress = totalFeatures > 0
742
- ? Math.round((completedFeatures / totalFeatures) * 100)
743
- : 0
744
-
745
- return { phases, completedFeatures, totalFeatures, progress: overallProgress }
746
- }
747
-
748
- // Parse context.jsonl - Full timeline with all event types
749
- export function parseTimeline(content: string): TimelineEvent[] {
750
- const events: TimelineEvent[] = []
751
- if (!content) return events
752
-
753
- const lines = content.split('\n').filter(l => l.trim())
754
-
755
- for (const line of lines) {
756
- try {
757
- const raw = JSON.parse(line)
758
- const ts = raw.ts || raw.timestamp || ''
759
- const type = raw.type || raw.action || 'unknown'
760
-
761
- // Normalize the event
762
- const event: TimelineEvent = {
763
- ts,
764
- type,
765
- ...raw
766
- }
767
-
768
- events.push(event)
769
- } catch {
770
- // Skip invalid JSON
771
- }
772
- }
773
-
774
- // Sort by timestamp descending (most recent first)
775
- return events.sort((a, b) => {
776
- const dateA = new Date(a.ts).getTime()
777
- const dateB = new Date(b.ts).getTime()
778
- return dateB - dateA
779
- })
780
- }
781
-
782
- // Parse session files from progress/sessions and memory/sessions
783
- export async function parseSessions(storagePath: string): Promise<SessionDay[]> {
784
- const sessions: Map<string, SessionDay> = new Map()
785
-
786
- const sessionDirs = [
787
- join(storagePath, 'progress', 'sessions'),
788
- join(storagePath, 'memory', 'sessions')
789
- ]
790
-
791
- for (const dir of sessionDirs) {
792
- // Check for monthly folders
793
- const months = await safeReadDir(dir)
794
-
795
- for (const month of months) {
796
- const monthPath = join(dir, month)
797
- const files = await safeReadDir(monthPath)
798
-
799
- for (const file of files) {
800
- if (!file.endsWith('.jsonl')) continue
801
-
802
- const date = file.replace('.jsonl', '')
803
- const content = await safeReadFile(join(monthPath, file))
804
- const events = parseTimeline(content)
805
-
806
- if (!sessions.has(date)) {
807
- sessions.set(date, {
808
- date,
809
- events: [],
810
- tasksStarted: 0,
811
- tasksCompleted: 0,
812
- featuresShipped: 0
813
- })
814
- }
815
-
816
- const day = sessions.get(date)!
817
- day.events.push(...events)
818
- day.tasksStarted += events.filter(e => e.type === 'task_start').length
819
- day.tasksCompleted += events.filter(e => e.type === 'task_complete').length
820
- day.featuresShipped += events.filter(e =>
821
- e.type === 'ship' || e.type === 'feature_ship' || e.type === 'feature_shipped'
822
- ).length
823
- }
824
- }
825
-
826
- // Also check root session files
827
- const rootFiles = await safeReadDir(dir)
828
- for (const file of rootFiles) {
829
- if (!file.endsWith('.jsonl')) continue
830
-
831
- const date = file.replace('.jsonl', '')
832
- if (date.match(/^\d{4}-\d{2}-\d{2}$/)) {
833
- const content = await safeReadFile(join(dir, file))
834
- const events = parseTimeline(content)
835
-
836
- if (!sessions.has(date)) {
837
- sessions.set(date, {
838
- date,
839
- events: [],
840
- tasksStarted: 0,
841
- tasksCompleted: 0,
842
- featuresShipped: 0
843
- })
844
- }
845
-
846
- const day = sessions.get(date)!
847
- day.events.push(...events)
848
- day.tasksStarted += events.filter(e => e.type === 'task_start').length
849
- day.tasksCompleted += events.filter(e => e.type === 'task_complete').length
850
- day.featuresShipped += events.filter(e =>
851
- e.type === 'ship' || e.type === 'feature_ship' || e.type === 'feature_shipped'
852
- ).length
853
- }
854
- }
855
- }
856
-
857
- // Sort by date descending
858
- return Array.from(sessions.values()).sort((a, b) =>
859
- new Date(b.date).getTime() - new Date(a.date).getTime()
860
- )
861
- }
862
-
863
- // Parse agents - Full agent details
864
- export async function parseAgents(storagePath: string): Promise<Agent[]> {
865
- const agents: Agent[] = []
866
- const agentsDir = join(storagePath, 'agents')
867
-
868
- const files = await safeReadDir(agentsDir)
869
-
870
- for (const file of files) {
871
- if (!file.endsWith('.md')) continue
872
-
873
- const name = file.replace('.md', '')
874
- const content = await safeReadFile(join(agentsDir, file))
875
-
876
- const agent: Agent = { name }
877
-
878
- // Parse role
879
- const roleMatch = content.match(/(?:Role|Description)\*?\*?:\s*([^\n]+)/i)
880
- if (roleMatch) agent.role = roleMatch[1].trim()
881
-
882
- // Parse responsibilities
883
- const respSection = content.match(/##\s*Responsibilities([\s\S]*?)(?=##|$)/i)
884
- if (respSection) {
885
- const items = respSection[1].match(/^[-*]\s+(.+)$/gm)
886
- if (items) {
887
- agent.responsibilities = items.map(i => i.replace(/^[-*]\s+/, '').trim())
888
- }
889
- }
890
-
891
- // Parse when to use
892
- const whenSection = content.match(/##\s*When to Use([\s\S]*?)(?=##|$)/i)
893
- if (whenSection) {
894
- const items = whenSection[1].match(/^[-*]\s+(.+)$/gm)
895
- if (items) {
896
- agent.whenToUse = items.map(i => i.replace(/^[-*]\s+/, '').trim())
897
- }
898
- }
899
-
900
- agents.push(agent)
901
- }
902
-
903
- return agents
904
- }
905
-
906
- // Parse repo-summary.md - Full analysis
907
- export function parseAnalysis(content: string): AnalysisData {
908
- const defaults: AnalysisData = { fileCount: 0, commitCount: 0, stack: 'Unknown' }
909
- if (!content) return defaults
910
-
911
- const fileMatch = content.match(/(\d+)\s*files/i)
912
- const commitMatch = content.match(/(?:Total Commits|Commits)\*?\*?:\s*(\d+)/i) || content.match(/(\d+)\s*commits/i)
913
- const stackMatch = content.match(/(?:Runtime|Tech Stack)[^:]*:\s*([^\n]+)/i)
914
-
915
- // Extract tech stack array
916
- const techStackSection = content.match(/##\s*(?:Tech Stack|Dependencies)([\s\S]*?)(?=##|$)/i)
917
- const techStack: string[] = []
918
- if (techStackSection) {
919
- const items = techStackSection[1].match(/[-*]\s+([^:\n]+)/g)
920
- if (items) {
921
- techStack.push(...items.map(i => i.replace(/^[-*]\s+/, '').trim()))
922
- }
923
- }
924
-
925
- // Extract structure
926
- const structureSection = content.match(/##\s*(?:Project )?Structure([\s\S]*?)(?=##|$)/i)
927
-
928
- // Extract architecture
929
- const archSection = content.match(/##\s*Architecture([\s\S]*?)(?=##|$)/i)
930
-
931
- return {
932
- fileCount: fileMatch ? parseInt(fileMatch[1]) : 0,
933
- commitCount: commitMatch ? parseInt(commitMatch[1]) : 0,
934
- stack: stackMatch ? stackMatch[1].trim().replace(/^[-*]\s*/, '') : 'Unknown',
935
- techStack: techStack.length > 0 ? techStack : undefined,
936
- structure: structureSection?.[1]?.trim(),
937
- architecture: archSection?.[1]?.trim()
938
- }
939
- }
940
-
941
- // Parse patterns.md - Code patterns
942
- export function parsePatterns(content: string): CodePatterns | undefined {
943
- if (!content) return undefined
944
-
945
- const patterns: CodePatterns = {}
946
-
947
- // Module system
948
- const moduleSection = content.match(/##\s*Module System([\s\S]*?)(?=##|$)/i)
949
- if (moduleSection) {
950
- const items = moduleSection[1].match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g)
951
- if (items) {
952
- patterns.moduleSystem = {}
953
- for (const item of items) {
954
- const match = item.match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/)
955
- if (match && !match[1].includes('Pattern')) {
956
- patterns.moduleSystem[match[1].trim()] = match[2].trim()
957
- }
958
- }
959
- }
960
- }
961
-
962
- // Naming conventions
963
- const namingSection = content.match(/##\s*Naming Conventions([\s\S]*?)(?=##|$)/i)
964
- if (namingSection) {
965
- const items = namingSection[1].match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g)
966
- if (items) {
967
- patterns.namingConventions = {}
968
- for (const item of items) {
969
- const match = item.match(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/)
970
- if (match && !match[1].includes('Element')) {
971
- patterns.namingConventions[match[1].trim()] = match[2].trim()
972
- }
973
- }
974
- }
975
- }
976
-
977
- return Object.keys(patterns).length > 0 ? patterns : undefined
978
- }
979
-
980
- // ============================================
981
- // MAIN EXPORT
982
- // ============================================
983
-
984
- export async function getProjectStats(projectId: string): Promise<ProjectStats> {
985
- const storagePath = getStoragePath(projectId)
986
-
987
- // Read all files in parallel
988
- const [
989
- nowContent,
990
- nextContent,
991
- metricsContent,
992
- shippedContent,
993
- ideasContent,
994
- roadmapContent,
995
- timelineContent,
996
- summaryContent,
997
- patternsContent,
998
- agents,
999
- sessions
1000
- ] = await Promise.all([
1001
- safeReadFile(join(storagePath, 'core', 'now.md')),
1002
- safeReadFile(join(storagePath, 'core', 'next.md')),
1003
- safeReadFile(join(storagePath, 'progress', 'metrics.md')),
1004
- safeReadFile(join(storagePath, 'progress', 'shipped.md')),
1005
- safeReadFile(join(storagePath, 'planning', 'ideas.md')),
1006
- safeReadFile(join(storagePath, 'planning', 'roadmap.md')),
1007
- safeReadFile(join(storagePath, 'memory', 'context.jsonl')),
1008
- safeReadFile(join(storagePath, 'analysis', 'repo-summary.md')),
1009
- safeReadFile(join(storagePath, 'analysis', 'patterns.md')),
1010
- parseAgents(storagePath),
1011
- parseSessions(storagePath)
1012
- ])
1013
-
1014
- const timeline = parseTimeline(timelineContent)
1015
- const ideas = parseIdeas(ideasContent)
1016
- const roadmap = parseRoadmap(roadmapContent)
1017
-
1018
- // Calculate summary stats from timeline
1019
- const taskStarts = timeline.filter(e => e.type === 'task_start')
1020
- const taskCompletes = timeline.filter(e => e.type === 'task_complete')
1021
- const ships = timeline.filter(e => ['ship', 'feature_ship', 'feature_shipped'].includes(e.type))
1022
- const bugFixes = timeline.filter(e => e.type === 'bug_fix')
1023
- const cleanups = timeline.filter(e => e.type === 'cleanup')
1024
-
1025
- const firstEvent = timeline[timeline.length - 1]
1026
- const lastEvent = timeline[0]
1027
-
1028
- // Calculate active days
1029
- const uniqueDays = new Set(timeline.map(e => e.ts?.split('T')[0]).filter(Boolean))
1030
-
1031
- return {
1032
- currentTask: parseNow(nowContent),
1033
- metrics: parseMetrics(metricsContent),
1034
- shipped: parseShipped(shippedContent),
1035
- queue: parseQueue(nextContent),
1036
- ideas,
1037
- roadmap,
1038
- agents,
1039
- timeline: timeline.slice(0, 100), // Last 100 events
1040
- sessions: sessions.slice(0, 30), // Last 30 days
1041
- analysis: parseAnalysis(summaryContent),
1042
- patterns: parsePatterns(patternsContent),
1043
- summary: {
1044
- totalEvents: timeline.length,
1045
- firstActivity: firstEvent?.ts,
1046
- lastActivity: lastEvent?.ts,
1047
- activeDays: uniqueDays.size,
1048
- totalTasksEver: taskStarts.length,
1049
- totalShipsEver: ships.length,
1050
- totalBugsFixed: bugFixes.length,
1051
- totalCleanups: cleanups.length
1052
- }
1053
- }
1054
- }
1055
-
1056
- // ============================================
1057
- // RAW FILES - Return markdown files as-is for rendering
1058
- // ============================================
1059
-
1060
- export interface RawProjectFiles {
1061
- shipped: string // progress/shipped.md
1062
- roadmap: string // planning/roadmap.md
1063
- ideas: string // planning/ideas.md
1064
- queue: string // core/next.md
1065
- now: string // core/now.md
1066
- context: string // core/context.md
1067
- timeline: string // memory/context.jsonl (raw for custom rendering)
1068
- agents: { name: string; content: string }[] // agents/*.md
1069
- }
1070
-
1071
- export async function getRawProjectFiles(projectId: string): Promise<RawProjectFiles> {
1072
- const storagePath = join(homedir(), '.prjct-cli', 'projects', projectId)
1073
-
1074
- // Read all files in parallel
1075
- const [
1076
- shipped,
1077
- roadmap,
1078
- ideas,
1079
- queue,
1080
- now,
1081
- context,
1082
- timeline,
1083
- agentFiles
1084
- ] = await Promise.all([
1085
- safeReadFile(join(storagePath, 'progress', 'shipped.md')),
1086
- safeReadFile(join(storagePath, 'planning', 'roadmap.md')),
1087
- safeReadFile(join(storagePath, 'planning', 'ideas.md')),
1088
- safeReadFile(join(storagePath, 'core', 'next.md')),
1089
- safeReadFile(join(storagePath, 'core', 'now.md')),
1090
- safeReadFile(join(storagePath, 'core', 'context.md')),
1091
- safeReadFile(join(storagePath, 'memory', 'context.jsonl')),
1092
- readAgentsRaw(join(storagePath, 'agents'))
1093
- ])
1094
-
1095
- return {
1096
- shipped,
1097
- roadmap,
1098
- ideas,
1099
- queue,
1100
- now,
1101
- context,
1102
- timeline,
1103
- agents: agentFiles
1104
- }
1105
- }
1106
-
1107
- async function readAgentsRaw(agentsDir: string): Promise<{ name: string; content: string }[]> {
1108
- try {
1109
- const files = await readdir(agentsDir)
1110
- const mdFiles = files.filter(f => f.endsWith('.md'))
1111
-
1112
- const agents = await Promise.all(
1113
- mdFiles.map(async (file) => ({
1114
- name: file.replace('.md', ''),
1115
- content: await safeReadFile(join(agentsDir, file))
1116
- }))
1117
- )
1118
-
1119
- return agents.filter(a => a.content.trim())
1120
- } catch {
1121
- return []
1122
- }
1123
- }