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.
- package/CHANGELOG.md +75 -0
- package/bin/prjct.ts +117 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +58 -39
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +28 -4
- package/core/commands/commands.ts +57 -24
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +13 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +18 -19
- package/core/context-tools/imports-tool.ts +13 -33
- package/core/context-tools/index.ts +29 -54
- package/core/context-tools/recent-tool.ts +16 -22
- package/core/context-tools/signatures-tool.ts +17 -26
- package/core/context-tools/summary-tool.ts +20 -22
- package/core/context-tools/token-counter.ts +25 -20
- package/core/context-tools/types.ts +5 -5
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -16
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +25 -25
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +87 -345
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -17
- package/core/storage/metrics-storage.ts +39 -34
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -305
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +14 -14
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18755 -15574
- package/dist/core/infrastructure/command-installer.js +86 -79
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +246 -225
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- 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 {
|
|
15
|
+
import type { CompactedContext, CompactionConfig, ConversationTurn } from '../types'
|
|
16
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
17
17
|
|
|
18
|
-
export type {
|
|
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) {
|
|
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)
|
|
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),
|
|
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
|
|
package/core/session/index.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
11
|
+
import * as jsonlHelper from '../utils/jsonl-helper'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Migrate legacy JSONL file
|
package/core/session/metrics.ts
CHANGED
|
@@ -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
|
-
|
|
148
|
+
}
|
|
149
|
+
case 'month': {
|
|
149
150
|
const monthAgo = new Date(now)
|
|
150
151
|
monthAgo.setMonth(monthAgo.getMonth() - 1)
|
|
151
152
|
return monthAgo
|
|
152
|
-
|
|
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(
|
|
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 =
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
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()
|