prjct-cli 0.44.1 → 0.45.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/bin/prjct.ts +131 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +287 -29
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +49 -8
  43. package/core/commands/commands.ts +60 -23
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +14 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -12
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +143 -0
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +170 -329
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Metrics Storage
3
+ *
4
+ * Manages value dashboard metrics via storage/metrics.json
5
+ * Generates context/metrics.md for Claude
6
+ *
7
+ * Tracks:
8
+ * - Token savings (compression)
9
+ * - Sync performance
10
+ * - Agent usage
11
+ * - Daily trends for visualization
12
+ */
13
+
14
+ import {
15
+ type AgentUsage,
16
+ type DailyStats,
17
+ DEFAULT_METRICS,
18
+ estimateCostSaved,
19
+ formatCost,
20
+ type MetricsJson,
21
+ } from '../schemas/metrics'
22
+ import { getTimestamp } from '../utils/date-helper'
23
+ import { StorageManager } from './storage-manager'
24
+
25
+ class MetricsStorage extends StorageManager<MetricsJson> {
26
+ constructor() {
27
+ super('metrics.json')
28
+ }
29
+
30
+ protected getDefault(): MetricsJson {
31
+ return { ...DEFAULT_METRICS }
32
+ }
33
+
34
+ protected getMdFilename(): string {
35
+ return 'metrics.md'
36
+ }
37
+
38
+ protected getLayer(): string {
39
+ return 'context'
40
+ }
41
+
42
+ protected getEventType(action: 'update' | 'create' | 'delete'): string {
43
+ return `metrics.${action}d`
44
+ }
45
+
46
+ protected toMarkdown(data: MetricsJson): string {
47
+ const lines = ['# Value Dashboard 📊', '']
48
+
49
+ if (data.syncCount === 0) {
50
+ lines.push('_No metrics yet. Run `prjct sync` to start tracking._')
51
+ lines.push('')
52
+ return lines.join('\n')
53
+ }
54
+
55
+ // Token Savings
56
+ lines.push('## 💰 Token Savings')
57
+ lines.push('')
58
+ lines.push(`- **Total saved**: ${this.formatTokens(data.totalTokensSaved)} tokens`)
59
+ lines.push(
60
+ `- **Compression**: ${(data.avgCompressionRate * 100).toFixed(0)}% average reduction`
61
+ )
62
+ lines.push(
63
+ `- **Estimated cost saved**: ${formatCost(estimateCostSaved(data.totalTokensSaved))}`
64
+ )
65
+ lines.push('')
66
+
67
+ // Performance
68
+ lines.push('## ⚡ Performance')
69
+ lines.push('')
70
+ lines.push(`- **Syncs completed**: ${data.syncCount.toLocaleString()}`)
71
+ lines.push(`- **Avg sync time**: ${this.formatDuration(data.avgSyncDuration)}`)
72
+ if (data.watchTriggers > 0) {
73
+ lines.push(`- **Watch triggers**: ${data.watchTriggers.toLocaleString()} auto-syncs`)
74
+ }
75
+ lines.push('')
76
+
77
+ // Agent Usage
78
+ if (data.agentUsage.length > 0) {
79
+ lines.push('## 🤖 Agent Usage')
80
+ lines.push('')
81
+ const sortedAgents = [...data.agentUsage].sort((a, b) => b.usageCount - a.usageCount)
82
+ const totalUsage = sortedAgents.reduce((sum, a) => sum + a.usageCount, 0)
83
+
84
+ sortedAgents.slice(0, 5).forEach((agent) => {
85
+ const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
86
+ lines.push(`- **${agent.agentName}**: ${pct}% (${agent.usageCount} uses)`)
87
+ })
88
+ lines.push('')
89
+ }
90
+
91
+ // Trend (last 30 days)
92
+ if (data.dailyStats.length > 0) {
93
+ lines.push('## 📈 30-Day Trend')
94
+ lines.push('')
95
+ const last30 = this.getLast30Days(data.dailyStats)
96
+ const totalLast30 = last30.reduce((sum, d) => sum + d.tokensSaved, 0)
97
+ lines.push(`- **Tokens saved**: ${this.formatTokens(totalLast30)}`)
98
+ lines.push(`- **Syncs**: ${last30.reduce((sum, d) => sum + d.syncs, 0)}`)
99
+ lines.push('')
100
+ lines.push('```')
101
+ lines.push(this.generateSparkline(last30))
102
+ lines.push('```')
103
+ lines.push('')
104
+ }
105
+
106
+ // Footer
107
+ lines.push('---')
108
+ lines.push('')
109
+ if (data.firstSync) {
110
+ lines.push(`_Tracking since ${new Date(data.firstSync).toLocaleDateString()}_`)
111
+ }
112
+ lines.push('')
113
+
114
+ return lines.join('\n')
115
+ }
116
+
117
+ // =========== Domain Methods ===========
118
+
119
+ /**
120
+ * Record a sync event with metrics
121
+ */
122
+ async recordSync(
123
+ projectId: string,
124
+ metrics: {
125
+ originalSize: number // Tokens before compression
126
+ filteredSize: number // Tokens after compression
127
+ duration: number // Sync duration in ms
128
+ isWatch?: boolean // From watch mode?
129
+ agents?: string[] // Agents used
130
+ }
131
+ ): Promise<void> {
132
+ const tokensSaved = Math.max(0, metrics.originalSize - metrics.filteredSize)
133
+ const compressionRate = metrics.originalSize > 0 ? tokensSaved / metrics.originalSize : 0
134
+
135
+ const today = new Date().toISOString().split('T')[0]
136
+
137
+ await this.update(projectId, (data) => {
138
+ // Update totals
139
+ const newSyncCount = data.syncCount + 1
140
+ const newTotalTokensSaved = data.totalTokensSaved + tokensSaved
141
+ const newTotalDuration = data.totalSyncDuration + metrics.duration
142
+
143
+ // Running average for compression rate
144
+ const newAvgCompression =
145
+ data.syncCount === 0
146
+ ? compressionRate
147
+ : (data.avgCompressionRate * data.syncCount + compressionRate) / newSyncCount
148
+
149
+ // Update daily stats
150
+ const dailyStats = [...data.dailyStats]
151
+ const todayIndex = dailyStats.findIndex((d) => d.date === today)
152
+
153
+ if (todayIndex >= 0) {
154
+ const existing = dailyStats[todayIndex]
155
+ dailyStats[todayIndex] = {
156
+ ...existing,
157
+ tokensSaved: existing.tokensSaved + tokensSaved,
158
+ syncs: existing.syncs + 1,
159
+ avgCompressionRate:
160
+ (existing.avgCompressionRate * existing.syncs + compressionRate) / (existing.syncs + 1),
161
+ totalDuration: existing.totalDuration + metrics.duration,
162
+ }
163
+ } else {
164
+ dailyStats.push({
165
+ date: today,
166
+ tokensSaved,
167
+ syncs: 1,
168
+ avgCompressionRate: compressionRate,
169
+ totalDuration: metrics.duration,
170
+ })
171
+ }
172
+
173
+ // Keep only last 90 days
174
+ const cutoff = new Date()
175
+ cutoff.setDate(cutoff.getDate() - 90)
176
+ const cutoffStr = cutoff.toISOString().split('T')[0]
177
+ const trimmedStats = dailyStats.filter((d) => d.date >= cutoffStr)
178
+
179
+ // Update agent usage
180
+ const agentUsage = [...data.agentUsage]
181
+ if (metrics.agents) {
182
+ for (const agentName of metrics.agents) {
183
+ const idx = agentUsage.findIndex((a) => a.agentName === agentName)
184
+ if (idx >= 0) {
185
+ agentUsage[idx] = {
186
+ ...agentUsage[idx],
187
+ usageCount: agentUsage[idx].usageCount + 1,
188
+ tokensSaved:
189
+ agentUsage[idx].tokensSaved + Math.floor(tokensSaved / metrics.agents.length),
190
+ }
191
+ } else {
192
+ agentUsage.push({
193
+ agentName,
194
+ usageCount: 1,
195
+ tokensSaved: Math.floor(tokensSaved / metrics.agents.length),
196
+ })
197
+ }
198
+ }
199
+ }
200
+
201
+ return {
202
+ totalTokensSaved: newTotalTokensSaved,
203
+ avgCompressionRate: newAvgCompression,
204
+ syncCount: newSyncCount,
205
+ watchTriggers: data.watchTriggers + (metrics.isWatch ? 1 : 0),
206
+ avgSyncDuration: newTotalDuration / newSyncCount,
207
+ totalSyncDuration: newTotalDuration,
208
+ agentUsage,
209
+ dailyStats: trimmedStats,
210
+ firstSync: data.firstSync || getTimestamp(),
211
+ lastUpdated: getTimestamp(),
212
+ }
213
+ })
214
+ }
215
+
216
+ /**
217
+ * Get metrics summary for dashboard
218
+ */
219
+ async getSummary(projectId: string): Promise<{
220
+ totalTokensSaved: number
221
+ estimatedCostSaved: number
222
+ compressionRate: number
223
+ syncCount: number
224
+ avgSyncDuration: number
225
+ topAgents: AgentUsage[]
226
+ last30DaysTokens: number
227
+ trend: number // Percentage change vs previous 30 days
228
+ }> {
229
+ const data = await this.read(projectId)
230
+
231
+ const last30 = this.getLast30Days(data.dailyStats)
232
+ const prev30 = this.getPrev30Days(data.dailyStats)
233
+
234
+ const last30Tokens = last30.reduce((sum, d) => sum + d.tokensSaved, 0)
235
+ const prev30Tokens = prev30.reduce((sum, d) => sum + d.tokensSaved, 0)
236
+
237
+ const trend = prev30Tokens > 0 ? ((last30Tokens - prev30Tokens) / prev30Tokens) * 100 : 0
238
+
239
+ return {
240
+ totalTokensSaved: data.totalTokensSaved,
241
+ estimatedCostSaved: estimateCostSaved(data.totalTokensSaved),
242
+ compressionRate: data.avgCompressionRate,
243
+ syncCount: data.syncCount,
244
+ avgSyncDuration: data.avgSyncDuration,
245
+ topAgents: [...data.agentUsage].sort((a, b) => b.usageCount - a.usageCount).slice(0, 5),
246
+ last30DaysTokens: last30Tokens,
247
+ trend,
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Get daily stats for a period
253
+ */
254
+ async getDailyStats(projectId: string, days: number = 30): Promise<DailyStats[]> {
255
+ const data = await this.read(projectId)
256
+ const cutoff = new Date()
257
+ cutoff.setDate(cutoff.getDate() - days)
258
+ const cutoffStr = cutoff.toISOString().split('T')[0]
259
+
260
+ return data.dailyStats
261
+ .filter((d) => d.date >= cutoffStr)
262
+ .sort((a, b) => a.date.localeCompare(b.date))
263
+ }
264
+
265
+ // =========== Helper Methods ===========
266
+
267
+ private getLast30Days(dailyStats: DailyStats[]): DailyStats[] {
268
+ const cutoff = new Date()
269
+ cutoff.setDate(cutoff.getDate() - 30)
270
+ const cutoffStr = cutoff.toISOString().split('T')[0]
271
+ return dailyStats.filter((d) => d.date >= cutoffStr)
272
+ }
273
+
274
+ private getPrev30Days(dailyStats: DailyStats[]): DailyStats[] {
275
+ const end = new Date()
276
+ end.setDate(end.getDate() - 30)
277
+ const start = new Date()
278
+ start.setDate(start.getDate() - 60)
279
+
280
+ const startStr = start.toISOString().split('T')[0]
281
+ const endStr = end.toISOString().split('T')[0]
282
+
283
+ return dailyStats.filter((d) => d.date >= startStr && d.date < endStr)
284
+ }
285
+
286
+ private formatTokens(tokens: number): string {
287
+ if (tokens >= 1_000_000) {
288
+ return `${(tokens / 1_000_000).toFixed(1)}M`
289
+ }
290
+ if (tokens >= 1_000) {
291
+ return `${(tokens / 1_000).toFixed(1)}K`
292
+ }
293
+ return tokens.toLocaleString()
294
+ }
295
+
296
+ private formatDuration(ms: number): string {
297
+ if (ms < 1000) {
298
+ return `${Math.round(ms)}ms`
299
+ }
300
+ return `${(ms / 1000).toFixed(1)}s`
301
+ }
302
+
303
+ private generateSparkline(dailyStats: DailyStats[]): string {
304
+ if (dailyStats.length === 0) return ''
305
+
306
+ const chars = '▁▂▃▄▅▆▇█'
307
+ const values = dailyStats.map((d) => d.tokensSaved)
308
+ const max = Math.max(...values, 1)
309
+
310
+ return values
311
+ .map((v) => {
312
+ const idx = Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)
313
+ return chars[idx]
314
+ })
315
+ .join('')
316
+ }
317
+ }
318
+
319
+ export const metricsStorage = new MetricsStorage()
320
+ export default metricsStorage
@@ -5,10 +5,10 @@
5
5
  * Generates context/next.md for Claude
6
6
  */
7
7
 
8
- import { StorageManager } from './storage-manager'
9
8
  import { generateUUID } from '../schemas'
9
+ import type { Priority, QueueJson, QueueTask, TaskSection } from '../schemas/state'
10
10
  import { getTimestamp } from '../utils/date-helper'
11
- import type { QueueJson, QueueTask, Priority, TaskType, TaskSection } from '../schemas/state'
11
+ import { StorageManager } from './storage-manager'
12
12
 
13
13
  class QueueStorage extends StorageManager<QueueJson> {
14
14
  constructor() {
@@ -18,7 +18,7 @@ class QueueStorage extends StorageManager<QueueJson> {
18
18
  protected getDefault(): QueueJson {
19
19
  return {
20
20
  tasks: [],
21
- lastUpdated: ''
21
+ lastUpdated: '',
22
22
  }
23
23
  }
24
24
 
@@ -37,9 +37,11 @@ class QueueStorage extends StorageManager<QueueJson> {
37
37
  protected toMarkdown(data: QueueJson): string {
38
38
  const lines = ['# Priority Queue', '']
39
39
 
40
- const activeTasks = data.tasks.filter(t => t.section === 'active' && !t.completed)
41
- const backlogTasks = data.tasks.filter(t => t.section === 'backlog' && !t.completed)
42
- const previouslyActive = data.tasks.filter(t => t.section === 'previously_active' && !t.completed)
40
+ const activeTasks = data.tasks.filter((t) => t.section === 'active' && !t.completed)
41
+ const backlogTasks = data.tasks.filter((t) => t.section === 'backlog' && !t.completed)
42
+ const previouslyActive = data.tasks.filter(
43
+ (t) => t.section === 'previously_active' && !t.completed
44
+ )
43
45
 
44
46
  // Active section
45
47
  lines.push('## Active Tasks')
@@ -60,7 +62,7 @@ class QueueStorage extends StorageManager<QueueJson> {
60
62
  // Previously active section (if any)
61
63
  if (previouslyActive.length > 0) {
62
64
  lines.push('## Previously Active')
63
- previouslyActive.forEach(task => {
65
+ previouslyActive.forEach((task) => {
64
66
  lines.push(`- [ ] ${task.description}`)
65
67
  })
66
68
  lines.push('')
@@ -69,7 +71,7 @@ class QueueStorage extends StorageManager<QueueJson> {
69
71
  // Backlog section
70
72
  lines.push('## Backlog')
71
73
  if (backlogTasks.length > 0) {
72
- backlogTasks.forEach(task => {
74
+ backlogTasks.forEach((task) => {
73
75
  const priority = task.priority !== 'medium' ? ` [${task.priority.toUpperCase()}]` : ''
74
76
  const bug = task.type === 'bug' ? ' \u{1F41B}' : ''
75
77
  lines.push(`- [ ]${bug}${priority} ${task.description}`)
@@ -97,7 +99,7 @@ class QueueStorage extends StorageManager<QueueJson> {
97
99
  */
98
100
  async getActiveTasks(projectId: string): Promise<QueueTask[]> {
99
101
  const queue = await this.read(projectId)
100
- return queue.tasks.filter(t => t.section === 'active' && !t.completed)
102
+ return queue.tasks.filter((t) => t.section === 'active' && !t.completed)
101
103
  }
102
104
 
103
105
  /**
@@ -105,7 +107,7 @@ class QueueStorage extends StorageManager<QueueJson> {
105
107
  */
106
108
  async getBacklog(projectId: string): Promise<QueueTask[]> {
107
109
  const queue = await this.read(projectId)
108
- return queue.tasks.filter(t => t.section === 'backlog' && !t.completed)
110
+ return queue.tasks.filter((t) => t.section === 'backlog' && !t.completed)
109
111
  }
110
112
 
111
113
  /**
@@ -127,12 +129,12 @@ class QueueStorage extends StorageManager<QueueJson> {
127
129
  ...task,
128
130
  id: generateUUID(),
129
131
  createdAt: getTimestamp(),
130
- completed: false
132
+ completed: false,
131
133
  }
132
134
 
133
135
  await this.update(projectId, (queue) => ({
134
136
  tasks: [...queue.tasks, newTask],
135
- lastUpdated: getTimestamp()
137
+ lastUpdated: getTimestamp(),
136
138
  }))
137
139
 
138
140
  // Publish incremental event
@@ -140,7 +142,7 @@ class QueueStorage extends StorageManager<QueueJson> {
140
142
  taskId: newTask.id,
141
143
  description: newTask.description,
142
144
  priority: newTask.priority,
143
- section: newTask.section
145
+ section: newTask.section,
144
146
  })
145
147
 
146
148
  return newTask
@@ -154,22 +156,22 @@ class QueueStorage extends StorageManager<QueueJson> {
154
156
  tasks: Omit<QueueTask, 'id' | 'createdAt' | 'completed' | 'completedAt'>[]
155
157
  ): Promise<QueueTask[]> {
156
158
  const now = getTimestamp()
157
- const newTasks: QueueTask[] = tasks.map(task => ({
159
+ const newTasks: QueueTask[] = tasks.map((task) => ({
158
160
  ...task,
159
161
  id: generateUUID(),
160
162
  createdAt: now,
161
- completed: false
163
+ completed: false,
162
164
  }))
163
165
 
164
166
  await this.update(projectId, (queue) => ({
165
167
  tasks: [...queue.tasks, ...newTasks],
166
- lastUpdated: now
168
+ lastUpdated: now,
167
169
  }))
168
170
 
169
171
  // Publish event for batch add
170
172
  await this.publishEvent(projectId, 'queue.tasks_added', {
171
173
  count: newTasks.length,
172
- tasks: newTasks.map(t => ({ id: t.id, description: t.description }))
174
+ tasks: newTasks.map((t) => ({ id: t.id, description: t.description })),
173
175
  })
174
176
 
175
177
  return newTasks
@@ -180,8 +182,8 @@ class QueueStorage extends StorageManager<QueueJson> {
180
182
  */
181
183
  async removeTask(projectId: string, taskId: string): Promise<void> {
182
184
  await this.update(projectId, (queue) => ({
183
- tasks: queue.tasks.filter(t => t.id !== taskId),
184
- lastUpdated: getTimestamp()
185
+ tasks: queue.tasks.filter((t) => t.id !== taskId),
186
+ lastUpdated: getTimestamp(),
185
187
  }))
186
188
 
187
189
  await this.publishEvent(projectId, 'queue.task_removed', { taskId })
@@ -194,12 +196,12 @@ class QueueStorage extends StorageManager<QueueJson> {
194
196
  let completedTask: QueueTask | null = null
195
197
 
196
198
  await this.update(projectId, (queue) => {
197
- const tasks = queue.tasks.map(t => {
199
+ const tasks = queue.tasks.map((t) => {
198
200
  if (t.id === taskId) {
199
201
  completedTask = {
200
202
  ...t,
201
203
  completed: true,
202
- completedAt: getTimestamp()
204
+ completedAt: getTimestamp(),
203
205
  }
204
206
  return completedTask
205
207
  }
@@ -213,7 +215,7 @@ class QueueStorage extends StorageManager<QueueJson> {
213
215
  await this.publishEvent(projectId, 'queue.task_completed', {
214
216
  taskId,
215
217
  description: task.description,
216
- completedAt: task.completedAt
218
+ completedAt: task.completedAt,
217
219
  })
218
220
  }
219
221
 
@@ -223,32 +225,20 @@ class QueueStorage extends StorageManager<QueueJson> {
223
225
  /**
224
226
  * Move task to different section
225
227
  */
226
- async moveToSection(
227
- projectId: string,
228
- taskId: string,
229
- section: TaskSection
230
- ): Promise<void> {
228
+ async moveToSection(projectId: string, taskId: string, section: TaskSection): Promise<void> {
231
229
  await this.update(projectId, (queue) => ({
232
- tasks: queue.tasks.map(t =>
233
- t.id === taskId ? { ...t, section } : t
234
- ),
235
- lastUpdated: getTimestamp()
230
+ tasks: queue.tasks.map((t) => (t.id === taskId ? { ...t, section } : t)),
231
+ lastUpdated: getTimestamp(),
236
232
  }))
237
233
  }
238
234
 
239
235
  /**
240
236
  * Set task priority
241
237
  */
242
- async setPriority(
243
- projectId: string,
244
- taskId: string,
245
- priority: Priority
246
- ): Promise<void> {
238
+ async setPriority(projectId: string, taskId: string, priority: Priority): Promise<void> {
247
239
  await this.update(projectId, (queue) => ({
248
- tasks: queue.tasks.map(t =>
249
- t.id === taskId ? { ...t, priority } : t
250
- ),
251
- lastUpdated: getTimestamp()
240
+ tasks: queue.tasks.map((t) => (t.id === taskId ? { ...t, priority } : t)),
241
+ lastUpdated: getTimestamp(),
252
242
  }))
253
243
  }
254
244
 
@@ -257,11 +247,11 @@ class QueueStorage extends StorageManager<QueueJson> {
257
247
  */
258
248
  async clearCompleted(projectId: string): Promise<number> {
259
249
  const queue = await this.read(projectId)
260
- const completedCount = queue.tasks.filter(t => t.completed).length
250
+ const completedCount = queue.tasks.filter((t) => t.completed).length
261
251
 
262
252
  await this.update(projectId, (q) => ({
263
- tasks: q.tasks.filter(t => !t.completed),
264
- lastUpdated: getTimestamp()
253
+ tasks: q.tasks.filter((t) => !t.completed),
254
+ lastUpdated: getTimestamp(),
265
255
  }))
266
256
 
267
257
  return completedCount
@@ -275,13 +265,13 @@ class QueueStorage extends StorageManager<QueueJson> {
275
265
  critical: 0,
276
266
  high: 1,
277
267
  medium: 2,
278
- low: 3
268
+ low: 3,
279
269
  }
280
270
 
281
271
  const sectionOrder: Record<TaskSection, number> = {
282
272
  active: 0,
283
273
  previously_active: 1,
284
- backlog: 2
274
+ backlog: 2,
285
275
  }
286
276
 
287
277
  return [...tasks].sort((a, b) => {
@@ -5,10 +5,10 @@
5
5
  * Generates context/shipped.md for Claude
6
6
  */
7
7
 
8
- import { StorageManager } from './storage-manager'
9
8
  import { generateUUID } from '../schemas'
10
- import { getTimestamp } from '../utils/date-helper'
11
9
  import type { ShippedFeature, ShippedJson } from '../types'
10
+ import { getTimestamp } from '../utils/date-helper'
11
+ import { StorageManager } from './storage-manager'
12
12
 
13
13
  class ShippedStorage extends StorageManager<ShippedJson> {
14
14
  constructor() {
@@ -18,7 +18,7 @@ class ShippedStorage extends StorageManager<ShippedJson> {
18
18
  protected getDefault(): ShippedJson {
19
19
  return {
20
20
  shipped: [],
21
- lastUpdated: ''
21
+ lastUpdated: '',
22
22
  }
23
23
  }
24
24
 
@@ -46,7 +46,7 @@ class ShippedStorage extends StorageManager<ShippedJson> {
46
46
  // Group by month
47
47
  const byMonth = new Map<string, ShippedFeature[]>()
48
48
 
49
- data.shipped.forEach(ship => {
49
+ data.shipped.forEach((ship) => {
50
50
  const date = new Date(ship.shippedAt)
51
51
  const month = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' })
52
52
 
@@ -63,18 +63,18 @@ class ShippedStorage extends StorageManager<ShippedJson> {
63
63
  return dateB.getTime() - dateA.getTime()
64
64
  })
65
65
 
66
- sortedMonths.forEach(month => {
66
+ sortedMonths.forEach((month) => {
67
67
  lines.push(`## ${month}`)
68
68
  lines.push('')
69
69
 
70
- const ships = byMonth.get(month)!.sort(
71
- (a, b) => new Date(b.shippedAt).getTime() - new Date(a.shippedAt).getTime()
72
- )
70
+ const ships = byMonth
71
+ .get(month)!
72
+ .sort((a, b) => new Date(b.shippedAt).getTime() - new Date(a.shippedAt).getTime())
73
73
 
74
- ships.forEach(ship => {
74
+ ships.forEach((ship) => {
75
75
  const date = new Date(ship.shippedAt).toLocaleDateString('en-US', {
76
76
  month: 'short',
77
- day: 'numeric'
77
+ day: 'numeric',
78
78
  })
79
79
  const version = ship.version ? ` v${ship.version}` : ''
80
80
  const duration = ship.duration ? ` (${ship.duration})` : ''
@@ -126,12 +126,12 @@ class ShippedStorage extends StorageManager<ShippedJson> {
126
126
  const shipped: ShippedFeature = {
127
127
  ...feature,
128
128
  id: generateUUID(),
129
- shippedAt: getTimestamp()
129
+ shippedAt: getTimestamp(),
130
130
  }
131
131
 
132
132
  await this.update(projectId, (data) => ({
133
133
  shipped: [shipped, ...data.shipped], // Prepend
134
- lastUpdated: getTimestamp()
134
+ lastUpdated: getTimestamp(),
135
135
  }))
136
136
 
137
137
  // Publish event
@@ -139,7 +139,7 @@ class ShippedStorage extends StorageManager<ShippedJson> {
139
139
  shipId: shipped.id,
140
140
  name: shipped.name,
141
141
  version: shipped.version,
142
- shippedAt: shipped.shippedAt
142
+ shippedAt: shipped.shippedAt,
143
143
  })
144
144
 
145
145
  return shipped
@@ -148,12 +148,9 @@ class ShippedStorage extends StorageManager<ShippedJson> {
148
148
  /**
149
149
  * Get shipped by version
150
150
  */
151
- async getByVersion(
152
- projectId: string,
153
- version: string
154
- ): Promise<ShippedFeature | undefined> {
151
+ async getByVersion(projectId: string, version: string): Promise<ShippedFeature | undefined> {
155
152
  const data = await this.read(projectId)
156
- return data.shipped.find(s => s.version === version)
153
+ return data.shipped.find((s) => s.version === version)
157
154
  }
158
155
 
159
156
  /**
@@ -173,7 +170,7 @@ class ShippedStorage extends StorageManager<ShippedJson> {
173
170
  endDate: Date
174
171
  ): Promise<ShippedFeature[]> {
175
172
  const data = await this.read(projectId)
176
- return data.shipped.filter(s => {
173
+ return data.shipped.filter((s) => {
177
174
  const date = new Date(s.shippedAt)
178
175
  return date >= startDate && date <= endDate
179
176
  })
@@ -205,7 +202,7 @@ class ShippedStorage extends StorageManager<ShippedJson> {
205
202
 
206
203
  return {
207
204
  count: shipped.length,
208
- period
205
+ period,
209
206
  }
210
207
  }
211
208
  }