prjct-cli 0.10.14 → 0.11.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 +19 -0
- package/bin/dev.js +217 -0
- package/bin/prjct +10 -0
- package/bin/serve.js +78 -0
- package/core/bus/index.js +322 -0
- package/core/command-registry.js +65 -0
- package/core/domain/snapshot-manager.js +375 -0
- package/core/plugin/hooks.js +313 -0
- package/core/plugin/index.js +52 -0
- package/core/plugin/loader.js +331 -0
- package/core/plugin/registry.js +325 -0
- package/core/plugins/webhook.js +143 -0
- package/core/session/index.js +449 -0
- package/core/session/metrics.js +293 -0
- package/package.json +18 -4
- package/templates/commands/done.md +176 -54
- package/templates/commands/history.md +176 -0
- package/templates/commands/init.md +28 -1
- package/templates/commands/now.md +191 -9
- package/templates/commands/pause.md +176 -12
- package/templates/commands/redo.md +142 -0
- package/templates/commands/resume.md +166 -62
- package/templates/commands/serve.md +121 -0
- package/templates/commands/ship.md +45 -1
- package/templates/commands/sync.md +34 -1
- package/templates/commands/undo.md +152 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionMetrics - Metrics Calculation and Aggregation
|
|
3
|
+
*
|
|
4
|
+
* Calculates productivity metrics from session data.
|
|
5
|
+
*
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs').promises
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const pathManager = require('../infrastructure/path-manager')
|
|
12
|
+
|
|
13
|
+
class SessionMetrics {
|
|
14
|
+
constructor(projectId) {
|
|
15
|
+
this.projectId = projectId
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get aggregated metrics for a time period
|
|
20
|
+
* @param {'day'|'week'|'month'|'all'} period
|
|
21
|
+
* @returns {Promise<Object>}
|
|
22
|
+
*/
|
|
23
|
+
async getMetrics(period = 'week') {
|
|
24
|
+
const sessions = await this.getSessionsForPeriod(period)
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
period,
|
|
28
|
+
totalSessions: sessions.length,
|
|
29
|
+
totalDuration: this.sumDurations(sessions),
|
|
30
|
+
totalDurationFormatted: this.formatDuration(this.sumDurations(sessions)),
|
|
31
|
+
averageDuration: this.averageDuration(sessions),
|
|
32
|
+
averageDurationFormatted: this.formatDuration(this.averageDuration(sessions)),
|
|
33
|
+
tasksCompleted: sessions.filter(s => s.status === 'completed').length,
|
|
34
|
+
filesChanged: this.sumMetric(sessions, 'filesChanged'),
|
|
35
|
+
linesAdded: this.sumMetric(sessions, 'linesAdded'),
|
|
36
|
+
linesRemoved: this.sumMetric(sessions, 'linesRemoved'),
|
|
37
|
+
commits: this.sumMetric(sessions, 'commits'),
|
|
38
|
+
productivityScore: this.calculateProductivityScore(sessions),
|
|
39
|
+
streak: await this.calculateStreak(),
|
|
40
|
+
byDay: this.groupByDay(sessions)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get sessions for a given period
|
|
46
|
+
* @param {string} period
|
|
47
|
+
* @returns {Promise<Array>}
|
|
48
|
+
*/
|
|
49
|
+
async getSessionsForPeriod(period) {
|
|
50
|
+
const globalPath = pathManager.getGlobalProjectPath(this.projectId)
|
|
51
|
+
const archiveDir = path.join(globalPath, 'sessions', 'archive')
|
|
52
|
+
|
|
53
|
+
const sessions = []
|
|
54
|
+
const cutoffDate = this.getCutoffDate(period)
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const months = await fs.readdir(archiveDir)
|
|
58
|
+
|
|
59
|
+
for (const month of months) {
|
|
60
|
+
const monthDir = path.join(archiveDir, month)
|
|
61
|
+
const files = await fs.readdir(monthDir)
|
|
62
|
+
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
if (!file.endsWith('.json')) continue
|
|
65
|
+
|
|
66
|
+
const content = await fs.readFile(path.join(monthDir, file), 'utf-8')
|
|
67
|
+
const session = JSON.parse(content)
|
|
68
|
+
|
|
69
|
+
if (new Date(session.completedAt) >= cutoffDate) {
|
|
70
|
+
sessions.push(session)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Archive might not exist
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Also check current session
|
|
79
|
+
try {
|
|
80
|
+
const currentPath = path.join(globalPath, 'sessions', 'current.json')
|
|
81
|
+
const content = await fs.readFile(currentPath, 'utf-8')
|
|
82
|
+
const current = JSON.parse(content)
|
|
83
|
+
if (new Date(current.startedAt) >= cutoffDate) {
|
|
84
|
+
sessions.push(current)
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// No current session
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return sessions
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get cutoff date for period
|
|
95
|
+
* @param {string} period
|
|
96
|
+
* @returns {Date}
|
|
97
|
+
*/
|
|
98
|
+
getCutoffDate(period) {
|
|
99
|
+
const now = new Date()
|
|
100
|
+
|
|
101
|
+
switch (period) {
|
|
102
|
+
case 'day':
|
|
103
|
+
return new Date(now.setHours(0, 0, 0, 0))
|
|
104
|
+
case 'week':
|
|
105
|
+
const weekAgo = new Date(now)
|
|
106
|
+
weekAgo.setDate(weekAgo.getDate() - 7)
|
|
107
|
+
return weekAgo
|
|
108
|
+
case 'month':
|
|
109
|
+
const monthAgo = new Date(now)
|
|
110
|
+
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
|
111
|
+
return monthAgo
|
|
112
|
+
case 'all':
|
|
113
|
+
default:
|
|
114
|
+
return new Date(0) // Beginning of time
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sum durations from sessions
|
|
120
|
+
* @param {Array} sessions
|
|
121
|
+
* @returns {number} Total seconds
|
|
122
|
+
*/
|
|
123
|
+
sumDurations(sessions) {
|
|
124
|
+
return sessions.reduce((sum, s) => sum + (s.duration || 0), 0)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Calculate average duration
|
|
129
|
+
* @param {Array} sessions
|
|
130
|
+
* @returns {number} Average seconds
|
|
131
|
+
*/
|
|
132
|
+
averageDuration(sessions) {
|
|
133
|
+
if (sessions.length === 0) return 0
|
|
134
|
+
return Math.round(this.sumDurations(sessions) / sessions.length)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sum a specific metric
|
|
139
|
+
* @param {Array} sessions
|
|
140
|
+
* @param {string} metric
|
|
141
|
+
* @returns {number}
|
|
142
|
+
*/
|
|
143
|
+
sumMetric(sessions, metric) {
|
|
144
|
+
return sessions.reduce((sum, s) => {
|
|
145
|
+
return sum + (s.metrics?.[metric] || 0)
|
|
146
|
+
}, 0)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Calculate productivity score (0-100)
|
|
151
|
+
* Based on consistency, duration, and output
|
|
152
|
+
* @param {Array} sessions
|
|
153
|
+
* @returns {number}
|
|
154
|
+
*/
|
|
155
|
+
calculateProductivityScore(sessions) {
|
|
156
|
+
if (sessions.length === 0) return 0
|
|
157
|
+
|
|
158
|
+
// Factors:
|
|
159
|
+
// 1. Session count (more = better, up to a point)
|
|
160
|
+
const sessionScore = Math.min(sessions.length / 10, 1) * 30
|
|
161
|
+
|
|
162
|
+
// 2. Average duration (25-90 min is optimal)
|
|
163
|
+
const avgMins = this.averageDuration(sessions) / 60
|
|
164
|
+
let durationScore = 0
|
|
165
|
+
if (avgMins >= 25 && avgMins <= 90) {
|
|
166
|
+
durationScore = 30
|
|
167
|
+
} else if (avgMins > 0) {
|
|
168
|
+
durationScore = Math.max(0, 30 - Math.abs(avgMins - 57.5) / 2)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 3. Completion rate
|
|
172
|
+
const completedCount = sessions.filter(s => s.status === 'completed').length
|
|
173
|
+
const completionScore = (completedCount / sessions.length) * 20
|
|
174
|
+
|
|
175
|
+
// 4. Output (commits + files changed)
|
|
176
|
+
const totalOutput = this.sumMetric(sessions, 'commits') + this.sumMetric(sessions, 'filesChanged')
|
|
177
|
+
const outputScore = Math.min(totalOutput / 50, 1) * 20
|
|
178
|
+
|
|
179
|
+
return Math.round(sessionScore + durationScore + completionScore + outputScore)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Calculate current streak (consecutive days with sessions)
|
|
184
|
+
* @returns {Promise<number>}
|
|
185
|
+
*/
|
|
186
|
+
async calculateStreak() {
|
|
187
|
+
const sessions = await this.getSessionsForPeriod('month')
|
|
188
|
+
|
|
189
|
+
// Get unique dates with sessions
|
|
190
|
+
const dates = new Set(
|
|
191
|
+
sessions.map(s => {
|
|
192
|
+
const d = new Date(s.completedAt || s.startedAt)
|
|
193
|
+
return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Count consecutive days from today
|
|
198
|
+
let streak = 0
|
|
199
|
+
const today = new Date()
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < 30; i++) {
|
|
202
|
+
const checkDate = new Date(today)
|
|
203
|
+
checkDate.setDate(checkDate.getDate() - i)
|
|
204
|
+
const key = `${checkDate.getFullYear()}-${checkDate.getMonth()}-${checkDate.getDate()}`
|
|
205
|
+
|
|
206
|
+
if (dates.has(key)) {
|
|
207
|
+
streak++
|
|
208
|
+
} else if (i > 0) {
|
|
209
|
+
break // Streak broken
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return streak
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Group sessions by day
|
|
218
|
+
* @param {Array} sessions
|
|
219
|
+
* @returns {Object}
|
|
220
|
+
*/
|
|
221
|
+
groupByDay(sessions) {
|
|
222
|
+
const byDay = {}
|
|
223
|
+
|
|
224
|
+
for (const session of sessions) {
|
|
225
|
+
const date = new Date(session.completedAt || session.startedAt)
|
|
226
|
+
const key = date.toISOString().split('T')[0]
|
|
227
|
+
|
|
228
|
+
if (!byDay[key]) {
|
|
229
|
+
byDay[key] = {
|
|
230
|
+
sessions: 0,
|
|
231
|
+
duration: 0,
|
|
232
|
+
commits: 0
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
byDay[key].sessions++
|
|
237
|
+
byDay[key].duration += session.duration || 0
|
|
238
|
+
byDay[key].commits += session.metrics?.commits || 0
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return byDay
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format duration as human readable
|
|
246
|
+
* @param {number} seconds
|
|
247
|
+
* @returns {string}
|
|
248
|
+
*/
|
|
249
|
+
formatDuration(seconds) {
|
|
250
|
+
if (seconds < 60) return `${seconds}s`
|
|
251
|
+
if (seconds < 3600) return `${Math.round(seconds / 60)}m`
|
|
252
|
+
|
|
253
|
+
const hours = Math.floor(seconds / 3600)
|
|
254
|
+
const minutes = Math.round((seconds % 3600) / 60)
|
|
255
|
+
|
|
256
|
+
if (minutes === 0) return `${hours}h`
|
|
257
|
+
return `${hours}h ${minutes}m`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Generate metrics summary for display
|
|
262
|
+
* @param {'day'|'week'|'month'} period
|
|
263
|
+
* @returns {Promise<string>}
|
|
264
|
+
*/
|
|
265
|
+
async generateSummary(period = 'week') {
|
|
266
|
+
const m = await this.getMetrics(period)
|
|
267
|
+
|
|
268
|
+
const periodLabel = {
|
|
269
|
+
day: 'Today',
|
|
270
|
+
week: 'This Week',
|
|
271
|
+
month: 'This Month',
|
|
272
|
+
all: 'All Time'
|
|
273
|
+
}[period]
|
|
274
|
+
|
|
275
|
+
return `
|
|
276
|
+
## ${periodLabel}
|
|
277
|
+
|
|
278
|
+
| Metric | Value |
|
|
279
|
+
|--------|-------|
|
|
280
|
+
| Sessions | ${m.totalSessions} |
|
|
281
|
+
| Total Time | ${m.totalDurationFormatted} |
|
|
282
|
+
| Avg Session | ${m.averageDurationFormatted} |
|
|
283
|
+
| Tasks Done | ${m.tasksCompleted} |
|
|
284
|
+
| Files Changed | ${m.filesChanged} |
|
|
285
|
+
| Lines +/- | +${m.linesAdded} / -${m.linesRemoved} |
|
|
286
|
+
| Commits | ${m.commits} |
|
|
287
|
+
| Streak | ${m.streak} days |
|
|
288
|
+
| Score | ${m.productivityScore}/100 |
|
|
289
|
+
`.trim()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = SessionMetrics
|
package/package.json
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prjct-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"prjct": "bin/prjct"
|
|
7
|
+
"prjct": "bin/prjct",
|
|
8
|
+
"prjct-dev": "bin/dev.js",
|
|
9
|
+
"prjct-serve": "bin/serve.js"
|
|
8
10
|
},
|
|
11
|
+
"workspaces": [
|
|
12
|
+
"packages/*"
|
|
13
|
+
],
|
|
9
14
|
"publishConfig": {
|
|
10
15
|
"access": "public",
|
|
11
16
|
"registry": "https://registry.npmjs.org"
|
|
12
17
|
},
|
|
13
18
|
"scripts": {
|
|
19
|
+
"dev": "turbo run dev",
|
|
20
|
+
"build": "turbo run build",
|
|
21
|
+
"serve": "turbo run dev --filter=@prjct/server --filter=@prjct/web",
|
|
14
22
|
"postinstall": "node scripts/postinstall.js",
|
|
15
23
|
"update-commands": "node -e \"const installer = require('./core/infrastructure/command-installer'); installer.syncCommands().then(r => console.log('Commands updated:', r)).catch(e => console.error('Error:', e.message))\"",
|
|
16
24
|
"install-global": "./scripts/install.sh",
|
|
@@ -41,6 +49,7 @@
|
|
|
41
49
|
"dependencies": {
|
|
42
50
|
"chalk": "^4.1.2",
|
|
43
51
|
"glob": "^10.3.10",
|
|
52
|
+
"lightningcss": "^1.30.2",
|
|
44
53
|
"prompts": "^2.4.2"
|
|
45
54
|
},
|
|
46
55
|
"devDependencies": {
|
|
@@ -52,6 +61,7 @@
|
|
|
52
61
|
"eslint-plugin-n": "^16.6.2",
|
|
53
62
|
"eslint-plugin-promise": "^6.6.0",
|
|
54
63
|
"prettier": "^3.6.2",
|
|
64
|
+
"turbo": "^2.3.0",
|
|
55
65
|
"vitest": "^3.2.4"
|
|
56
66
|
},
|
|
57
67
|
"repository": {
|
|
@@ -62,6 +72,7 @@
|
|
|
62
72
|
"url": "https://github.com/jlopezlira/prjct-cli/issues"
|
|
63
73
|
},
|
|
64
74
|
"homepage": "https://prjct.app",
|
|
75
|
+
"packageManager": "npm@10.2.4",
|
|
65
76
|
"engines": {
|
|
66
77
|
"node": ">=18.0.0"
|
|
67
78
|
},
|
|
@@ -79,5 +90,8 @@
|
|
|
79
90
|
"trustedDependencies": [
|
|
80
91
|
"chalk",
|
|
81
92
|
"prompts"
|
|
82
|
-
]
|
|
83
|
-
|
|
93
|
+
],
|
|
94
|
+
"overrides": {
|
|
95
|
+
"esbuild": "^0.25.0"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
allowed-tools: [Read, Write]
|
|
3
|
-
description: 'Complete current task'
|
|
2
|
+
allowed-tools: [Read, Write, Bash]
|
|
3
|
+
description: 'Complete current task with session metrics'
|
|
4
4
|
timestamp-rule: 'GetTimestamp() for all timestamps'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /p:done - Complete Current Task
|
|
7
|
+
# /p:done - Complete Current Task with Session Metrics
|
|
8
8
|
|
|
9
9
|
## Context Variables
|
|
10
10
|
- `{projectId}`: From `.prjct/prjct.config.json`
|
|
11
11
|
- `{globalPath}`: `~/.prjct-cli/projects/{projectId}`
|
|
12
12
|
- `{nowPath}`: `{globalPath}/core/now.md`
|
|
13
|
+
- `{sessionPath}`: `{globalPath}/sessions/current.json`
|
|
14
|
+
- `{archiveDir}`: `{globalPath}/sessions/archive`
|
|
13
15
|
- `{memoryPath}`: `{globalPath}/memory/context.jsonl`
|
|
14
16
|
- `{metricsPath}`: `{globalPath}/progress/metrics.md`
|
|
15
17
|
|
|
@@ -22,116 +24,236 @@ IF file not found:
|
|
|
22
24
|
OUTPUT: "No prjct project. Run /p:init first."
|
|
23
25
|
STOP
|
|
24
26
|
|
|
25
|
-
## Step 2:
|
|
27
|
+
## Step 2: Check Session State
|
|
26
28
|
|
|
29
|
+
### Try structured session first
|
|
30
|
+
READ: `{sessionPath}`
|
|
31
|
+
|
|
32
|
+
IF file exists:
|
|
33
|
+
PARSE as JSON
|
|
34
|
+
EXTRACT: {session} object
|
|
35
|
+
GOTO Step 3 (Session Completion)
|
|
36
|
+
|
|
37
|
+
### Fallback to legacy now.md
|
|
27
38
|
READ: `{nowPath}`
|
|
28
39
|
|
|
29
40
|
IF empty OR contains "No current task":
|
|
30
41
|
OUTPUT: "⚠️ No active task to complete. Use /p:now to start one."
|
|
31
42
|
STOP
|
|
32
43
|
|
|
33
|
-
## Step 3:
|
|
44
|
+
## Step 3: Session Completion
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
### Calculate Final Duration
|
|
47
|
+
SET: {now} = GetTimestamp()
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
For each event in {session.timeline}:
|
|
50
|
+
Track start/resume/pause/complete times
|
|
51
|
+
Calculate total active time
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- Example: `Started: 11/28/2025, 2:30:00 PM`
|
|
53
|
+
SET: {duration} = total active seconds
|
|
54
|
+
SET: {durationFormatted} = format as "Xh Ym" or "Xm"
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- Format: "Xh Ym" (e.g., "2h 15m")
|
|
49
|
-
- If < 1 hour: "Xm"
|
|
50
|
-
- If < 1 minute: "< 1m"
|
|
56
|
+
### Calculate Git Metrics
|
|
57
|
+
BASH: `git rev-list --count --since="{session.startedAt}" HEAD 2>/dev/null || echo "0"`
|
|
58
|
+
CAPTURE as {commits}
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
BASH: `git diff --stat HEAD~{commits} 2>/dev/null || git diff --stat`
|
|
61
|
+
PARSE output for:
|
|
62
|
+
- {filesChanged}: number of files
|
|
63
|
+
- {linesAdded}: insertions
|
|
64
|
+
- {linesRemoved}: deletions
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
### Update Session Object
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"id": "{session.id}",
|
|
70
|
+
"projectId": "{projectId}",
|
|
71
|
+
"task": "{session.task}",
|
|
72
|
+
"status": "completed",
|
|
73
|
+
"startedAt": "{session.startedAt}",
|
|
74
|
+
"pausedAt": null,
|
|
75
|
+
"completedAt": "{now}",
|
|
76
|
+
"duration": {duration},
|
|
77
|
+
"metrics": {
|
|
78
|
+
"filesChanged": {filesChanged},
|
|
79
|
+
"linesAdded": {linesAdded},
|
|
80
|
+
"linesRemoved": {linesRemoved},
|
|
81
|
+
"commits": {commits},
|
|
82
|
+
"snapshots": {session.metrics.snapshots}
|
|
83
|
+
},
|
|
84
|
+
"timeline": [
|
|
85
|
+
...{session.timeline},
|
|
86
|
+
{"type": "complete", "at": "{now}"}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Step 4: Archive Session
|
|
92
|
+
|
|
93
|
+
### Create Archive Directory
|
|
94
|
+
GET: {yearMonth} = YYYY-MM from {now}
|
|
95
|
+
ENSURE: `{archiveDir}/{yearMonth}` exists
|
|
96
|
+
|
|
97
|
+
BASH: `mkdir -p {archiveDir}/{yearMonth}`
|
|
98
|
+
|
|
99
|
+
### Write Archived Session
|
|
100
|
+
WRITE: `{archiveDir}/{yearMonth}/{session.id}.json`
|
|
101
|
+
Content: Updated session object from Step 3
|
|
102
|
+
|
|
103
|
+
## Step 5: Clear Current Session
|
|
104
|
+
|
|
105
|
+
DELETE: `{sessionPath}`
|
|
55
106
|
|
|
56
|
-
|
|
107
|
+
OR WRITE empty state:
|
|
108
|
+
WRITE: `{sessionPath}`
|
|
109
|
+
Content:
|
|
110
|
+
```json
|
|
111
|
+
{}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Step 6: Update Legacy now.md
|
|
115
|
+
|
|
116
|
+
WRITE: `{nowPath}`
|
|
117
|
+
Content:
|
|
57
118
|
```markdown
|
|
58
119
|
# NOW
|
|
59
120
|
|
|
60
121
|
No current task. Use `/p:now` to set focus.
|
|
61
122
|
```
|
|
62
123
|
|
|
63
|
-
## Step
|
|
124
|
+
## Step 7: Log to Memory
|
|
64
125
|
|
|
65
126
|
APPEND to: `{memoryPath}`
|
|
66
127
|
|
|
67
128
|
Single line (JSONL format):
|
|
68
129
|
```json
|
|
69
|
-
{"timestamp":"{
|
|
130
|
+
{"timestamp":"{now}","action":"session_completed","sessionId":"{session.id}","task":"{session.task}","duration":{duration},"metrics":{"files":{filesChanged},"added":{linesAdded},"removed":{linesRemoved},"commits":{commits}}}
|
|
70
131
|
```
|
|
71
132
|
|
|
72
|
-
## Step
|
|
133
|
+
## Step 8: Update Metrics Summary
|
|
134
|
+
|
|
135
|
+
READ: `{metricsPath}` (create if not exists)
|
|
73
136
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
137
|
+
### Append Daily Entry
|
|
138
|
+
GET: {date} = YYYY-MM-DD from {now}
|
|
139
|
+
|
|
140
|
+
INSERT or UPDATE entry for {date}:
|
|
141
|
+
```markdown
|
|
142
|
+
### {date}
|
|
143
|
+
- **{session.task}** ({durationFormatted})
|
|
144
|
+
- Files: {filesChanged} | +{linesAdded}/-{linesRemoved} | Commits: {commits}
|
|
145
|
+
```
|
|
77
146
|
|
|
78
147
|
## Output
|
|
79
148
|
|
|
80
149
|
SUCCESS:
|
|
81
150
|
```
|
|
82
|
-
✅ {task} ({
|
|
151
|
+
✅ {session.task} ({durationFormatted})
|
|
152
|
+
|
|
153
|
+
Session: {session.id}
|
|
154
|
+
Files: {filesChanged} | +{linesAdded}/-{linesRemoved}
|
|
155
|
+
Commits: {commits}
|
|
83
156
|
|
|
84
157
|
Next:
|
|
85
158
|
• /p:now - Start next task
|
|
86
159
|
• /p:ship - Ship completed work
|
|
87
|
-
• /p:
|
|
160
|
+
• /p:progress - View metrics
|
|
88
161
|
```
|
|
89
162
|
|
|
90
163
|
## Error Handling
|
|
91
164
|
|
|
92
|
-
| Error | Response |
|
|
93
|
-
|
|
94
|
-
| Config not found | "No prjct project
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
165
|
+
| Error | Response | Action |
|
|
166
|
+
|-------|----------|--------|
|
|
167
|
+
| Config not found | "No prjct project" | STOP |
|
|
168
|
+
| No session/task | "No active task" | STOP |
|
|
169
|
+
| Git fails | Use zeros for metrics | CONTINUE |
|
|
170
|
+
| Archive fails | Log warning | CONTINUE |
|
|
171
|
+
| Write fails | Log warning | CONTINUE |
|
|
98
172
|
|
|
99
173
|
## Examples
|
|
100
174
|
|
|
101
|
-
### Example 1:
|
|
102
|
-
**
|
|
175
|
+
### Example 1: Full Session Completion
|
|
176
|
+
**Session:**
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"id": "sess_abc12345",
|
|
180
|
+
"task": "implement authentication",
|
|
181
|
+
"status": "active",
|
|
182
|
+
"startedAt": "2025-12-07T10:00:00.000Z",
|
|
183
|
+
"timeline": [
|
|
184
|
+
{"type": "start", "at": "2025-12-07T10:00:00.000Z"}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
103
187
|
```
|
|
104
|
-
# NOW
|
|
105
188
|
|
|
106
|
-
**
|
|
189
|
+
**Git activity:**
|
|
190
|
+
- 3 commits
|
|
191
|
+
- 5 files changed
|
|
192
|
+
- +120/-30 lines
|
|
107
193
|
|
|
108
|
-
|
|
194
|
+
**Output:**
|
|
109
195
|
```
|
|
196
|
+
✅ implement authentication (2h 15m)
|
|
110
197
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
198
|
+
Session: sess_abc12345
|
|
199
|
+
Files: 5 | +120/-30
|
|
200
|
+
Commits: 3
|
|
114
201
|
|
|
115
|
-
|
|
116
|
-
|
|
202
|
+
Next:
|
|
203
|
+
• /p:now - Start next task
|
|
204
|
+
• /p:ship - Ship completed work
|
|
205
|
+
• /p:progress - View metrics
|
|
117
206
|
```
|
|
118
|
-
# NOW
|
|
119
207
|
|
|
120
|
-
|
|
208
|
+
### Example 2: Session with Pauses
|
|
209
|
+
**Session with multiple pause/resume:**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"id": "sess_xyz98765",
|
|
213
|
+
"task": "fix login bug",
|
|
214
|
+
"timeline": [
|
|
215
|
+
{"type": "start", "at": "2025-12-07T09:00:00.000Z"},
|
|
216
|
+
{"type": "pause", "at": "2025-12-07T10:00:00.000Z"},
|
|
217
|
+
{"type": "resume", "at": "2025-12-07T14:00:00.000Z"},
|
|
218
|
+
{"type": "pause", "at": "2025-12-07T15:30:00.000Z"},
|
|
219
|
+
{"type": "resume", "at": "2025-12-07T16:00:00.000Z"}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Completion at 17:00:**
|
|
225
|
+
- Active time: 1h + 1.5h + 1h = 3.5h
|
|
226
|
+
- Duration: 3h 30m
|
|
227
|
+
|
|
228
|
+
**Output:**
|
|
121
229
|
```
|
|
230
|
+
✅ fix login bug (3h 30m)
|
|
122
231
|
|
|
123
|
-
|
|
232
|
+
Session: sess_xyz98765
|
|
233
|
+
Files: 2 | +45/-12
|
|
234
|
+
Commits: 1
|
|
124
235
|
|
|
125
|
-
|
|
236
|
+
Next:
|
|
237
|
+
• /p:now - Start next task
|
|
238
|
+
• /p:ship - Ship completed work
|
|
239
|
+
• /p:progress - View metrics
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Example 3: Legacy Fallback (No Session)
|
|
126
243
|
**now.md content:**
|
|
127
244
|
```
|
|
128
245
|
# NOW
|
|
129
246
|
|
|
130
|
-
**fix
|
|
247
|
+
**quick fix**
|
|
131
248
|
|
|
132
|
-
Started:
|
|
249
|
+
Started: 2025-12-07T16:45:00.000Z
|
|
133
250
|
```
|
|
134
251
|
|
|
135
|
-
**
|
|
136
|
-
|
|
137
|
-
|
|
252
|
+
**Output:**
|
|
253
|
+
```
|
|
254
|
+
✅ quick fix (15m)
|
|
255
|
+
|
|
256
|
+
Next:
|
|
257
|
+
• /p:now - Start next task
|
|
258
|
+
• /p:ship - Ship completed work
|
|
259
|
+
```
|