prjct-cli 0.13.3 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/setup.md +18 -3
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/templates/mcp-config.json +20 -1
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
package/core/view-generator.ts
DELETED
|
@@ -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
|
-
}
|