prjct-cli 0.45.0 → 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 +75 -0
  2. package/bin/prjct.ts +117 -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 +58 -39
  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 +28 -4
  43. package/core/commands/commands.ts +57 -24
  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 +13 -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 +18 -19
  59. package/core/context-tools/imports-tool.ts +13 -33
  60. package/core/context-tools/index.ts +29 -54
  61. package/core/context-tools/recent-tool.ts +16 -22
  62. package/core/context-tools/signatures-tool.ts +17 -26
  63. package/core/context-tools/summary-tool.ts +20 -22
  64. package/core/context-tools/token-counter.ts +25 -20
  65. package/core/context-tools/types.ts +5 -5
  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 -16
  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 +25 -25
  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 +87 -345
  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 -17
  157. package/core/storage/metrics-storage.ts +39 -34
  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 -305
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +14 -14
  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 +18755 -15574
  194. package/dist/core/infrastructure/command-installer.js +86 -79
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +246 -225
  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,294 @@
1
+ /**
2
+ * WatchService - Smart file watcher for auto-sync
3
+ *
4
+ * Monitors project files and triggers sync on meaningful changes.
5
+ * Features:
6
+ * - Debouncing to batch rapid changes
7
+ * - Smart filtering of meaningful files
8
+ * - Graceful shutdown
9
+ * - Low CPU/memory footprint
10
+ */
11
+
12
+ import path from 'node:path'
13
+ import chalk from 'chalk'
14
+ import chokidar, { type FSWatcher } from 'chokidar'
15
+ import configManager from '../infrastructure/config-manager'
16
+ import dateHelper from '../utils/date-helper'
17
+ import { syncService } from './sync-service'
18
+
19
+ // ============================================================================
20
+ // TYPES
21
+ // ============================================================================
22
+
23
+ interface WatchOptions {
24
+ debounceMs?: number // Debounce window (default: 2000ms)
25
+ minIntervalMs?: number // Minimum sync interval (default: 30000ms)
26
+ verbose?: boolean // Show detailed output
27
+ quiet?: boolean // Suppress all output except errors
28
+ }
29
+
30
+ interface WatchResult {
31
+ success: boolean
32
+ error?: string
33
+ }
34
+
35
+ // ============================================================================
36
+ // DEFAULT CONFIGURATION
37
+ // ============================================================================
38
+
39
+ // Files that trigger a sync when changed
40
+ const TRIGGER_PATTERNS = [
41
+ 'package.json',
42
+ 'package-lock.json',
43
+ 'bun.lockb',
44
+ 'pnpm-lock.yaml',
45
+ 'yarn.lock',
46
+ 'tsconfig.json',
47
+ 'tsconfig.*.json',
48
+ '.env',
49
+ '.env.*',
50
+ 'Cargo.toml',
51
+ 'go.mod',
52
+ 'pyproject.toml',
53
+ 'requirements.txt',
54
+ // Source files (new files trigger sync)
55
+ 'src/**/*.ts',
56
+ 'src/**/*.tsx',
57
+ 'src/**/*.js',
58
+ 'src/**/*.jsx',
59
+ 'lib/**/*.ts',
60
+ 'core/**/*.ts',
61
+ 'app/**/*.ts',
62
+ 'app/**/*.tsx',
63
+ 'pages/**/*.ts',
64
+ 'pages/**/*.tsx',
65
+ ]
66
+
67
+ // Patterns to always ignore
68
+ const IGNORE_PATTERNS = [
69
+ '**/node_modules/**',
70
+ '**/.git/**',
71
+ '**/dist/**',
72
+ '**/build/**',
73
+ '**/.next/**',
74
+ '**/.nuxt/**',
75
+ '**/coverage/**',
76
+ '**/*.log',
77
+ '**/*.tmp',
78
+ '**/CLAUDE.md', // Don't trigger on our own output
79
+ '**/.cursorrules',
80
+ '**/.windsurfrules',
81
+ '**/.prjct/**',
82
+ '**/.prjct-cli/**',
83
+ ]
84
+
85
+ // ============================================================================
86
+ // WATCH SERVICE
87
+ // ============================================================================
88
+
89
+ class WatchService {
90
+ private watcher: FSWatcher | null = null
91
+ private projectPath: string = ''
92
+ private projectId: string | null = null
93
+ private debounceTimer: ReturnType<typeof setTimeout> | null = null
94
+ private lastSyncTime: number = 0
95
+ private pendingChanges: Set<string> = new Set()
96
+ private options: Required<WatchOptions> = {
97
+ debounceMs: 2000,
98
+ minIntervalMs: 30000,
99
+ verbose: false,
100
+ quiet: false,
101
+ }
102
+ private isRunning: boolean = false
103
+ private syncCount: number = 0
104
+
105
+ /**
106
+ * Start watching for file changes
107
+ */
108
+ async start(
109
+ projectPath: string = process.cwd(),
110
+ options: WatchOptions = {}
111
+ ): Promise<WatchResult> {
112
+ this.projectPath = projectPath
113
+ this.options = { ...this.options, ...options }
114
+
115
+ // Get project ID
116
+ this.projectId = await configManager.getProjectId(projectPath)
117
+ if (!this.projectId) {
118
+ return { success: false, error: 'No prjct project. Run "prjct init" first.' }
119
+ }
120
+
121
+ // Check if already running
122
+ if (this.isRunning) {
123
+ return { success: false, error: 'Watch mode is already running' }
124
+ }
125
+
126
+ this.isRunning = true
127
+
128
+ // Print startup message
129
+ if (!this.options.quiet) {
130
+ this.printStartup()
131
+ }
132
+
133
+ // Initialize watcher
134
+ this.watcher = chokidar.watch(TRIGGER_PATTERNS, {
135
+ cwd: this.projectPath,
136
+ ignored: IGNORE_PATTERNS,
137
+ persistent: true,
138
+ ignoreInitial: true, // Don't trigger on initial scan
139
+ awaitWriteFinish: {
140
+ // Wait for writes to complete
141
+ stabilityThreshold: 500,
142
+ pollInterval: 100,
143
+ },
144
+ })
145
+
146
+ // Set up event handlers
147
+ this.watcher
148
+ .on('add', (filePath: string) => this.handleChange('add', filePath))
149
+ .on('change', (filePath: string) => this.handleChange('change', filePath))
150
+ .on('unlink', (filePath: string) => this.handleChange('unlink', filePath))
151
+ .on('error', (error: unknown) => this.handleError(error as Error))
152
+
153
+ // Handle graceful shutdown
154
+ process.on('SIGINT', () => this.stop())
155
+ process.on('SIGTERM', () => this.stop())
156
+
157
+ return { success: true }
158
+ }
159
+
160
+ /**
161
+ * Stop watching
162
+ */
163
+ async stop(): Promise<void> {
164
+ if (!this.options.quiet) {
165
+ console.log('')
166
+ console.log(chalk.dim(`\n👋 Stopped watching (${this.syncCount} syncs performed)`))
167
+ }
168
+
169
+ if (this.debounceTimer) {
170
+ clearTimeout(this.debounceTimer)
171
+ this.debounceTimer = null
172
+ }
173
+
174
+ if (this.watcher) {
175
+ await this.watcher.close()
176
+ this.watcher = null
177
+ }
178
+
179
+ this.isRunning = false
180
+ process.exit(0)
181
+ }
182
+
183
+ /**
184
+ * Handle file change event
185
+ */
186
+ private handleChange(event: 'add' | 'change' | 'unlink', filePath: string): void {
187
+ // Add to pending changes
188
+ this.pendingChanges.add(filePath)
189
+
190
+ if (this.options.verbose && !this.options.quiet) {
191
+ const eventIcon = event === 'add' ? '➕' : event === 'unlink' ? '➖' : '📝'
192
+ console.log(chalk.dim(` ${eventIcon} ${filePath}`))
193
+ }
194
+
195
+ // Debounce the sync
196
+ this.scheduleSyncIfNeeded()
197
+ }
198
+
199
+ /**
200
+ * Schedule a sync with debouncing and rate limiting
201
+ */
202
+ private scheduleSyncIfNeeded(): void {
203
+ // Clear existing timer
204
+ if (this.debounceTimer) {
205
+ clearTimeout(this.debounceTimer)
206
+ }
207
+
208
+ // Schedule new sync
209
+ this.debounceTimer = setTimeout(async () => {
210
+ // Check rate limit
211
+ const now = Date.now()
212
+ const timeSinceLastSync = now - this.lastSyncTime
213
+
214
+ if (timeSinceLastSync < this.options.minIntervalMs && this.lastSyncTime > 0) {
215
+ // Too soon, reschedule
216
+ const waitTime = this.options.minIntervalMs - timeSinceLastSync
217
+ if (this.options.verbose && !this.options.quiet) {
218
+ console.log(chalk.dim(` ⏳ Rate limited, waiting ${Math.round(waitTime / 1000)}s...`))
219
+ }
220
+ this.debounceTimer = setTimeout(() => this.performSync(), waitTime)
221
+ return
222
+ }
223
+
224
+ await this.performSync()
225
+ }, this.options.debounceMs)
226
+ }
227
+
228
+ /**
229
+ * Perform the actual sync
230
+ */
231
+ private async performSync(): Promise<void> {
232
+ const changedFiles = Array.from(this.pendingChanges)
233
+ this.pendingChanges.clear()
234
+
235
+ if (changedFiles.length === 0) return
236
+
237
+ const timestamp = dateHelper.getTimestamp().split('T')[1].split('.')[0]
238
+
239
+ if (!this.options.quiet) {
240
+ const filesSummary =
241
+ changedFiles.length === 1 ? changedFiles[0] : `${changedFiles.length} files`
242
+ console.log(
243
+ `\n${chalk.dim(`[${timestamp}]`)} ${chalk.cyan('⟳')} ${filesSummary} changed → syncing...`
244
+ )
245
+ }
246
+
247
+ try {
248
+ const result = await syncService.sync(this.projectPath)
249
+
250
+ this.lastSyncTime = Date.now()
251
+ this.syncCount++
252
+
253
+ if (result.success) {
254
+ if (!this.options.quiet) {
255
+ const agents = result.agents.filter((a) => a.type === 'domain').map((a) => a.name)
256
+ const agentStr = agents.length > 0 ? ` [${agents.join(', ')}]` : ''
257
+ console.log(`${chalk.dim(`[${timestamp}]`)} ${chalk.green('✓')} Synced${agentStr}`)
258
+ }
259
+ } else {
260
+ console.error(
261
+ `${chalk.dim(`[${timestamp}]`)} ${chalk.red('✗')} Sync failed: ${result.error}`
262
+ )
263
+ }
264
+ } catch (error) {
265
+ console.error(
266
+ `${chalk.dim(`[${timestamp}]`)} ${chalk.red('✗')} Error: ${(error as Error).message}`
267
+ )
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Handle watcher errors
273
+ */
274
+ private handleError(error: Error): void {
275
+ console.error(chalk.red(`Watch error: ${error.message}`))
276
+ }
277
+
278
+ /**
279
+ * Print startup message
280
+ */
281
+ private printStartup(): void {
282
+ console.log('')
283
+ console.log(chalk.cyan('👁️ Watching for changes...'))
284
+ console.log(chalk.dim(` Project: ${path.basename(this.projectPath)}`))
285
+ console.log(chalk.dim(` Debounce: ${this.options.debounceMs}ms`))
286
+ console.log(chalk.dim(` Min interval: ${this.options.minIntervalMs / 1000}s`))
287
+ console.log('')
288
+ console.log(chalk.dim(' Press Ctrl+C to stop'))
289
+ console.log('')
290
+ }
291
+ }
292
+
293
+ export const watchService = new WatchService()
294
+ export { WatchService, type WatchOptions, type WatchResult }
@@ -9,13 +9,13 @@
9
9
  * @version 1.0.0
10
10
  */
11
11
 
12
- import fs from 'fs/promises'
13
- import path from 'path'
14
- import { getTimestamp } from '../utils/date-helper'
12
+ import fs from 'node:fs/promises'
13
+ import path from 'node:path'
15
14
  import pathManager from '../infrastructure/path-manager'
16
- import type { ConversationTurn, CompactedContext, CompactionConfig } from '../types'
15
+ import type { CompactedContext, CompactionConfig, ConversationTurn } from '../types'
16
+ import { getTimestamp } from '../utils/date-helper'
17
17
 
18
- export type { ConversationTurn, CompactedContext, CompactionConfig } from '../types'
18
+ export type { CompactedContext, CompactionConfig, ConversationTurn } from '../types'
19
19
 
20
20
  const DEFAULT_CONFIG: Required<CompactionConfig> = {
21
21
  maxTurns: 50,
@@ -76,17 +76,14 @@ function extractKeyInfo(turns: ConversationTurn[]): {
76
76
  }
77
77
 
78
78
  // Extract completed tasks
79
- const taskPatterns = [
80
- /✅\s*(.+)/g,
81
- /completed[:\s]+(.+)/gi,
82
- /finished[:\s]+(.+)/gi,
83
- ]
79
+ const taskPatterns = [/✅\s*(.+)/g, /completed[:\s]+(.+)/gi, /finished[:\s]+(.+)/gi]
84
80
 
85
81
  for (const pattern of taskPatterns) {
86
82
  const matches = content.matchAll(pattern)
87
83
  for (const match of matches) {
88
84
  const task = match[1].trim()
89
- if (task.length < 100) { // Avoid capturing large blocks
85
+ if (task.length < 100) {
86
+ // Avoid capturing large blocks
90
87
  tasksCompleted.push(task)
91
88
  }
92
89
  }
@@ -106,19 +103,19 @@ function extractKeyInfo(turns: ConversationTurn[]): {
106
103
  function generateSummary(turns: ConversationTurn[], maxLength: number): string {
107
104
  // Get key user requests
108
105
  const userRequests = turns
109
- .filter(t => t.role === 'user')
110
- .map(t => t.content.slice(0, 200))
106
+ .filter((t) => t.role === 'user')
107
+ .map((t) => t.content.slice(0, 200))
111
108
  .slice(0, 5)
112
109
 
113
110
  // Get key assistant actions
114
111
  const assistantActions = turns
115
- .filter(t => t.role === 'assistant')
116
- .map(t => {
112
+ .filter((t) => t.role === 'assistant')
113
+ .map((t) => {
117
114
  // Extract first meaningful sentence
118
115
  const firstLine = t.content.split('\n')[0]
119
116
  return firstLine.slice(0, 150)
120
117
  })
121
- .filter(a => a.length > 10)
118
+ .filter((a) => a.length > 10)
122
119
  .slice(0, 5)
123
120
 
124
121
  const summary = [
@@ -160,10 +157,7 @@ export function compactContext(
160
157
  /**
161
158
  * Check if compaction is needed
162
159
  */
163
- export function needsCompaction(
164
- turns: ConversationTurn[],
165
- config: CompactionConfig = {}
166
- ): boolean {
160
+ export function needsCompaction(turns: ConversationTurn[], config: CompactionConfig = {}): boolean {
167
161
  const cfg = { ...DEFAULT_CONFIG, ...config }
168
162
 
169
163
  // Check turn count
@@ -192,7 +186,7 @@ export async function saveCompactedContext(
192
186
 
193
187
  await fs.mkdir(dirPath, { recursive: true })
194
188
 
195
- const line = JSON.stringify(context) + '\n'
189
+ const line = `${JSON.stringify(context)}\n`
196
190
  await fs.appendFile(filePath, line, 'utf-8')
197
191
 
198
192
  return filePath
@@ -206,13 +200,15 @@ export async function loadCompactedContexts(
206
200
  limit = 5
207
201
  ): Promise<CompactedContext[]> {
208
202
  const filePath = path.join(
209
- pathManager.getGlobalProjectPath(projectId), 'memory', 'compacted.jsonl'
203
+ pathManager.getGlobalProjectPath(projectId),
204
+ 'memory',
205
+ 'compacted.jsonl'
210
206
  )
211
207
 
212
208
  try {
213
209
  const content = await fs.readFile(filePath, 'utf-8')
214
210
  const lines = content.trim().split('\n').filter(Boolean)
215
- const contexts = lines.map(line => JSON.parse(line) as CompactedContext)
211
+ const contexts = lines.map((line) => JSON.parse(line) as CompactedContext)
216
212
 
217
213
  // Return most recent
218
214
  return contexts.slice(-limit)
@@ -225,27 +221,23 @@ export async function loadCompactedContexts(
225
221
  * Format compacted context for prompt injection
226
222
  */
227
223
  export function formatCompactedForPrompt(context: CompactedContext): string {
228
- const lines = [
229
- '<compacted-context>',
230
- context.summary,
231
- '',
232
- ]
224
+ const lines = ['<compacted-context>', context.summary, '']
233
225
 
234
226
  if (context.filesModified.length > 0) {
235
227
  lines.push('### Files Modified:')
236
- lines.push(context.filesModified.map(f => `- ${f}`).join('\n'))
228
+ lines.push(context.filesModified.map((f) => `- ${f}`).join('\n'))
237
229
  lines.push('')
238
230
  }
239
231
 
240
232
  if (context.tasksCompleted.length > 0) {
241
233
  lines.push('### Tasks Completed:')
242
- lines.push(context.tasksCompleted.map(t => `- ${t}`).join('\n'))
234
+ lines.push(context.tasksCompleted.map((t) => `- ${t}`).join('\n'))
243
235
  lines.push('')
244
236
  }
245
237
 
246
238
  if (context.decisions.length > 0) {
247
239
  lines.push('### Decisions Made:')
248
- lines.push(context.decisions.map(d => `- ${d}`).join('\n'))
240
+ lines.push(context.decisions.map((d) => `- ${d}`).join('\n'))
249
241
  lines.push('')
250
242
  }
251
243
 
@@ -11,15 +11,21 @@
11
11
  */
12
12
 
13
13
  // Task session types and utilities
14
- export type { Session, SessionMetrics, TimelineEvent } from '../types'
15
- export { generateId, calculateDuration, formatDuration } from './utils'
16
-
17
14
  // Log session types
18
- export type { SessionEntry, SessionLogMetadata, SessionStats, MigrationResult } from '../types'
15
+ export type {
16
+ MigrationResult,
17
+ Session,
18
+ SessionEntry,
19
+ SessionLogMetadata,
20
+ SessionMetrics,
21
+ SessionStats,
22
+ TimelineEvent,
23
+ } from '../types'
24
+ export { SessionLogManager } from './session-log-manager'
19
25
 
20
26
  // Main exports
21
27
  export { TaskSessionManager } from './task-session-manager'
22
- export { SessionLogManager } from './session-log-manager'
28
+ export { calculateDuration, formatDuration, generateId } from './utils'
23
29
 
24
30
  // Default: TaskSessionManager for backward compatibility
25
31
  import { TaskSessionManager } from './task-session-manager'
@@ -3,12 +3,12 @@
3
3
  * Migrate legacy single-file logs to session structure
4
4
  */
5
5
 
6
- import path from 'path'
6
+ import path from 'node:path'
7
7
  import pathManager from '../infrastructure/path-manager'
8
+ import type { SessionEntry, SessionLogMetadata, SessionMigrationResult } from '../types'
8
9
  import * as dateHelper from '../utils/date-helper'
9
- import * as jsonlHelper from '../utils/jsonl-helper'
10
10
  import * as fileHelper from '../utils/file-helper'
11
- import type { SessionEntry, SessionMigrationResult, SessionLogMetadata } from '../types'
11
+ import * as jsonlHelper from '../utils/jsonl-helper'
12
12
 
13
13
  /**
14
14
  * Migrate legacy JSONL file
@@ -6,8 +6,8 @@
6
6
  * @version 1.0.0
7
7
  */
8
8
 
9
- import fs from 'fs/promises'
10
- import path from 'path'
9
+ import fs from 'node:fs/promises'
10
+ import path from 'node:path'
11
11
  import pathManager from '../infrastructure/path-manager'
12
12
  import { isNotFoundError } from '../types/fs'
13
13
 
@@ -68,14 +68,14 @@ class SessionMetrics {
68
68
  totalDurationFormatted: this.formatDuration(this.sumDurations(sessions)),
69
69
  averageDuration: this.averageDuration(sessions),
70
70
  averageDurationFormatted: this.formatDuration(this.averageDuration(sessions)),
71
- tasksCompleted: sessions.filter(s => s.status === 'completed').length,
71
+ tasksCompleted: sessions.filter((s) => s.status === 'completed').length,
72
72
  filesChanged: this.sumMetric(sessions, 'filesChanged'),
73
73
  linesAdded: this.sumMetric(sessions, 'linesAdded'),
74
74
  linesRemoved: this.sumMetric(sessions, 'linesRemoved'),
75
75
  commits: this.sumMetric(sessions, 'commits'),
76
76
  productivityScore: this.calculateProductivityScore(sessions),
77
77
  streak: await this.calculateStreak(),
78
- byDay: this.groupByDay(sessions)
78
+ byDay: this.groupByDay(sessions),
79
79
  }
80
80
  }
81
81
 
@@ -141,15 +141,16 @@ class SessionMetrics {
141
141
  switch (period) {
142
142
  case 'day':
143
143
  return new Date(now.setHours(0, 0, 0, 0))
144
- case 'week':
144
+ case 'week': {
145
145
  const weekAgo = new Date(now)
146
146
  weekAgo.setDate(weekAgo.getDate() - 7)
147
147
  return weekAgo
148
- case 'month':
148
+ }
149
+ case 'month': {
149
150
  const monthAgo = new Date(now)
150
151
  monthAgo.setMonth(monthAgo.getMonth() - 1)
151
152
  return monthAgo
152
- case 'all':
153
+ }
153
154
  default:
154
155
  return new Date(0) // Beginning of time
155
156
  }
@@ -173,7 +174,10 @@ class SessionMetrics {
173
174
  /**
174
175
  * Sum a specific metric
175
176
  */
176
- sumMetric(sessions: Session[], metric: 'filesChanged' | 'linesAdded' | 'linesRemoved' | 'commits'): number {
177
+ sumMetric(
178
+ sessions: Session[],
179
+ metric: 'filesChanged' | 'linesAdded' | 'linesRemoved' | 'commits'
180
+ ): number {
177
181
  return sessions.reduce((sum, s) => {
178
182
  return sum + (s.metrics?.[metric] || 0)
179
183
  }, 0)
@@ -200,11 +204,12 @@ class SessionMetrics {
200
204
  }
201
205
 
202
206
  // 3. Completion rate
203
- const completedCount = sessions.filter(s => s.status === 'completed').length
207
+ const completedCount = sessions.filter((s) => s.status === 'completed').length
204
208
  const completionScore = (completedCount / sessions.length) * 20
205
209
 
206
210
  // 4. Output (commits + files changed)
207
- const totalOutput = this.sumMetric(sessions, 'commits') + this.sumMetric(sessions, 'filesChanged')
211
+ const totalOutput =
212
+ this.sumMetric(sessions, 'commits') + this.sumMetric(sessions, 'filesChanged')
208
213
  const outputScore = Math.min(totalOutput / 50, 1) * 20
209
214
 
210
215
  return Math.round(sessionScore + durationScore + completionScore + outputScore)
@@ -218,7 +223,7 @@ class SessionMetrics {
218
223
 
219
224
  // Get unique dates with sessions
220
225
  const dates = new Set(
221
- sessions.map(s => {
226
+ sessions.map((s) => {
222
227
  const d = new Date(s.completedAt || s.startedAt)
223
228
  return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`
224
229
  })
@@ -257,7 +262,7 @@ class SessionMetrics {
257
262
  byDay[key] = {
258
263
  sessions: 0,
259
264
  duration: 0,
260
- commits: 0
265
+ commits: 0,
261
266
  }
262
267
  }
263
268
 
@@ -293,7 +298,7 @@ class SessionMetrics {
293
298
  day: 'Today',
294
299
  week: 'This Week',
295
300
  month: 'This Month',
296
- all: 'All Time'
301
+ all: 'All Time',
297
302
  }
298
303
 
299
304
  return `
@@ -315,4 +320,4 @@ class SessionMetrics {
315
320
  }
316
321
 
317
322
  export default SessionMetrics
318
- export { SessionMetrics, AggregatedMetrics, DayMetrics }
323
+ export { SessionMetrics, type AggregatedMetrics, type DayMetrics }
@@ -4,14 +4,19 @@
4
4
  * Writes to sessions/YYYY-MM/DD/ structure with auto-rotation.
5
5
  */
6
6
 
7
- import path from 'path'
7
+ import path from 'node:path'
8
8
  import pathManager from '../infrastructure/path-manager'
9
- import { VERSION } from '../utils/version'
9
+ import type {
10
+ SessionEntry,
11
+ SessionLogMetadata,
12
+ SessionMigrationResult,
13
+ SessionStats,
14
+ } from '../types'
10
15
  import * as dateHelper from '../utils/date-helper'
11
- import * as jsonlHelper from '../utils/jsonl-helper'
12
16
  import * as fileHelper from '../utils/file-helper'
17
+ import * as jsonlHelper from '../utils/jsonl-helper'
18
+ import { VERSION } from '../utils/version'
13
19
  import { migrateLegacyJsonl, migrateLegacyMarkdown } from './log-migration'
14
- import type { SessionEntry, SessionLogMetadata, SessionStats, SessionMigrationResult } from '../types'
15
20
 
16
21
  export class SessionLogManager {
17
22
  private currentSessionCache: Map<string, string>
@@ -69,7 +74,7 @@ export class SessionLogManager {
69
74
 
70
75
  const exists = await fileHelper.fileExists(filePath)
71
76
  if (!exists && filename === 'shipped.md') {
72
- await fileHelper.writeFile(filePath, '# SHIPPED 🚀\n\n' + content)
77
+ await fileHelper.writeFile(filePath, `# SHIPPED 🚀\n\n${content}`)
73
78
  } else {
74
79
  await fileHelper.appendToFile(filePath, content)
75
80
  }
@@ -218,11 +223,8 @@ export class SessionLogManager {
218
223
  )
219
224
  } else {
220
225
  const sessionPath = await this.getCurrentSession(projectId)
221
- return await migrateLegacyMarkdown(
222
- sessionPath,
223
- content,
224
- sessionFilename,
225
- (sp, u) => this._updateSessionLogMetadata(sp, u)
226
+ return await migrateLegacyMarkdown(sessionPath, content, sessionFilename, (sp, u) =>
227
+ this._updateSessionLogMetadata(sp, u)
226
228
  )
227
229
  }
228
230
  } catch (error) {
@@ -294,13 +296,6 @@ export class SessionLogManager {
294
296
  return dateHelper.getTodayKey()
295
297
  }
296
298
 
297
- /**
298
- * Get date key for any date (YYYY-MM-DD)
299
- */
300
- private _getDateKey(date: Date): string {
301
- return dateHelper.getDateKey(date)
302
- }
303
-
304
299
  clearCache(): void {
305
300
  this.currentSessionCache.clear()
306
301
  this.sessionMetadataCache.clear()