prjct-cli 0.13.2 → 0.15.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 (193) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/prjct +10 -13
  3. package/core/agentic/memory-system/semantic-memories.ts +2 -1
  4. package/core/agentic/plan-mode/plan-mode.ts +2 -1
  5. package/core/agentic/prompt-builder.ts +22 -43
  6. package/core/agentic/services.ts +5 -5
  7. package/core/agentic/smart-context.ts +7 -2
  8. package/core/command-registry/core-commands.ts +54 -29
  9. package/core/command-registry/optional-commands.ts +64 -0
  10. package/core/command-registry/setup-commands.ts +18 -3
  11. package/core/commands/analysis.ts +21 -68
  12. package/core/commands/analytics.ts +247 -213
  13. package/core/commands/base.ts +1 -1
  14. package/core/commands/index.ts +41 -36
  15. package/core/commands/maintenance.ts +300 -31
  16. package/core/commands/planning.ts +233 -22
  17. package/core/commands/setup.ts +3 -8
  18. package/core/commands/shipping.ts +14 -18
  19. package/core/commands/types.ts +8 -6
  20. package/core/commands/workflow.ts +105 -100
  21. package/core/context/generator.ts +317 -0
  22. package/core/context-sync.ts +7 -350
  23. package/core/data/index.ts +13 -32
  24. package/core/data/md-ideas-manager.ts +155 -0
  25. package/core/data/md-queue-manager.ts +4 -3
  26. package/core/data/md-shipped-manager.ts +90 -0
  27. package/core/data/md-state-manager.ts +11 -7
  28. package/core/domain/agent-generator.ts +23 -63
  29. package/core/events/index.ts +143 -0
  30. package/core/index.ts +17 -14
  31. package/core/infrastructure/capability-installer.ts +13 -149
  32. package/core/infrastructure/migrator/project-scanner.ts +2 -1
  33. package/core/infrastructure/path-manager.ts +4 -6
  34. package/core/infrastructure/setup.ts +3 -0
  35. package/core/infrastructure/uuid-migration.ts +750 -0
  36. package/core/outcomes/recorder.ts +2 -1
  37. package/core/plugin/loader.ts +4 -7
  38. package/core/plugin/registry.ts +3 -3
  39. package/core/schemas/index.ts +23 -25
  40. package/core/schemas/state.ts +1 -0
  41. package/core/serializers/ideas-serializer.ts +187 -0
  42. package/core/serializers/index.ts +16 -0
  43. package/core/serializers/shipped-serializer.ts +108 -0
  44. package/core/session/utils.ts +3 -9
  45. package/core/storage/ideas-storage.ts +273 -0
  46. package/core/storage/index.ts +204 -0
  47. package/core/storage/queue-storage.ts +297 -0
  48. package/core/storage/shipped-storage.ts +223 -0
  49. package/core/storage/state-storage.ts +235 -0
  50. package/core/storage/storage-manager.ts +175 -0
  51. package/package.json +1 -1
  52. package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
  53. package/packages/web/app/api/sessions/current/route.ts +132 -0
  54. package/packages/web/app/api/sessions/history/route.ts +96 -14
  55. package/packages/web/app/globals.css +5 -0
  56. package/packages/web/app/layout.tsx +2 -0
  57. package/packages/web/app/project/[id]/code/layout.tsx +18 -0
  58. package/packages/web/app/project/[id]/code/page.tsx +408 -0
  59. package/packages/web/app/project/[id]/page.tsx +359 -389
  60. package/packages/web/app/project/[id]/reports/page.tsx +59 -0
  61. package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
  62. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
  63. package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
  64. package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
  65. package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
  66. package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
  67. package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
  68. package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
  69. package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
  70. package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
  71. package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
  72. package/packages/web/components/CommandBar/index.ts +1 -0
  73. package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
  74. package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
  75. package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
  76. package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
  77. package/packages/web/components/EventRow/EventRow.tsx +4 -4
  78. package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
  79. package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
  80. package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
  81. package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
  82. package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
  83. package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
  84. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
  85. package/packages/web/components/MasonryGrid/index.ts +1 -0
  86. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
  87. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
  88. package/packages/web/components/MomentumWidget/index.ts +2 -0
  89. package/packages/web/components/NowCard/NowCard.tsx +81 -56
  90. package/packages/web/components/NowCard/NowCard.types.ts +1 -0
  91. package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
  92. package/packages/web/components/PageHeader/index.ts +1 -0
  93. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
  94. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
  95. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
  96. package/packages/web/components/ProjectColorDot/index.ts +1 -0
  97. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
  98. package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
  99. package/packages/web/components/Providers/Providers.tsx +4 -1
  100. package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
  101. package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
  102. package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
  103. package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
  104. package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
  105. package/packages/web/components/RecoverCard/index.ts +2 -0
  106. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
  107. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
  108. package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
  109. package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
  110. package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
  111. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
  112. package/packages/web/components/StatsMasonry/index.ts +1 -0
  113. package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
  114. package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
  115. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
  116. package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
  117. package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
  118. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
  119. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
  120. package/packages/web/components/TerminalDock/index.ts +2 -0
  121. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
  122. package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
  123. package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
  124. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
  125. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
  126. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
  127. package/packages/web/components/WeeklyReports/index.ts +4 -0
  128. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
  129. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
  130. package/packages/web/components/charts/SessionsChart.tsx +6 -3
  131. package/packages/web/components/ui/dialog.tsx +143 -0
  132. package/packages/web/components/ui/drawer.tsx +135 -0
  133. package/packages/web/components/ui/select.tsx +187 -0
  134. package/packages/web/context/GlobalTerminalContext.tsx +538 -0
  135. package/packages/web/lib/commands.ts +81 -0
  136. package/packages/web/lib/generate-week-report.ts +285 -0
  137. package/packages/web/lib/parse-prjct-files.ts +56 -55
  138. package/packages/web/lib/project-colors.ts +58 -0
  139. package/packages/web/lib/projects.ts +58 -5
  140. package/packages/web/lib/services/projects.server.ts +11 -1
  141. package/packages/web/next-env.d.ts +1 -1
  142. package/packages/web/package.json +5 -1
  143. package/templates/commands/analyze.md +39 -3
  144. package/templates/commands/ask.md +58 -3
  145. package/templates/commands/bug.md +117 -26
  146. package/templates/commands/dash.md +95 -158
  147. package/templates/commands/done.md +130 -148
  148. package/templates/commands/feature.md +125 -103
  149. package/templates/commands/git.md +18 -3
  150. package/templates/commands/idea.md +121 -38
  151. package/templates/commands/init.md +124 -20
  152. package/templates/commands/migrate-all.md +63 -28
  153. package/templates/commands/migrate.md +140 -0
  154. package/templates/commands/next.md +115 -5
  155. package/templates/commands/now.md +146 -82
  156. package/templates/commands/pause.md +89 -74
  157. package/templates/commands/redo.md +6 -4
  158. package/templates/commands/resume.md +141 -59
  159. package/templates/commands/ship.md +103 -231
  160. package/templates/commands/spec.md +98 -8
  161. package/templates/commands/suggest.md +22 -2
  162. package/templates/commands/sync.md +192 -203
  163. package/templates/commands/undo.md +6 -4
  164. package/core/data/agents-manager.ts +0 -76
  165. package/core/data/analysis-manager.ts +0 -83
  166. package/core/data/base-manager.ts +0 -156
  167. package/core/data/ideas-manager.ts +0 -81
  168. package/core/data/outcomes-manager.ts +0 -96
  169. package/core/data/project-manager.ts +0 -75
  170. package/core/data/roadmap-manager.ts +0 -118
  171. package/core/data/shipped-manager.ts +0 -65
  172. package/core/data/state-manager.ts +0 -214
  173. package/core/state/index.ts +0 -25
  174. package/core/state/manager.ts +0 -376
  175. package/core/state/types.ts +0 -185
  176. package/core/utils/project-capabilities.ts +0 -156
  177. package/core/view-generator.ts +0 -536
  178. package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
  179. package/packages/web/app/project/[id]/stats/page.tsx +0 -253
  180. package/templates/agent-assignment.md +0 -72
  181. package/templates/analysis/project-analysis.md +0 -78
  182. package/templates/checklists/accessibility.md +0 -33
  183. package/templates/commands/build.md +0 -17
  184. package/templates/commands/decision.md +0 -226
  185. package/templates/commands/fix.md +0 -79
  186. package/templates/commands/help.md +0 -61
  187. package/templates/commands/progress.md +0 -14
  188. package/templates/commands/recap.md +0 -14
  189. package/templates/commands/roadmap.md +0 -52
  190. package/templates/commands/status.md +0 -17
  191. package/templates/commands/task.md +0 -63
  192. package/templates/commands/work.md +0 -44
  193. package/templates/commands/workflow.md +0 -12
@@ -1,536 +0,0 @@
1
- /**
2
- * View Generator
3
- *
4
- * Generates MD views from JSON data files.
5
- * JSON is the source of truth, MD is the generated view for Claude.
6
- *
7
- * Flow: JSON (data/) → Generator → MD (views/)
8
- */
9
-
10
- import fs from 'fs/promises'
11
- import path from 'path'
12
- import {
13
- StateJson,
14
- QueueJson,
15
- IdeasJson,
16
- RoadmapJson,
17
- ShippedJson,
18
- ProjectSchema,
19
- DEFAULT_STATE,
20
- DEFAULT_QUEUE,
21
- DEFAULT_IDEAS,
22
- DEFAULT_ROADMAP,
23
- DEFAULT_SHIPPED,
24
- getProjectPath,
25
- getDataPath,
26
- getViewsPath,
27
- } from './schemas'
28
-
29
- // ============================================
30
- // HELPERS
31
- // ============================================
32
-
33
- async function readJson<T>(filePath: string, defaultValue: T): Promise<T> {
34
- try {
35
- const content = await fs.readFile(filePath, 'utf-8')
36
- return JSON.parse(content) as T
37
- } catch {
38
- return defaultValue
39
- }
40
- }
41
-
42
- async function writeFile(filePath: string, content: string): Promise<void> {
43
- const dir = path.dirname(filePath)
44
- await fs.mkdir(dir, { recursive: true })
45
- await fs.writeFile(filePath, content, 'utf-8')
46
- }
47
-
48
- function formatDate(isoString: string): string {
49
- if (!isoString) return 'Unknown'
50
- const date = new Date(isoString)
51
- return date.toLocaleDateString('en-US', {
52
- year: 'numeric',
53
- month: 'short',
54
- day: 'numeric',
55
- })
56
- }
57
-
58
- function formatDateTime(isoString: string): string {
59
- if (!isoString) return 'Unknown'
60
- const date = new Date(isoString)
61
- return date.toLocaleString('en-US', {
62
- year: 'numeric',
63
- month: 'short',
64
- day: 'numeric',
65
- hour: '2-digit',
66
- minute: '2-digit',
67
- })
68
- }
69
-
70
- function formatDuration(ms: number): string {
71
- const seconds = Math.floor(ms / 1000)
72
- const minutes = Math.floor(seconds / 60)
73
- const hours = Math.floor(minutes / 60)
74
- const days = Math.floor(hours / 24)
75
-
76
- if (days > 0) return `${days}d ${hours % 24}h`
77
- if (hours > 0) return `${hours}h ${minutes % 60}m`
78
- if (minutes > 0) return `${minutes}m`
79
- return `${seconds}s`
80
- }
81
-
82
- function timeAgo(isoString: string): string {
83
- if (!isoString) return ''
84
- const ms = Date.now() - new Date(isoString).getTime()
85
- return formatDuration(ms) + ' ago'
86
- }
87
-
88
- // ============================================
89
- // VIEW GENERATORS
90
- // ============================================
91
-
92
- /**
93
- * Generate now.md from state.json
94
- */
95
- export function generateNowView(state: StateJson): string {
96
- if (!state.currentTask) {
97
- return `# NOW
98
-
99
- No current task. Use \`/p:now <task>\` to start.
100
-
101
- ---
102
- **Quick Actions:**
103
- - \`/p:now "task description"\` - Start a new task
104
- - \`/p:next\` - View priority queue
105
- - \`/p:feature\` - Plan a new feature
106
- `
107
- }
108
-
109
- const { description, startedAt, sessionId, featureId } = state.currentTask
110
- const elapsed = timeAgo(startedAt)
111
-
112
- let content = `# NOW
113
-
114
- **${description}**
115
-
116
- | Info | Value |
117
- |------|-------|
118
- | Started | ${formatDateTime(startedAt)} (${elapsed}) |
119
- | Session | \`${sessionId}\` |`
120
-
121
- if (featureId) {
122
- content += `
123
- | Feature | \`${featureId}\` |`
124
- }
125
-
126
- content += `
127
-
128
- ---
129
- **Actions:**
130
- - \`/p:done\` - Complete this task
131
- - \`/p:pause\` - Pause and switch to something else
132
- `
133
-
134
- return content
135
- }
136
-
137
- /**
138
- * Generate next.md from queue.json
139
- */
140
- export function generateQueueView(queue: QueueJson): string {
141
- const pending = queue.tasks.filter(t => !t.completed)
142
- const completed = queue.tasks.filter(t => t.completed)
143
-
144
- if (pending.length === 0 && completed.length === 0) {
145
- return `# Priority Queue
146
-
147
- No tasks in queue. Use \`/p:feature\` to add tasks.
148
-
149
- ---
150
- **Quick Actions:**
151
- - \`/p:feature "description"\` - Add a new feature with tasks
152
- - \`/p:idea "text"\` - Capture a quick idea
153
- `
154
- }
155
-
156
- let content = `# Priority Queue
157
-
158
- > ${pending.length} pending task${pending.length !== 1 ? 's' : ''}
159
- `
160
-
161
- // Group by priority
162
- const critical = pending.filter(t => t.priority === 'critical')
163
- const high = pending.filter(t => t.priority === 'high')
164
- const medium = pending.filter(t => t.priority === 'medium')
165
- const low = pending.filter(t => t.priority === 'low')
166
-
167
- if (critical.length > 0) {
168
- content += `
169
- ## 🔴 Critical
170
-
171
- ${critical.map(t => `- [ ] ${t.description}${t.featureId ? ` *(${t.featureId})*` : ''}`).join('\n')}
172
- `
173
- }
174
-
175
- if (high.length > 0) {
176
- content += `
177
- ## 🟠 High
178
-
179
- ${high.map(t => `- [ ] ${t.description}${t.featureId ? ` *(${t.featureId})*` : ''}`).join('\n')}
180
- `
181
- }
182
-
183
- if (medium.length > 0) {
184
- content += `
185
- ## 🟡 Medium
186
-
187
- ${medium.map(t => `- [ ] ${t.description}${t.featureId ? ` *(${t.featureId})*` : ''}`).join('\n')}
188
- `
189
- }
190
-
191
- if (low.length > 0) {
192
- content += `
193
- ## 🟢 Low
194
-
195
- ${low.map(t => `- [ ] ${t.description}${t.featureId ? ` *(${t.featureId})*` : ''}`).join('\n')}
196
- `
197
- }
198
-
199
- if (completed.length > 0) {
200
- content += `
201
- ---
202
-
203
- ## ✅ Recently Completed
204
-
205
- ${completed.slice(0, 5).map(t => `- [x] ${t.description}`).join('\n')}
206
- `
207
- }
208
-
209
- content += `
210
- ---
211
- *Updated: ${formatDateTime(queue.lastUpdated)}*
212
- `
213
-
214
- return content
215
- }
216
-
217
- /**
218
- * Generate ideas.md from ideas.json
219
- */
220
- export function generateIdeasView(ideas: IdeasJson): string {
221
- const pending = ideas.ideas.filter(i => i.status === 'pending')
222
- const reviewing = ideas.ideas.filter(i => i.status === 'reviewing')
223
- const converted = ideas.ideas.filter(i => i.status === 'converted')
224
- const archived = ideas.ideas.filter(i => i.status === 'archived')
225
-
226
- if (ideas.ideas.length === 0) {
227
- return `# Ideas
228
-
229
- No ideas captured yet. Use \`/p:idea "text"\` to capture one.
230
-
231
- ---
232
- **Quick Actions:**
233
- - \`/p:idea "your idea"\` - Capture a new idea
234
- - \`/p:feature\` - Convert an idea to a feature
235
- `
236
- }
237
-
238
- let content = `# Ideas
239
-
240
- > ${pending.length} pending | ${reviewing.length} reviewing | ${converted.length} converted
241
- `
242
-
243
- if (pending.length > 0) {
244
- content += `
245
- ## 💡 Pending
246
-
247
- ${pending.map(i => {
248
- const tags = i.tags.length > 0 ? ` [${i.tags.join(', ')}]` : ''
249
- // Support both addedAt (new schema) and createdAt (legacy)
250
- const addedDate = i.addedAt || (i as unknown as { createdAt?: string }).createdAt || ''
251
- return `- **${i.text}**${tags}
252
- *Added: ${formatDate(addedDate)}*`
253
- }).join('\n\n')}
254
- `
255
- }
256
-
257
- if (reviewing.length > 0) {
258
- content += `
259
- ## 🔍 Under Review
260
-
261
- ${reviewing.map(i => `- **${i.text}**`).join('\n')}
262
- `
263
- }
264
-
265
- if (converted.length > 0) {
266
- content += `
267
- ## ✅ Converted to Features
268
-
269
- ${converted.slice(0, 5).map(i => `- ~~${i.text}~~ → \`${i.convertedTo}\``).join('\n')}
270
- `
271
- }
272
-
273
- content += `
274
- ---
275
- *Updated: ${formatDateTime(ideas.lastUpdated)}*
276
- `
277
-
278
- return content
279
- }
280
-
281
- /**
282
- * Generate roadmap.md from roadmap.json
283
- */
284
- export function generateRoadmapView(roadmap: RoadmapJson): string {
285
- const active = roadmap.features.filter(f => f.status === 'active')
286
- const planned = roadmap.features.filter(f => f.status === 'planned')
287
- const completed = roadmap.features.filter(f => f.status === 'completed')
288
- const shipped = roadmap.features.filter(f => f.status === 'shipped')
289
-
290
- if (roadmap.features.length === 0 && roadmap.backlog.length === 0) {
291
- return `# Roadmap
292
-
293
- No features planned yet. Use \`/p:feature "description"\` to add one.
294
-
295
- ---
296
- **Quick Actions:**
297
- - \`/p:feature "feature name"\` - Plan a new feature
298
- - \`/p:idea\` - Capture ideas for later
299
- `
300
- }
301
-
302
- let content = `# Roadmap
303
-
304
- > ${active.length} active | ${planned.length} planned | ${shipped.length} shipped
305
- `
306
-
307
- if (active.length > 0) {
308
- content += `
309
- ## 🚀 Active
310
-
311
- ${active.map(f => {
312
- const completedTasks = f.tasks.filter(t => t.completed).length
313
- const totalTasks = f.tasks.length
314
- const progress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0
315
-
316
- return `### ${f.name}
317
-
318
- - **Impact**: ${f.impact} | **Effort**: ${f.effort || 'TBD'}
319
- - **Progress**: ${progress}% (${completedTasks}/${totalTasks} tasks)
320
-
321
- **Tasks:**
322
- ${f.tasks.map(t => `- [${t.completed ? 'x' : ' '}] ${t.description}`).join('\n')}
323
- `
324
- }).join('\n')}
325
- `
326
- }
327
-
328
- if (planned.length > 0) {
329
- content += `
330
- ## 📋 Planned
331
-
332
- ${planned.map(f => `- **${f.name}** - Impact: ${f.impact}, Effort: ${f.effort || 'TBD'}`).join('\n')}
333
- `
334
- }
335
-
336
- if (shipped.length > 0) {
337
- content += `
338
- ## ✅ Recently Shipped
339
-
340
- ${shipped.slice(0, 5).map(f => `- **${f.name}**${f.version ? ` (${f.version})` : ''} - ${formatDate(f.shippedAt || '')}`).join('\n')}
341
- `
342
- }
343
-
344
- if (roadmap.backlog.length > 0) {
345
- content += `
346
- ## 📝 Backlog
347
-
348
- ${roadmap.backlog.map(item => `- ${item}`).join('\n')}
349
- `
350
- }
351
-
352
- content += `
353
- ---
354
- *Updated: ${formatDateTime(roadmap.lastUpdated)}*
355
- `
356
-
357
- return content
358
- }
359
-
360
- /**
361
- * Generate shipped.md from shipped.json
362
- */
363
- export function generateShippedView(shipped: ShippedJson): string {
364
- if (shipped.items.length === 0) {
365
- return `# Shipped
366
-
367
- Nothing shipped yet. Use \`/p:ship "feature"\` after completing work.
368
-
369
- ---
370
- **Quick Actions:**
371
- - \`/p:now\` - Start working on something
372
- - \`/p:ship "feature name"\` - Ship your work
373
- `
374
- }
375
-
376
- // Group by date (YYYY-MM-DD)
377
- const byDate = new Map<string, typeof shipped.items>()
378
- for (const item of shipped.items) {
379
- const date = item.shippedAt.split('T')[0]
380
- if (!byDate.has(date)) byDate.set(date, [])
381
- byDate.get(date)!.push(item)
382
- }
383
-
384
- let content = `# Shipped
385
-
386
- > ${shipped.items.length} item${shipped.items.length !== 1 ? 's' : ''} shipped
387
- `
388
-
389
- // Sort dates descending
390
- const sortedDates = Array.from(byDate.keys()).sort().reverse()
391
-
392
- for (const date of sortedDates.slice(0, 10)) {
393
- const items = byDate.get(date)!
394
- content += `
395
- ## ${formatDate(date + 'T00:00:00Z')}
396
-
397
- ${items.map(item => {
398
- let entry = `- ✅ **${item.name}**`
399
- if (item.version) entry += ` (${item.version})`
400
- entry += `\n - Type: ${item.type}`
401
-
402
- if (item.changes && item.changes.length > 0) {
403
- entry += `\n - Changes:`
404
- for (const change of item.changes.slice(0, 3)) {
405
- // Handle both string and ShipChange object formats
406
- const desc = typeof change === 'string' ? change : change.description
407
- entry += `\n - ${desc}`
408
- }
409
- }
410
-
411
- // Support both new (qualityMetrics) and legacy (metrics) formats
412
- const qm = item.qualityMetrics || (item as unknown as { metrics?: { lintStatus: string; testStatus: string } }).metrics
413
- if (qm) {
414
- entry += `\n - Lint: ${qm.lintStatus} | Tests: ${qm.testStatus}`
415
- }
416
-
417
- // Add duration if available
418
- if (item.duration) {
419
- const dur = item.duration
420
- const durStr = dur.hours > 0 ? `${dur.hours}h ${dur.minutes}m` : `${dur.minutes}m`
421
- entry += `\n - Duration: ${durStr}`
422
- }
423
-
424
- // Add code metrics if available
425
- if (item.codeMetrics) {
426
- const cm = item.codeMetrics
427
- entry += `\n - Files: ${cm.filesChanged} | +${cm.linesAdded}/-${cm.linesRemoved}`
428
- }
429
-
430
- return entry
431
- }).join('\n\n')}
432
- `
433
- }
434
-
435
- content += `
436
- ---
437
- *Updated: ${formatDateTime(shipped.lastUpdated)}*
438
- `
439
-
440
- return content
441
- }
442
-
443
- // ============================================
444
- // MAIN GENERATOR
445
- // ============================================
446
-
447
- interface GenerateResult {
448
- generated: string[]
449
- errors: string[]
450
- }
451
-
452
- /**
453
- * Generate all views for a project from JSON data
454
- */
455
- export async function generateViews(projectId: string): Promise<GenerateResult> {
456
- const dataPath = getDataPath(projectId)
457
- const viewsPath = getViewsPath(projectId)
458
-
459
- const generated: string[] = []
460
- const errors: string[] = []
461
-
462
- // Read all JSON data
463
- const state = await readJson<StateJson>(path.join(dataPath, 'state.json'), DEFAULT_STATE)
464
- const queue = await readJson<QueueJson>(path.join(dataPath, 'queue.json'), DEFAULT_QUEUE)
465
- const ideas = await readJson<IdeasJson>(path.join(dataPath, 'ideas.json'), DEFAULT_IDEAS)
466
- const roadmap = await readJson<RoadmapJson>(path.join(dataPath, 'roadmap.json'), DEFAULT_ROADMAP)
467
- const shipped = await readJson<ShippedJson>(path.join(dataPath, 'shipped.json'), DEFAULT_SHIPPED)
468
-
469
- // Generate views
470
- const views = [
471
- { name: 'now.md', content: generateNowView(state) },
472
- { name: 'next.md', content: generateQueueView(queue) },
473
- { name: 'ideas.md', content: generateIdeasView(ideas) },
474
- { name: 'roadmap.md', content: generateRoadmapView(roadmap) },
475
- { name: 'shipped.md', content: generateShippedView(shipped) },
476
- ]
477
-
478
- // Write views
479
- for (const view of views) {
480
- try {
481
- await writeFile(path.join(viewsPath, view.name), view.content)
482
- generated.push(view.name)
483
- } catch (err) {
484
- errors.push(`${view.name}: ${err instanceof Error ? err.message : 'Unknown error'}`)
485
- }
486
- }
487
-
488
- return { generated, errors }
489
- }
490
-
491
- /**
492
- * Generate a single view
493
- */
494
- export async function generateView(
495
- projectId: string,
496
- viewName: 'now' | 'next' | 'ideas' | 'roadmap' | 'shipped'
497
- ): Promise<void> {
498
- const dataPath = getDataPath(projectId)
499
- const viewsPath = getViewsPath(projectId)
500
-
501
- const generators: Record<string, () => Promise<string>> = {
502
- now: async () => {
503
- const state = await readJson<StateJson>(path.join(dataPath, 'state.json'), DEFAULT_STATE)
504
- return generateNowView(state)
505
- },
506
- next: async () => {
507
- const queue = await readJson<QueueJson>(path.join(dataPath, 'queue.json'), DEFAULT_QUEUE)
508
- return generateQueueView(queue)
509
- },
510
- ideas: async () => {
511
- const ideas = await readJson<IdeasJson>(path.join(dataPath, 'ideas.json'), DEFAULT_IDEAS)
512
- return generateIdeasView(ideas)
513
- },
514
- roadmap: async () => {
515
- const roadmap = await readJson<RoadmapJson>(path.join(dataPath, 'roadmap.json'), DEFAULT_ROADMAP)
516
- return generateRoadmapView(roadmap)
517
- },
518
- shipped: async () => {
519
- const shipped = await readJson<ShippedJson>(path.join(dataPath, 'shipped.json'), DEFAULT_SHIPPED)
520
- return generateShippedView(shipped)
521
- },
522
- }
523
-
524
- const content = await generators[viewName]()
525
- await writeFile(path.join(viewsPath, `${viewName}.md`), content)
526
- }
527
-
528
- export default {
529
- generateViews,
530
- generateView,
531
- generateNowView,
532
- generateQueueView,
533
- generateIdeasView,
534
- generateRoadmapView,
535
- generateShippedView,
536
- }
@@ -1,43 +0,0 @@
1
- import { BentoGrid } from '@/components/BentoGrid'
2
- import { BentoCardSkeleton } from '@/components/BentoCardSkeleton'
3
-
4
- export default function StatsLoading() {
5
- return (
6
- <div className="p-4 md:p-8 space-y-6 md:space-y-8">
7
- {/* Hero skeleton */}
8
- <div className="flex flex-col sm:flex-row items-center sm:items-start gap-4 sm:gap-6 pl-10 md:pl-0">
9
- <div className="h-16 w-16 md:h-20 md:w-20 rounded-full bg-muted animate-pulse" />
10
- <div className="space-y-3 text-center sm:text-left">
11
- <div className="h-12 md:h-16 w-24 md:w-32 bg-muted rounded animate-pulse mx-auto sm:mx-0" />
12
- <div className="h-4 w-40 md:w-48 bg-muted rounded animate-pulse mx-auto sm:mx-0" />
13
- </div>
14
- </div>
15
-
16
- {/* Bento grid skeleton */}
17
- <BentoGrid>
18
- <BentoCardSkeleton size="2x2" />
19
- <BentoCardSkeleton size="1x1" />
20
- <BentoCardSkeleton size="2x2" />
21
- <BentoCardSkeleton size="1x1" />
22
- <BentoCardSkeleton size="1x2" />
23
- <BentoCardSkeleton size="1x2" />
24
- <BentoCardSkeleton size="1x1" />
25
- <BentoCardSkeleton size="1x1" />
26
- </BentoGrid>
27
-
28
- {/* Timeline skeleton */}
29
- <div className="space-y-3">
30
- <div className="h-4 w-24 bg-muted rounded animate-pulse" />
31
- <div className="space-y-2">
32
- {Array.from({ length: 5 }).map((_, i) => (
33
- <div key={i} className="flex items-center gap-3">
34
- <div className="h-2 w-2 rounded-full bg-muted animate-pulse" />
35
- <div className="h-4 flex-1 bg-muted rounded animate-pulse" />
36
- <div className="h-3 w-16 bg-muted rounded animate-pulse" />
37
- </div>
38
- ))}
39
- </div>
40
- </div>
41
- </div>
42
- )
43
- }