prjct-cli 0.13.3 ā 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.
- package/CHANGELOG.md +106 -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/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/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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Analytics Commands:
|
|
2
|
+
* Analytics Commands: dash, help
|
|
3
|
+
* Unified dashboard and contextual help - MD-First Architecture
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import path from 'path'
|
|
7
|
+
import registry from '../command-registry'
|
|
6
8
|
|
|
7
9
|
import type { CommandResult, Context } from './types'
|
|
8
10
|
import {
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
dateHelper,
|
|
16
18
|
out
|
|
17
19
|
} from './base'
|
|
20
|
+
import { stateStorage, queueStorage, shippedStorage, ideasStorage } from '../storage'
|
|
18
21
|
|
|
19
22
|
interface MemoryEntry {
|
|
20
23
|
timestamp: string
|
|
@@ -24,142 +27,165 @@ interface MemoryEntry {
|
|
|
24
27
|
|
|
25
28
|
export class AnalyticsCommands extends PrjctCommandsBase {
|
|
26
29
|
/**
|
|
27
|
-
* /p:
|
|
30
|
+
* /p:dash - Unified dashboard
|
|
31
|
+
* Views: default, week, month, roadmap, compact
|
|
28
32
|
*/
|
|
29
|
-
async
|
|
33
|
+
async dash(view: string = 'default', projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
30
34
|
try {
|
|
31
35
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
32
36
|
if (!initResult.success) return initResult
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const nextContent = (await toolRegistry.get('Read')!(context.paths.next)) as string | null
|
|
39
|
-
|
|
40
|
-
let task = 'none'
|
|
41
|
-
if (nowContent && !nowContent.includes('No current task')) {
|
|
42
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
43
|
-
task = taskMatch ? taskMatch[1] : 'active'
|
|
38
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
39
|
+
if (!projectId) {
|
|
40
|
+
out.fail('no project ID')
|
|
41
|
+
return { success: false, error: 'No project ID found' }
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
const
|
|
47
|
-
const queueCount = nextLines.length
|
|
44
|
+
const projectName = path.basename(projectPath)
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
out.done(`task: ${task} | queue: ${queueCount}`)
|
|
52
|
-
return { success: true }
|
|
53
|
-
} catch (error) {
|
|
54
|
-
out.fail((error as Error).message)
|
|
55
|
-
return { success: false, error: (error as Error).message }
|
|
56
|
-
}
|
|
57
|
-
}
|
|
46
|
+
// Get current task (from storage layer - JSON source of truth)
|
|
47
|
+
const currentTask = await stateStorage.getCurrentTask(projectId)
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
async recap(projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
63
|
-
try {
|
|
64
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
65
|
-
if (!initResult.success) return initResult
|
|
49
|
+
// Get queue
|
|
50
|
+
const queueTasks = await queueStorage.getActiveTasks(projectId)
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
const
|
|
52
|
+
// Get shipped (recent)
|
|
53
|
+
const shipped = await shippedStorage.getRecent(projectId, 5)
|
|
69
54
|
|
|
70
|
-
|
|
71
|
-
const
|
|
55
|
+
// Get ideas
|
|
56
|
+
const ideas = await ideasStorage.getPending(projectId)
|
|
72
57
|
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
if (view === 'compact') {
|
|
59
|
+
// One-liner status
|
|
60
|
+
const taskStatus = currentTask ? `šÆ ${currentTask.description.slice(0, 30)}` : 'š¤ idle'
|
|
61
|
+
const queueStatus = `š ${queueTasks.length}`
|
|
62
|
+
const shippedStatus = `š ${shipped.length}`
|
|
63
|
+
out.done(`${taskStatus} | ${queueStatus} | ${shippedStatus}`)
|
|
64
|
+
return { success: true, view: 'compact' }
|
|
65
|
+
}
|
|
75
66
|
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
if (view === 'week' || view === 'month') {
|
|
68
|
+
// Period-based metrics
|
|
69
|
+
const days = view === 'week' ? 7 : 30
|
|
70
|
+
const startDate = dateHelper.getDaysAgo(days)
|
|
71
|
+
|
|
72
|
+
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
73
|
+
let entries: MemoryEntry[] = []
|
|
74
|
+
try {
|
|
75
|
+
const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
|
|
76
|
+
entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
|
|
77
|
+
} catch { entries = [] }
|
|
78
|
+
|
|
79
|
+
const metrics = {
|
|
80
|
+
tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
|
|
81
|
+
featuresShipped: entries.filter((e) => e.action === 'feature_shipped').length,
|
|
82
|
+
totalActions: entries.length,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`\nš ${view.toUpperCase()} PROGRESS - ${projectName}\n`)
|
|
86
|
+
console.log('ā'.repeat(50))
|
|
87
|
+
console.log(` Tasks completed: ${metrics.tasksCompleted}`)
|
|
88
|
+
console.log(` Features shipped: ${metrics.featuresShipped}`)
|
|
89
|
+
console.log(` Total actions: ${metrics.totalActions}`)
|
|
90
|
+
console.log('ā'.repeat(50))
|
|
91
|
+
|
|
92
|
+
// ASCII sparkline
|
|
93
|
+
const sparkline = this._generateSparkline(entries, days)
|
|
94
|
+
console.log(`\n Activity: ${sparkline}\n`)
|
|
95
|
+
|
|
96
|
+
return { success: true, view, metrics }
|
|
97
|
+
}
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
if (view === 'roadmap') {
|
|
100
|
+
// Roadmap view
|
|
101
|
+
const context = await contextBuilder.build(projectPath) as Context
|
|
102
|
+
const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as string | null
|
|
103
|
+
|
|
104
|
+
console.log(`\nšŗļø ROADMAP - ${projectName}\n`)
|
|
105
|
+
console.log('ā'.repeat(50))
|
|
106
|
+
|
|
107
|
+
if (!roadmapContent || roadmapContent.trim() === '# ROADMAP') {
|
|
108
|
+
console.log(' No features planned yet.')
|
|
109
|
+
console.log(' Use /p:feature to add features.\n')
|
|
110
|
+
} else {
|
|
111
|
+
// Parse and display roadmap
|
|
112
|
+
const features = roadmapContent.split('##').filter(s => s.trim() && !s.includes('ROADMAP'))
|
|
113
|
+
features.slice(0, 5).forEach((f, i) => {
|
|
114
|
+
const name = f.split('\n')[0].trim()
|
|
115
|
+
console.log(` ${i + 1}. ${name}`)
|
|
116
|
+
})
|
|
117
|
+
if (features.length > 5) {
|
|
118
|
+
console.log(` ... and ${features.length - 5} more`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log('ā'.repeat(50) + '\n')
|
|
122
|
+
|
|
123
|
+
return { success: true, view: 'roadmap' }
|
|
124
|
+
}
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
// Default view - project overview
|
|
127
|
+
console.log(`\nš DASHBOARD - ${projectName}\n`)
|
|
128
|
+
console.log('ā'.repeat(50))
|
|
129
|
+
|
|
130
|
+
// Current task
|
|
131
|
+
console.log('\nšÆ CURRENT FOCUS')
|
|
132
|
+
if (currentTask) {
|
|
133
|
+
console.log(` ${currentTask.description}`)
|
|
134
|
+
if (currentTask.startedAt) {
|
|
135
|
+
const elapsed = dateHelper.calculateDuration(new Date(currentTask.startedAt))
|
|
136
|
+
console.log(` Started: ${elapsed} ago`)
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
console.log(' No active task. Use /p:work to start.')
|
|
140
|
+
}
|
|
92
141
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
142
|
+
// Queue
|
|
143
|
+
console.log('\nš QUEUE')
|
|
144
|
+
if (queueTasks.length === 0) {
|
|
145
|
+
console.log(' Queue is empty')
|
|
146
|
+
} else {
|
|
147
|
+
queueTasks.slice(0, 3).forEach((t, i) => {
|
|
148
|
+
const priority = t.priority ? `[${t.priority}]` : ''
|
|
149
|
+
console.log(` ${i + 1}. ${t.description.slice(0, 40)} ${priority}`)
|
|
150
|
+
})
|
|
151
|
+
if (queueTasks.length > 3) {
|
|
152
|
+
console.log(` ... and ${queueTasks.length - 3} more`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
100
155
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
// Recent ships
|
|
157
|
+
console.log('\nš RECENT SHIPS')
|
|
158
|
+
if (shipped.length === 0) {
|
|
159
|
+
console.log(' Nothing shipped yet')
|
|
160
|
+
} else {
|
|
161
|
+
shipped.slice(0, 3).forEach((s) => {
|
|
162
|
+
const date = s.shippedAt ? new Date(s.shippedAt).toLocaleDateString() : ''
|
|
163
|
+
console.log(` ⢠${s.name} ${date ? `(${date})` : ''}`)
|
|
164
|
+
})
|
|
104
165
|
}
|
|
105
166
|
|
|
106
|
-
|
|
167
|
+
// Ideas
|
|
168
|
+
console.log('\nš” IDEAS')
|
|
169
|
+
console.log(` ${ideas.length} pending ideas`)
|
|
107
170
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const packageJson = await analyzer.readPackageJson()
|
|
111
|
-
const detectedStack = packageJson?.name || 'project'
|
|
171
|
+
console.log('\n' + 'ā'.repeat(50))
|
|
172
|
+
console.log('š” /p:work to start | /p:done to complete | /p:ship to ship\n')
|
|
112
173
|
|
|
113
|
-
await this.logToMemory(projectPath, '
|
|
114
|
-
|
|
115
|
-
stack: detectedStack,
|
|
174
|
+
await this.logToMemory(projectPath, 'dash_viewed', {
|
|
175
|
+
view,
|
|
116
176
|
timestamp: dateHelper.getTimestamp(),
|
|
117
177
|
})
|
|
118
178
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
* /p:progress - Show metrics for period
|
|
129
|
-
*/
|
|
130
|
-
async progress(period: string = 'week', projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
131
|
-
try {
|
|
132
|
-
const initResult = await this.ensureProjectInit(projectPath)
|
|
133
|
-
if (!initResult.success) return initResult
|
|
134
|
-
|
|
135
|
-
const validPeriods = ['day', 'week', 'month', 'all']
|
|
136
|
-
if (!validPeriods.includes(period)) period = 'week'
|
|
137
|
-
|
|
138
|
-
out.spin(`loading ${period} progress...`)
|
|
139
|
-
|
|
140
|
-
const projectId = await configManager.getProjectId(projectPath)
|
|
141
|
-
const memoryPath = pathManager.getFilePath(projectId!, 'memory', 'context.jsonl')
|
|
142
|
-
|
|
143
|
-
const startDate = period === 'day' ? dateHelper.getDaysAgo(1) :
|
|
144
|
-
period === 'week' ? dateHelper.getDaysAgo(7) :
|
|
145
|
-
period === 'month' ? dateHelper.getDaysAgo(30) : new Date(0)
|
|
146
|
-
|
|
147
|
-
let entries: MemoryEntry[] = []
|
|
148
|
-
try {
|
|
149
|
-
const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
|
|
150
|
-
entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
|
|
151
|
-
} catch { entries = [] }
|
|
152
|
-
|
|
153
|
-
const metrics = {
|
|
154
|
-
tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
|
|
155
|
-
featuresShipped: entries.filter((e) => e.action === 'feature_shipped').length,
|
|
156
|
-
totalActions: entries.length,
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
view: 'default',
|
|
182
|
+
stats: {
|
|
183
|
+
currentTask: currentTask?.description || null,
|
|
184
|
+
queueCount: queueTasks.length,
|
|
185
|
+
shippedCount: shipped.length,
|
|
186
|
+
ideasCount: ideas.length
|
|
187
|
+
}
|
|
157
188
|
}
|
|
158
|
-
|
|
159
|
-
await this.logToMemory(projectPath, 'progress_viewed', { period, metrics, timestamp: dateHelper.getTimestamp() })
|
|
160
|
-
|
|
161
|
-
out.done(`${period}: ${metrics.tasksCompleted} tasks | ${metrics.featuresShipped} shipped`)
|
|
162
|
-
return { success: true, period, metrics }
|
|
163
189
|
} catch (error) {
|
|
164
190
|
out.fail((error as Error).message)
|
|
165
191
|
return { success: false, error: (error as Error).message }
|
|
@@ -167,28 +193,100 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
167
193
|
}
|
|
168
194
|
|
|
169
195
|
/**
|
|
170
|
-
* /p:
|
|
196
|
+
* /p:help - Contextual help and guidance
|
|
171
197
|
*/
|
|
172
|
-
async
|
|
198
|
+
async help(topic: string = '', projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
173
199
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
200
|
+
if (!topic) {
|
|
201
|
+
// Show command overview
|
|
202
|
+
console.log('\nš§ PRJCT COMMANDS\n')
|
|
203
|
+
console.log('ā'.repeat(50))
|
|
204
|
+
|
|
205
|
+
const categories = registry.getCategories()
|
|
206
|
+
const commands = registry.getAll()
|
|
207
|
+
|
|
208
|
+
// Group by category
|
|
209
|
+
const byCategory: Record<string, typeof commands> = {}
|
|
210
|
+
commands.forEach(cmd => {
|
|
211
|
+
if (cmd.deprecated) return
|
|
212
|
+
if (!byCategory[cmd.category]) byCategory[cmd.category] = []
|
|
213
|
+
byCategory[cmd.category].push(cmd)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
Object.entries(byCategory).forEach(([cat, cmds]) => {
|
|
217
|
+
const catInfo = categories[cat]
|
|
218
|
+
console.log(`\n${catInfo?.title || cat}:`)
|
|
219
|
+
cmds.forEach(cmd => {
|
|
220
|
+
const params = cmd.params ? ` ${cmd.params}` : ''
|
|
221
|
+
console.log(` ${cmd.name}${params}`)
|
|
222
|
+
console.log(` ${cmd.description}`)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
console.log('\n' + 'ā'.repeat(50))
|
|
227
|
+
console.log('š” Use /p:help <command> for detailed help\n')
|
|
228
|
+
|
|
229
|
+
return { success: true, topic: 'overview' }
|
|
230
|
+
}
|
|
180
231
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
232
|
+
// Topic-specific help
|
|
233
|
+
const command = registry.getByName(topic)
|
|
234
|
+
if (command) {
|
|
235
|
+
console.log(`\nš HELP: /p:${command.name}\n`)
|
|
236
|
+
console.log('ā'.repeat(50))
|
|
237
|
+
console.log(`Description: ${command.description}`)
|
|
238
|
+
|
|
239
|
+
if (command.params) {
|
|
240
|
+
console.log(`Parameters: ${command.params}`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (command.usage) {
|
|
244
|
+
console.log('\nUsage:')
|
|
245
|
+
if (command.usage.claude) console.log(` Claude: ${command.usage.claude}`)
|
|
246
|
+
if (command.usage.terminal) console.log(` Terminal: ${command.usage.terminal}`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (command.features) {
|
|
250
|
+
console.log('\nFeatures:')
|
|
251
|
+
command.features.forEach(f => console.log(` ⢠${f}`))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log('\n' + 'ā'.repeat(50) + '\n')
|
|
255
|
+
return { success: true, topic, command }
|
|
184
256
|
}
|
|
185
257
|
|
|
186
|
-
|
|
258
|
+
// Intent translation (like old /p:ask)
|
|
259
|
+
const intents: Record<string, { command: string; hint: string }> = {
|
|
260
|
+
'start': { command: 'work', hint: 'Start working on a task' },
|
|
261
|
+
'begin': { command: 'work', hint: 'Start working on a task' },
|
|
262
|
+
'finish': { command: 'done', hint: 'Mark current task complete' },
|
|
263
|
+
'complete': { command: 'done', hint: 'Mark current task complete' },
|
|
264
|
+
'deploy': { command: 'ship', hint: 'Ship a feature' },
|
|
265
|
+
'release': { command: 'ship', hint: 'Ship a feature' },
|
|
266
|
+
'status': { command: 'dash', hint: 'View project dashboard' },
|
|
267
|
+
'overview': { command: 'dash', hint: 'View project dashboard' },
|
|
268
|
+
'queue': { command: 'next', hint: 'View task queue' },
|
|
269
|
+
'tasks': { command: 'next', hint: 'View task queue' },
|
|
270
|
+
'add': { command: 'feature', hint: 'Add a new feature' },
|
|
271
|
+
'new': { command: 'feature', hint: 'Add a new feature' },
|
|
272
|
+
'break': { command: 'pause', hint: 'Pause current task' },
|
|
273
|
+
'stop': { command: 'pause', hint: 'Pause current task' },
|
|
274
|
+
'continue': { command: 'resume', hint: 'Resume paused task' },
|
|
275
|
+
'back': { command: 'resume', hint: 'Resume paused task' },
|
|
276
|
+
}
|
|
187
277
|
|
|
188
|
-
|
|
278
|
+
const lowerTopic = topic.toLowerCase()
|
|
279
|
+
for (const [intent, info] of Object.entries(intents)) {
|
|
280
|
+
if (lowerTopic.includes(intent)) {
|
|
281
|
+
console.log(`\nš” Did you mean /p:${info.command}?`)
|
|
282
|
+
console.log(` ${info.hint}\n`)
|
|
283
|
+
return { success: true, topic, suggestion: info.command }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
189
286
|
|
|
190
|
-
|
|
191
|
-
|
|
287
|
+
console.log(`\nā Unknown topic: ${topic}`)
|
|
288
|
+
console.log(' Use /p:help to see all commands\n')
|
|
289
|
+
return { success: false, error: `Unknown topic: ${topic}` }
|
|
192
290
|
} catch (error) {
|
|
193
291
|
out.fail((error as Error).message)
|
|
194
292
|
return { success: false, error: (error as Error).message }
|
|
@@ -196,93 +294,29 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
196
294
|
}
|
|
197
295
|
|
|
198
296
|
/**
|
|
199
|
-
*
|
|
297
|
+
* Generate ASCII sparkline for activity
|
|
200
298
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
?.split('\n')
|
|
220
|
-
.filter((line) => line.trim().match(/^\d+\./) || line.includes('[ ]')).length || 0,
|
|
221
|
-
featuresShipped:
|
|
222
|
-
shippedContent
|
|
223
|
-
?.split('##')
|
|
224
|
-
.filter((section) => section.trim() && !section.includes('SHIPPED š')).length || 0,
|
|
225
|
-
ideasCaptured:
|
|
226
|
-
ideasContent
|
|
227
|
-
?.split('##')
|
|
228
|
-
.filter(
|
|
229
|
-
(section) =>
|
|
230
|
-
section.trim() && !section.includes('IDEAS š”') && !section.includes('Brain Dump')
|
|
231
|
-
).length || 0,
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')
|
|
235
|
-
console.log(` ${path.basename(projectPath)} - Status Overview`)
|
|
236
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n')
|
|
237
|
-
|
|
238
|
-
console.log('## šÆ Current Focus\n')
|
|
239
|
-
if (stats.activeTask && nowContent) {
|
|
240
|
-
const taskMatch = nowContent.match(/\*\*(.+?)\*\*/)
|
|
241
|
-
const task = taskMatch ? taskMatch[1] : 'Active task'
|
|
242
|
-
const startedMatch = nowContent.match(/Started: (.+)/)
|
|
243
|
-
const started = startedMatch ? startedMatch[1] : 'Unknown'
|
|
244
|
-
console.log(` š ${task}`)
|
|
245
|
-
console.log(` ā±ļø Started: ${started}\n`)
|
|
246
|
-
} else {
|
|
247
|
-
console.log(' No active task\n')
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
console.log('## š Queue Status\n')
|
|
251
|
-
console.log(` Tasks in Queue: ${stats.tasksInQueue}`)
|
|
252
|
-
this._renderProgressBar('Queue Load', stats.tasksInQueue, 20)
|
|
253
|
-
console.log('')
|
|
254
|
-
|
|
255
|
-
console.log('## š Shipped Features\n')
|
|
256
|
-
console.log(` Features Shipped: ${stats.featuresShipped}`)
|
|
257
|
-
this._renderProgressBar('Progress', stats.featuresShipped, 10)
|
|
258
|
-
console.log('')
|
|
259
|
-
|
|
260
|
-
console.log('## š” Ideas Backlog\n')
|
|
261
|
-
console.log(` Ideas Captured: ${stats.ideasCaptured}`)
|
|
262
|
-
this._renderProgressBar('Backlog', stats.ideasCaptured, 15)
|
|
263
|
-
console.log('')
|
|
264
|
-
|
|
265
|
-
console.log('## š Overall Health\n')
|
|
266
|
-
const health = this._calculateHealth(stats)
|
|
267
|
-
console.log(` Health Score: ${health.score}/100`)
|
|
268
|
-
this._renderProgressBar('Health', health.score, 100)
|
|
269
|
-
console.log(` ${health.message}\n`)
|
|
270
|
-
|
|
271
|
-
console.log('š” Next steps:')
|
|
272
|
-
console.log('⢠/p:now ā Start working on a task')
|
|
273
|
-
console.log('⢠/p:feature ā Add new feature')
|
|
274
|
-
console.log('⢠/p:ship ā Ship completed work')
|
|
275
|
-
|
|
276
|
-
await this.logToMemory(projectPath, 'status_viewed', {
|
|
277
|
-
stats,
|
|
278
|
-
health: health.score,
|
|
279
|
-
timestamp: dateHelper.getTimestamp(),
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
return { success: true, stats, health }
|
|
283
|
-
} catch (error) {
|
|
284
|
-
console.error('ā Error:', (error as Error).message)
|
|
285
|
-
return { success: false, error: (error as Error).message }
|
|
299
|
+
private _generateSparkline(entries: MemoryEntry[], days: number): string {
|
|
300
|
+
const bars = ['ā', 'ā', 'ā', 'ā', 'ā
', 'ā', 'ā', 'ā']
|
|
301
|
+
const now = new Date()
|
|
302
|
+
const counts: number[] = []
|
|
303
|
+
|
|
304
|
+
// Count entries per day
|
|
305
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
306
|
+
const date = new Date(now)
|
|
307
|
+
date.setDate(date.getDate() - i)
|
|
308
|
+
const dayStart = new Date(date.setHours(0, 0, 0, 0))
|
|
309
|
+
const dayEnd = new Date(date.setHours(23, 59, 59, 999))
|
|
310
|
+
|
|
311
|
+
const count = entries.filter(e => {
|
|
312
|
+
const ts = new Date(e.timestamp)
|
|
313
|
+
return ts >= dayStart && ts <= dayEnd
|
|
314
|
+
}).length
|
|
315
|
+
|
|
316
|
+
counts.push(count)
|
|
286
317
|
}
|
|
318
|
+
|
|
319
|
+
const max = Math.max(...counts, 1)
|
|
320
|
+
return counts.map(c => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
|
|
287
321
|
}
|
|
288
322
|
}
|
package/core/commands/base.ts
CHANGED
|
@@ -65,7 +65,7 @@ export class PrjctCommandsBase {
|
|
|
65
65
|
throw new Error('Unsupported agent. Please use Claude Code, Claude Desktop, or Terminal.')
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const Agent =
|
|
68
|
+
const { default: Agent } = await import(`../infrastructure/agents/${this.agentInfo.type}-agent`)
|
|
69
69
|
this.agent = new Agent()
|
|
70
70
|
|
|
71
71
|
return this.agent
|