prjct-cli 0.18.2 → 0.19.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 (243) hide show
  1. package/CHANGELOG.md +40 -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/commands/done.md +57 -258
  11. package/templates/commands/now.md +72 -277
  12. package/templates/commands/ship.md +55 -261
  13. package/templates/commands/test.md +328 -21
  14. package/templates/global/CLAUDE.md +40 -205
  15. package/templates/global/docs/agents.md +88 -0
  16. package/templates/global/docs/architecture.md +103 -0
  17. package/templates/global/docs/commands.md +98 -0
  18. package/templates/global/docs/validation.md +95 -0
  19. package/templates/mcp-config.json +36 -0
  20. package/bin/dev.js +0 -216
  21. package/bin/serve.js +0 -361
  22. package/packages/web/README.md +0 -36
  23. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  24. package/packages/web/app/api/claude/status/route.ts +0 -34
  25. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  26. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  27. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  28. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  29. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  30. package/packages/web/app/api/projects/route.ts +0 -16
  31. package/packages/web/app/api/sessions/current/route.ts +0 -132
  32. package/packages/web/app/api/sessions/history/route.ts +0 -204
  33. package/packages/web/app/error.tsx +0 -34
  34. package/packages/web/app/favicon.ico +0 -0
  35. package/packages/web/app/globals.css +0 -198
  36. package/packages/web/app/layout.tsx +0 -53
  37. package/packages/web/app/loading.tsx +0 -7
  38. package/packages/web/app/not-found.tsx +0 -25
  39. package/packages/web/app/page.tsx +0 -12
  40. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  41. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  42. package/packages/web/app/project/[id]/error.tsx +0 -41
  43. package/packages/web/app/project/[id]/loading.tsx +0 -9
  44. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  45. package/packages/web/app/project/[id]/page.tsx +0 -384
  46. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  47. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  48. package/packages/web/app/sessions/page.tsx +0 -165
  49. package/packages/web/app/settings/page.tsx +0 -151
  50. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  51. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  52. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  53. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  55. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  56. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  57. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  58. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  59. package/packages/web/components/AgentsCard/index.ts +0 -2
  60. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  61. package/packages/web/components/AppSidebar/index.ts +0 -1
  62. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  63. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  64. package/packages/web/components/BackLink/index.ts +0 -2
  65. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  66. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  67. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  68. package/packages/web/components/BentoCard/index.ts +0 -2
  69. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  70. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  71. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  72. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  73. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  74. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  75. package/packages/web/components/BentoGrid/index.ts +0 -2
  76. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  77. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  78. package/packages/web/components/BlockersCard/index.ts +0 -2
  79. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  80. package/packages/web/components/CommandBar/index.ts +0 -1
  81. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  82. package/packages/web/components/CommandButton/index.ts +0 -1
  83. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  84. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  85. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  86. package/packages/web/components/DashboardContent/index.ts +0 -1
  87. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  88. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  89. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  90. package/packages/web/components/DateGroup/index.ts +0 -2
  91. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  92. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  93. package/packages/web/components/EmptyState/index.ts +0 -2
  94. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  95. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  96. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  97. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  98. package/packages/web/components/EventRow/index.ts +0 -2
  99. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  100. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  101. package/packages/web/components/ExpandButton/index.ts +0 -2
  102. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  103. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  104. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  105. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  106. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  107. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  108. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  109. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  110. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  111. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  112. package/packages/web/components/HeroSection/index.ts +0 -2
  113. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  114. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  115. package/packages/web/components/IdeasCard/index.ts +0 -2
  116. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  117. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  118. package/packages/web/components/InsightMessage/index.ts +0 -2
  119. package/packages/web/components/Logo/Logo.tsx +0 -65
  120. package/packages/web/components/Logo/index.ts +0 -1
  121. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  122. package/packages/web/components/MarkdownContent/index.ts +0 -1
  123. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  124. package/packages/web/components/MasonryGrid/index.ts +0 -1
  125. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  126. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  127. package/packages/web/components/MomentumWidget/index.ts +0 -2
  128. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  129. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  130. package/packages/web/components/NowCard/index.ts +0 -2
  131. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  132. package/packages/web/components/PageHeader/index.ts +0 -1
  133. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  134. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  135. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  136. package/packages/web/components/ProgressRing/index.ts +0 -2
  137. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  138. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  139. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  140. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  141. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  142. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  143. package/packages/web/components/Providers/Providers.tsx +0 -48
  144. package/packages/web/components/Providers/index.ts +0 -1
  145. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  146. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  147. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  148. package/packages/web/components/QueueCard/index.ts +0 -2
  149. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  150. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  151. package/packages/web/components/RecoverCard/index.ts +0 -2
  152. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  153. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  154. package/packages/web/components/RoadmapCard/index.ts +0 -2
  155. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  156. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  157. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  158. package/packages/web/components/ShipsCard/index.ts +0 -2
  159. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  160. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  161. package/packages/web/components/SparklineChart/index.ts +0 -2
  162. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  163. package/packages/web/components/StatsMasonry/index.ts +0 -1
  164. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  165. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  166. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  167. package/packages/web/components/StreakCard/index.ts +0 -2
  168. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  169. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  170. package/packages/web/components/TasksCounter/index.ts +0 -2
  171. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  172. package/packages/web/components/TechStackBadges/index.ts +0 -1
  173. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  174. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  175. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  176. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  177. package/packages/web/components/TerminalDock/index.ts +0 -2
  178. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  179. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  180. package/packages/web/components/TerminalTabs/index.ts +0 -1
  181. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  182. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  183. package/packages/web/components/VelocityBadge/index.ts +0 -2
  184. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  185. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  186. package/packages/web/components/VelocityCard/index.ts +0 -2
  187. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  188. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  189. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  190. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  191. package/packages/web/components/WeeklyReports/index.ts +0 -4
  192. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  193. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  194. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  195. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  196. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  197. package/packages/web/components/ui/badge.tsx +0 -46
  198. package/packages/web/components/ui/button.tsx +0 -60
  199. package/packages/web/components/ui/card.tsx +0 -92
  200. package/packages/web/components/ui/chart.tsx +0 -385
  201. package/packages/web/components/ui/dialog.tsx +0 -143
  202. package/packages/web/components/ui/drawer.tsx +0 -135
  203. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  204. package/packages/web/components/ui/input.tsx +0 -21
  205. package/packages/web/components/ui/scroll-area.tsx +0 -58
  206. package/packages/web/components/ui/select.tsx +0 -187
  207. package/packages/web/components/ui/sheet.tsx +0 -139
  208. package/packages/web/components/ui/tabs.tsx +0 -66
  209. package/packages/web/components/ui/tooltip.tsx +0 -61
  210. package/packages/web/components.json +0 -22
  211. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  212. package/packages/web/context/TerminalContext.tsx +0 -45
  213. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  214. package/packages/web/eslint.config.mjs +0 -18
  215. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  216. package/packages/web/hooks/useProjectStats.ts +0 -93
  217. package/packages/web/hooks/useProjects.ts +0 -73
  218. package/packages/web/lib/actions/projects.ts +0 -15
  219. package/packages/web/lib/commands.ts +0 -81
  220. package/packages/web/lib/format.ts +0 -23
  221. package/packages/web/lib/generate-week-report.ts +0 -285
  222. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  223. package/packages/web/lib/project-colors.ts +0 -58
  224. package/packages/web/lib/projects.ts +0 -506
  225. package/packages/web/lib/pty.ts +0 -101
  226. package/packages/web/lib/query-config.ts +0 -44
  227. package/packages/web/lib/services/index.ts +0 -9
  228. package/packages/web/lib/services/projects.server.ts +0 -66
  229. package/packages/web/lib/services/stats.server.ts +0 -562
  230. package/packages/web/lib/unified-loader.ts +0 -396
  231. package/packages/web/lib/utils.ts +0 -6
  232. package/packages/web/next-env.d.ts +0 -6
  233. package/packages/web/next.config.ts +0 -7
  234. package/packages/web/package.json +0 -57
  235. package/packages/web/postcss.config.mjs +0 -7
  236. package/packages/web/public/file.svg +0 -1
  237. package/packages/web/public/globe.svg +0 -1
  238. package/packages/web/public/next.svg +0 -1
  239. package/packages/web/public/vercel.svg +0 -1
  240. package/packages/web/public/window.svg +0 -1
  241. package/packages/web/server.ts +0 -312
  242. package/packages/web/tsconfig.json +0 -34
  243. 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
- }