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.
- package/CHANGELOG.md +40 -0
- package/CLAUDE.md +74 -211
- package/core/agentic/prompt-builder.ts +3 -7
- package/core/command-registry/optional-commands.ts +0 -20
- package/core/infrastructure/command-installer/command-installer.ts +8 -1
- package/core/infrastructure/command-installer/global-config.ts +31 -1
- package/core/infrastructure/command-installer/index.ts +1 -1
- package/core/infrastructure/setup.ts +3 -0
- package/package.json +3 -17
- package/templates/commands/done.md +57 -258
- package/templates/commands/now.md +72 -277
- package/templates/commands/ship.md +55 -261
- package/templates/commands/test.md +328 -21
- package/templates/global/CLAUDE.md +40 -205
- package/templates/global/docs/agents.md +88 -0
- package/templates/global/docs/architecture.md +103 -0
- package/templates/global/docs/commands.md +98 -0
- package/templates/global/docs/validation.md +95 -0
- package/templates/mcp-config.json +36 -0
- package/bin/dev.js +0 -216
- package/bin/serve.js +0 -361
- package/packages/web/README.md +0 -36
- package/packages/web/app/api/claude/sessions/route.ts +0 -44
- package/packages/web/app/api/claude/status/route.ts +0 -34
- package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
- package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
- package/packages/web/app/api/projects/[id]/route.ts +0 -29
- package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
- package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
- package/packages/web/app/api/projects/route.ts +0 -16
- package/packages/web/app/api/sessions/current/route.ts +0 -132
- package/packages/web/app/api/sessions/history/route.ts +0 -204
- package/packages/web/app/error.tsx +0 -34
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +0 -198
- package/packages/web/app/layout.tsx +0 -53
- package/packages/web/app/loading.tsx +0 -7
- package/packages/web/app/not-found.tsx +0 -25
- package/packages/web/app/page.tsx +0 -12
- package/packages/web/app/project/[id]/code/layout.tsx +0 -18
- package/packages/web/app/project/[id]/code/page.tsx +0 -408
- package/packages/web/app/project/[id]/error.tsx +0 -41
- package/packages/web/app/project/[id]/loading.tsx +0 -9
- package/packages/web/app/project/[id]/not-found.tsx +0 -27
- package/packages/web/app/project/[id]/page.tsx +0 -384
- package/packages/web/app/project/[id]/reports/page.tsx +0 -59
- package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
- package/packages/web/app/sessions/page.tsx +0 -165
- package/packages/web/app/settings/page.tsx +0 -151
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
- package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
- package/packages/web/components/ActivityTimeline/index.ts +0 -2
- package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
- package/packages/web/components/AgentsCard/index.ts +0 -2
- package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
- package/packages/web/components/AppSidebar/index.ts +0 -1
- package/packages/web/components/BackLink/BackLink.tsx +0 -18
- package/packages/web/components/BackLink/BackLink.types.ts +0 -5
- package/packages/web/components/BackLink/index.ts +0 -2
- package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
- package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
- package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
- package/packages/web/components/BentoCard/index.ts +0 -2
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
- package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
- package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
- package/packages/web/components/BentoGrid/index.ts +0 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
- package/packages/web/components/BlockersCard/index.ts +0 -2
- package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
- package/packages/web/components/CommandBar/index.ts +0 -1
- package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
- package/packages/web/components/CommandButton/index.ts +0 -1
- package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
- package/packages/web/components/ConnectionStatus/index.ts +0 -1
- package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
- package/packages/web/components/DashboardContent/index.ts +0 -1
- package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
- package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
- package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
- package/packages/web/components/DateGroup/index.ts +0 -2
- package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
- package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
- package/packages/web/components/EmptyState/index.ts +0 -2
- package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
- package/packages/web/components/EventRow/EventRow.tsx +0 -49
- package/packages/web/components/EventRow/EventRow.types.ts +0 -7
- package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
- package/packages/web/components/EventRow/index.ts +0 -2
- package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
- package/packages/web/components/ExpandButton/index.ts +0 -2
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
- package/packages/web/components/HealthGradientBackground/index.ts +0 -2
- package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
- package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
- package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
- package/packages/web/components/HeroSection/hooks/index.ts +0 -2
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
- package/packages/web/components/HeroSection/index.ts +0 -2
- package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
- package/packages/web/components/IdeasCard/index.ts +0 -2
- package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
- package/packages/web/components/InsightMessage/index.ts +0 -2
- package/packages/web/components/Logo/Logo.tsx +0 -65
- package/packages/web/components/Logo/index.ts +0 -1
- package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
- package/packages/web/components/MarkdownContent/index.ts +0 -1
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
- package/packages/web/components/MasonryGrid/index.ts +0 -1
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
- package/packages/web/components/MomentumWidget/index.ts +0 -2
- package/packages/web/components/NowCard/NowCard.tsx +0 -118
- package/packages/web/components/NowCard/NowCard.types.ts +0 -16
- package/packages/web/components/NowCard/index.ts +0 -2
- package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
- package/packages/web/components/PageHeader/index.ts +0 -1
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
- package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
- package/packages/web/components/ProgressRing/index.ts +0 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
- package/packages/web/components/ProjectAvatar/index.ts +0 -1
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
- package/packages/web/components/ProjectColorDot/index.ts +0 -1
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
- package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
- package/packages/web/components/Providers/Providers.tsx +0 -48
- package/packages/web/components/Providers/index.ts +0 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
- package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
- package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
- package/packages/web/components/QueueCard/index.ts +0 -2
- package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
- package/packages/web/components/RecoverCard/index.ts +0 -2
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
- package/packages/web/components/RoadmapCard/index.ts +0 -2
- package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
- package/packages/web/components/ShipsCard/index.ts +0 -2
- package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
- package/packages/web/components/SparklineChart/index.ts +0 -2
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
- package/packages/web/components/StatsMasonry/index.ts +0 -1
- package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
- package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
- package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
- package/packages/web/components/StreakCard/index.ts +0 -2
- package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
- package/packages/web/components/TasksCounter/index.ts +0 -2
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
- package/packages/web/components/TechStackBadges/index.ts +0 -1
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
- package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
- package/packages/web/components/TerminalDock/index.ts +0 -2
- package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
- package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
- package/packages/web/components/TerminalTabs/index.ts +0 -1
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
- package/packages/web/components/VelocityBadge/index.ts +0 -2
- package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
- package/packages/web/components/VelocityCard/index.ts +0 -2
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
- package/packages/web/components/WeeklyReports/index.ts +0 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
- package/packages/web/components/WeeklySparkline/index.ts +0 -2
- package/packages/web/components/charts/SessionsChart.tsx +0 -175
- package/packages/web/components/ui/alert-dialog.tsx +0 -157
- package/packages/web/components/ui/badge.tsx +0 -46
- package/packages/web/components/ui/button.tsx +0 -60
- package/packages/web/components/ui/card.tsx +0 -92
- package/packages/web/components/ui/chart.tsx +0 -385
- package/packages/web/components/ui/dialog.tsx +0 -143
- package/packages/web/components/ui/drawer.tsx +0 -135
- package/packages/web/components/ui/dropdown-menu.tsx +0 -257
- package/packages/web/components/ui/input.tsx +0 -21
- package/packages/web/components/ui/scroll-area.tsx +0 -58
- package/packages/web/components/ui/select.tsx +0 -187
- package/packages/web/components/ui/sheet.tsx +0 -139
- package/packages/web/components/ui/tabs.tsx +0 -66
- package/packages/web/components/ui/tooltip.tsx +0 -61
- package/packages/web/components.json +0 -22
- package/packages/web/context/GlobalTerminalContext.tsx +0 -538
- package/packages/web/context/TerminalContext.tsx +0 -45
- package/packages/web/context/TerminalTabsContext.tsx +0 -181
- package/packages/web/eslint.config.mjs +0 -18
- package/packages/web/hooks/useClaudeTerminal.ts +0 -425
- package/packages/web/hooks/useProjectStats.ts +0 -93
- package/packages/web/hooks/useProjects.ts +0 -73
- package/packages/web/lib/actions/projects.ts +0 -15
- package/packages/web/lib/commands.ts +0 -81
- package/packages/web/lib/format.ts +0 -23
- package/packages/web/lib/generate-week-report.ts +0 -285
- package/packages/web/lib/parse-prjct-files.ts +0 -1123
- package/packages/web/lib/project-colors.ts +0 -58
- package/packages/web/lib/projects.ts +0 -506
- package/packages/web/lib/pty.ts +0 -101
- package/packages/web/lib/query-config.ts +0 -44
- package/packages/web/lib/services/index.ts +0 -9
- package/packages/web/lib/services/projects.server.ts +0 -66
- package/packages/web/lib/services/stats.server.ts +0 -562
- package/packages/web/lib/unified-loader.ts +0 -396
- package/packages/web/lib/utils.ts +0 -6
- package/packages/web/next-env.d.ts +0 -6
- package/packages/web/next.config.ts +0 -7
- package/packages/web/package.json +0 -57
- package/packages/web/postcss.config.mjs +0 -7
- package/packages/web/public/file.svg +0 -1
- package/packages/web/public/globe.svg +0 -1
- package/packages/web/public/next.svg +0 -1
- package/packages/web/public/vercel.svg +0 -1
- package/packages/web/public/window.svg +0 -1
- package/packages/web/server.ts +0 -312
- package/packages/web/tsconfig.json +0 -34
- 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
|
-
}
|