prjct-cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -6
- package/CLAUDE.md +442 -36
- package/README.md +47 -54
- package/bin/prjct +170 -240
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +106 -62
- package/core/commands.js +2030 -2211
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +4 -12
- package/templates/agents/AGENTS.md +101 -27
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +9 -2
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +5 -2
- package/templates/commands/cleanup.md +5 -2
- package/templates/commands/design.md +5 -2
- package/templates/commands/done.md +4 -2
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +41 -10
- package/templates/commands/git.md +7 -2
- package/templates/commands/help.md +2 -2
- package/templates/commands/idea.md +14 -5
- package/templates/commands/init.md +62 -7
- package/templates/commands/next.md +4 -2
- package/templates/commands/now.md +4 -2
- package/templates/commands/progress.md +27 -5
- package/templates/commands/recap.md +39 -10
- package/templates/commands/roadmap.md +19 -5
- package/templates/commands/ship.md +118 -16
- package/templates/commands/status.md +4 -2
- package/templates/commands/sync.md +19 -15
- package/templates/commands/task.md +4 -2
- package/templates/commands/test.md +5 -2
- package/templates/commands/workflow.md +4 -2
- package/core/agent-generator.js +0 -525
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/ascii-graphics.js +0 -433
- package/core/git-integration.js +0 -401
- package/core/task-schema.js +0 -342
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -27
- package/templates/agents/coordinator.template.md +0 -34
- package/templates/agents/data.template.md +0 -27
- package/templates/agents/devops.template.md +0 -27
- package/templates/agents/fe.template.md +0 -27
- package/templates/agents/mobile.template.md +0 -27
- package/templates/agents/qa.template.md +0 -27
- package/templates/agents/scribe.template.md +0 -29
- package/templates/agents/security.template.md +0 -27
- package/templates/agents/ux.template.md +0 -27
- package/templates/commands/context.md +0 -36
- package/templates/commands/stuck.md +0 -36
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
|
@@ -20,7 +20,7 @@ class CapabilityInstaller {
|
|
|
20
20
|
const { stdout, stderr } = await execAsync(command)
|
|
21
21
|
|
|
22
22
|
const duration = Date.now() - startTime
|
|
23
|
-
const durationMin = Math.round(duration / 1000 / 60 * 10) / 10
|
|
23
|
+
const durationMin = Math.round((duration / 1000 / 60) * 10) / 10
|
|
24
24
|
|
|
25
25
|
return {
|
|
26
26
|
success: true,
|
|
@@ -160,10 +160,7 @@ export default defineConfig({
|
|
|
160
160
|
},
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
await fs.writeFile(
|
|
164
|
-
path.join(projectPath, 'jsdoc.json'),
|
|
165
|
-
JSON.stringify(config, null, 2),
|
|
166
|
-
)
|
|
163
|
+
await fs.writeFile(path.join(projectPath, 'jsdoc.json'), JSON.stringify(config, null, 2))
|
|
167
164
|
|
|
168
165
|
// Add docs script
|
|
169
166
|
pkg.scripts = pkg.scripts || {}
|
|
@@ -180,7 +177,7 @@ export default defineConfig({
|
|
|
180
177
|
* Verify installation succeeded
|
|
181
178
|
*/
|
|
182
179
|
async verify(capability, projectPath) {
|
|
183
|
-
const caps = require('
|
|
180
|
+
const caps = require('../utils/project-capabilities')
|
|
184
181
|
const detected = await caps.detect(projectPath)
|
|
185
182
|
|
|
186
183
|
return detected[capability] === true
|
|
@@ -39,7 +39,7 @@ class CommandInstaller {
|
|
|
39
39
|
async getCommandFiles() {
|
|
40
40
|
try {
|
|
41
41
|
const files = await fs.readdir(this.templatesDir)
|
|
42
|
-
return files.filter(f => f.endsWith('.md'))
|
|
42
|
+
return files.filter((f) => f.endsWith('.md'))
|
|
43
43
|
} catch (error) {
|
|
44
44
|
// Fallback to core commands if template directory not accessible
|
|
45
45
|
return [
|
|
@@ -175,7 +175,9 @@ class CommandInstaller {
|
|
|
175
175
|
try {
|
|
176
176
|
await fs.access(this.claudeCommandsPath)
|
|
177
177
|
const files = await fs.readdir(this.claudeCommandsPath)
|
|
178
|
-
const installedCommands = files
|
|
178
|
+
const installedCommands = files
|
|
179
|
+
.filter((f) => f.endsWith('.md'))
|
|
180
|
+
.map((f) => f.replace('.md', ''))
|
|
179
181
|
|
|
180
182
|
return {
|
|
181
183
|
installed: installedCommands.length > 0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs').promises
|
|
2
2
|
const pathManager = require('./path-manager')
|
|
3
|
-
const { VERSION } = require('
|
|
3
|
+
const { VERSION } = require('../utils/version')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ConfigManager - Manages prjct.config.json files
|
|
@@ -234,7 +234,7 @@ class ConfigManager {
|
|
|
234
234
|
const globalConfig = await this.readGlobalConfig(projectId)
|
|
235
235
|
if (!globalConfig || !globalConfig.authors) return null
|
|
236
236
|
|
|
237
|
-
return globalConfig.authors.find(a => a.github === githubUsername) || null
|
|
237
|
+
return globalConfig.authors.find((a) => a.github === githubUsername) || null
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
@@ -248,7 +248,7 @@ class ConfigManager {
|
|
|
248
248
|
async addAuthor(projectId, author) {
|
|
249
249
|
const globalConfig = await this.ensureGlobalConfig(projectId)
|
|
250
250
|
|
|
251
|
-
const exists = globalConfig.authors.some(a => a.github === author.github)
|
|
251
|
+
const exists = globalConfig.authors.some((a) => a.github === author.github)
|
|
252
252
|
if (exists) return
|
|
253
253
|
|
|
254
254
|
const now = new Date().toISOString()
|
|
@@ -276,7 +276,7 @@ class ConfigManager {
|
|
|
276
276
|
const globalConfig = await this.readGlobalConfig(projectId)
|
|
277
277
|
if (!globalConfig || !globalConfig.authors) return
|
|
278
278
|
|
|
279
|
-
const author = globalConfig.authors.find(a => a.github === githubUsername)
|
|
279
|
+
const author = globalConfig.authors.find((a) => a.github === githubUsername)
|
|
280
280
|
if (author) {
|
|
281
281
|
author.lastActivity = new Date().toISOString()
|
|
282
282
|
globalConfig.lastSync = author.lastActivity
|
|
@@ -64,11 +64,7 @@ class EditorsConfig {
|
|
|
64
64
|
path: claudePath,
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
await fs.writeFile(
|
|
68
|
-
this.configFile,
|
|
69
|
-
JSON.stringify(config, null, 2),
|
|
70
|
-
'utf-8',
|
|
71
|
-
)
|
|
67
|
+
await fs.writeFile(this.configFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
72
68
|
|
|
73
69
|
return true
|
|
74
70
|
} catch (error) {
|
|
@@ -111,11 +107,7 @@ class EditorsConfig {
|
|
|
111
107
|
config.version = version
|
|
112
108
|
config.lastInstall = new Date().toISOString()
|
|
113
109
|
|
|
114
|
-
await fs.writeFile(
|
|
115
|
-
this.configFile,
|
|
116
|
-
JSON.stringify(config, null, 2),
|
|
117
|
-
'utf-8',
|
|
118
|
-
)
|
|
110
|
+
await fs.writeFile(this.configFile, JSON.stringify(config, null, 2), 'utf-8')
|
|
119
111
|
|
|
120
112
|
return true
|
|
121
113
|
} catch (error) {
|
|
@@ -64,8 +64,12 @@ class Migrator {
|
|
|
64
64
|
|
|
65
65
|
const globalConfig = await configManager.readGlobalConfig(projectId)
|
|
66
66
|
if (globalConfig && globalConfig.authors && globalConfig.authors.length > 0) {
|
|
67
|
-
const needsCleanup =
|
|
68
|
-
|
|
67
|
+
const needsCleanup =
|
|
68
|
+
localConfig.authors ||
|
|
69
|
+
localConfig.author ||
|
|
70
|
+
localConfig.version ||
|
|
71
|
+
localConfig.created ||
|
|
72
|
+
localConfig.lastSync
|
|
69
73
|
|
|
70
74
|
if (needsCleanup) {
|
|
71
75
|
delete localConfig.authors
|
|
@@ -182,7 +186,11 @@ class Migrator {
|
|
|
182
186
|
return { layer: 'planning', filename }
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
if (
|
|
189
|
+
if (
|
|
190
|
+
filename === 'memory.jsonl' ||
|
|
191
|
+
filename === 'context.jsonl' ||
|
|
192
|
+
filename === 'decisions.jsonl'
|
|
193
|
+
) {
|
|
186
194
|
return { layer: 'memory', filename }
|
|
187
195
|
}
|
|
188
196
|
|
|
@@ -317,9 +325,7 @@ class Migrator {
|
|
|
317
325
|
const layerPath = path.join(legacyPath, layer)
|
|
318
326
|
try {
|
|
319
327
|
await fs.rm(layerPath, { recursive: true, force: true })
|
|
320
|
-
} catch {
|
|
321
|
-
|
|
322
|
-
}
|
|
328
|
+
} catch {}
|
|
323
329
|
}
|
|
324
330
|
}
|
|
325
331
|
|
|
@@ -510,10 +516,20 @@ class Migrator {
|
|
|
510
516
|
if (deepScan) {
|
|
511
517
|
searchPaths = [os.homedir()]
|
|
512
518
|
} else {
|
|
513
|
-
const commonDirs = [
|
|
519
|
+
const commonDirs = [
|
|
520
|
+
'Projects',
|
|
521
|
+
'Documents',
|
|
522
|
+
'Developer',
|
|
523
|
+
'Code',
|
|
524
|
+
'dev',
|
|
525
|
+
'workspace',
|
|
526
|
+
'repos',
|
|
527
|
+
'src',
|
|
528
|
+
'Apps',
|
|
529
|
+
]
|
|
514
530
|
searchPaths = commonDirs
|
|
515
|
-
.map(dir => path.join(os.homedir(), dir))
|
|
516
|
-
.filter(dirPath => {
|
|
531
|
+
.map((dir) => path.join(os.homedir(), dir))
|
|
532
|
+
.filter((dirPath) => {
|
|
517
533
|
try {
|
|
518
534
|
fs.accessSync(dirPath)
|
|
519
535
|
return true
|
|
@@ -540,13 +556,13 @@ class Migrator {
|
|
|
540
556
|
return skipDirs.includes(dirName) || (dirName.startsWith('.') && dirName !== '.prjct')
|
|
541
557
|
}
|
|
542
558
|
|
|
543
|
-
const searchDirectory = async(dirPath, depth = 0) => {
|
|
559
|
+
const searchDirectory = async (dirPath, depth = 0) => {
|
|
544
560
|
if (depth > 10) return
|
|
545
561
|
|
|
546
562
|
try {
|
|
547
563
|
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
548
564
|
|
|
549
|
-
if (entries.some(entry => entry.name === '.prjct' && entry.isDirectory())) {
|
|
565
|
+
if (entries.some((entry) => entry.name === '.prjct' && entry.isDirectory())) {
|
|
550
566
|
projectDirs.push(dirPath)
|
|
551
567
|
return // Don't search subdirectories if we found a project
|
|
552
568
|
}
|
|
@@ -557,9 +573,7 @@ class Migrator {
|
|
|
557
573
|
await searchDirectory(subPath, depth + 1)
|
|
558
574
|
}
|
|
559
575
|
}
|
|
560
|
-
} catch (error) {
|
|
561
|
-
|
|
562
|
-
}
|
|
576
|
+
} catch (error) {}
|
|
563
577
|
}
|
|
564
578
|
|
|
565
579
|
for (const searchPath of searchPaths) {
|
|
@@ -637,7 +651,8 @@ class Migrator {
|
|
|
637
651
|
|
|
638
652
|
if (status.status === 'migrated' || status.status === 'new') {
|
|
639
653
|
projectInfo.result = 'skipped'
|
|
640
|
-
projectInfo.reason =
|
|
654
|
+
projectInfo.reason =
|
|
655
|
+
status.status === 'migrated' ? 'Already migrated' : 'Not initialized'
|
|
641
656
|
summary.alreadyMigrated++
|
|
642
657
|
} else if (status.needsMigration) {
|
|
643
658
|
if (interactive && onProgress) {
|
|
@@ -749,8 +764,8 @@ class Migrator {
|
|
|
749
764
|
if (summary.successfullyMigrated > 0) {
|
|
750
765
|
lines.push('✅ Successfully Migrated:')
|
|
751
766
|
summary.projects
|
|
752
|
-
.filter(p => p.result === 'success')
|
|
753
|
-
.forEach(project => {
|
|
767
|
+
.filter((p) => p.result === 'success')
|
|
768
|
+
.forEach((project) => {
|
|
754
769
|
lines.push(` • ${project.name}`)
|
|
755
770
|
lines.push(` Files: ${project.filesCopied} | ID: ${project.projectId}`)
|
|
756
771
|
})
|
|
@@ -759,9 +774,9 @@ class Migrator {
|
|
|
759
774
|
|
|
760
775
|
if (summary.errors.length > 0) {
|
|
761
776
|
lines.push('❌ Errors:')
|
|
762
|
-
summary.errors.forEach(error => {
|
|
777
|
+
summary.errors.forEach((error) => {
|
|
763
778
|
lines.push(` • ${error.project}`)
|
|
764
|
-
error.issues.forEach(issue => lines.push(` - ${issue}`))
|
|
779
|
+
error.issues.forEach((issue) => lines.push(` - ${issue}`))
|
|
765
780
|
})
|
|
766
781
|
lines.push('')
|
|
767
782
|
}
|
|
@@ -2,6 +2,8 @@ const fs = require('fs').promises
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const crypto = require('crypto')
|
|
4
4
|
const os = require('os')
|
|
5
|
+
const dateHelper = require('../utils/date-helper')
|
|
6
|
+
const fileHelper = require('../utils/file-helper')
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* PathManager - Manages project paths between local and global storage
|
|
@@ -90,13 +92,8 @@ class PathManager {
|
|
|
90
92
|
* @returns {Promise<boolean>} - True if legacy directory exists
|
|
91
93
|
*/
|
|
92
94
|
async hasLegacyStructure(projectPath) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
await fs.access(legacyPath)
|
|
96
|
-
return true
|
|
97
|
-
} catch {
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
95
|
+
const legacyPath = this.getLegacyPrjctPath(projectPath)
|
|
96
|
+
return await fileHelper.dirExists(legacyPath)
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
/**
|
|
@@ -106,13 +103,8 @@ class PathManager {
|
|
|
106
103
|
* @returns {Promise<boolean>} - True if config exists
|
|
107
104
|
*/
|
|
108
105
|
async hasConfig(projectPath) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await fs.access(configPath)
|
|
112
|
-
return true
|
|
113
|
-
} catch {
|
|
114
|
-
return false
|
|
115
|
-
}
|
|
106
|
+
const configPath = this.getLocalConfigPath(projectPath)
|
|
107
|
+
return await fileHelper.fileExists(configPath)
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
/**
|
|
@@ -122,9 +114,9 @@ class PathManager {
|
|
|
122
114
|
* @returns {Promise<void>}
|
|
123
115
|
*/
|
|
124
116
|
async ensureGlobalStructure() {
|
|
125
|
-
await
|
|
126
|
-
await
|
|
127
|
-
await
|
|
117
|
+
await fileHelper.ensureDir(this.globalBaseDir)
|
|
118
|
+
await fileHelper.ensureDir(this.globalProjectsDir)
|
|
119
|
+
await fileHelper.ensureDir(this.globalConfigDir)
|
|
128
120
|
}
|
|
129
121
|
|
|
130
122
|
/**
|
|
@@ -142,12 +134,11 @@ class PathManager {
|
|
|
142
134
|
const layers = ['core', 'progress', 'planning', 'analysis', 'memory']
|
|
143
135
|
|
|
144
136
|
for (const layer of layers) {
|
|
145
|
-
await
|
|
137
|
+
await fileHelper.ensureDir(path.join(projectPath, layer))
|
|
146
138
|
}
|
|
147
139
|
|
|
148
|
-
await
|
|
149
|
-
|
|
150
|
-
await fs.mkdir(path.join(projectPath, 'sessions'), { recursive: true })
|
|
140
|
+
await fileHelper.ensureDir(path.join(projectPath, 'planning', 'tasks'))
|
|
141
|
+
await fileHelper.ensureDir(path.join(projectPath, 'sessions'))
|
|
151
142
|
|
|
152
143
|
return projectPath
|
|
153
144
|
}
|
|
@@ -161,17 +152,9 @@ class PathManager {
|
|
|
161
152
|
* @returns {string} - Path to session directory
|
|
162
153
|
*/
|
|
163
154
|
getSessionPath(projectId, date = new Date()) {
|
|
164
|
-
const year =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return path.join(
|
|
169
|
-
this.getGlobalProjectPath(projectId),
|
|
170
|
-
'sessions',
|
|
171
|
-
year,
|
|
172
|
-
month,
|
|
173
|
-
day,
|
|
174
|
-
)
|
|
155
|
+
const { year, month, day } = dateHelper.getYearMonthDay(date)
|
|
156
|
+
|
|
157
|
+
return path.join(this.getGlobalProjectPath(projectId), 'sessions', year, month, day)
|
|
175
158
|
}
|
|
176
159
|
|
|
177
160
|
/**
|
|
@@ -193,7 +176,7 @@ class PathManager {
|
|
|
193
176
|
*/
|
|
194
177
|
async ensureSessionPath(projectId, date = new Date()) {
|
|
195
178
|
const sessionPath = this.getSessionPath(projectId, date)
|
|
196
|
-
await
|
|
179
|
+
await fileHelper.ensureDir(sessionPath)
|
|
197
180
|
return sessionPath
|
|
198
181
|
}
|
|
199
182
|
|
|
@@ -258,7 +241,7 @@ class PathManager {
|
|
|
258
241
|
async getSessionsInRange(projectId, fromDate, toDate = new Date()) {
|
|
259
242
|
const allSessions = await this.listSessions(projectId)
|
|
260
243
|
|
|
261
|
-
return allSessions.filter(session => session.date >= fromDate && session.date <= toDate)
|
|
244
|
+
return allSessions.filter((session) => session.date >= fromDate && session.date <= toDate)
|
|
262
245
|
}
|
|
263
246
|
|
|
264
247
|
/**
|
|
@@ -282,9 +265,7 @@ class PathManager {
|
|
|
282
265
|
try {
|
|
283
266
|
await this.ensureGlobalStructure()
|
|
284
267
|
const entries = await fs.readdir(this.globalProjectsDir, { withFileTypes: true })
|
|
285
|
-
return entries
|
|
286
|
-
.filter(entry => entry.isDirectory())
|
|
287
|
-
.map(entry => entry.name)
|
|
268
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
288
269
|
} catch {
|
|
289
270
|
return []
|
|
290
271
|
}
|
|
@@ -297,13 +278,8 @@ class PathManager {
|
|
|
297
278
|
* @returns {Promise<boolean>} - True if project exists
|
|
298
279
|
*/
|
|
299
280
|
async projectExists(projectId) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
await fs.access(projectPath)
|
|
303
|
-
return true
|
|
304
|
-
} catch {
|
|
305
|
-
return false
|
|
306
|
-
}
|
|
281
|
+
const projectPath = this.getGlobalProjectPath(projectId)
|
|
282
|
+
return await fileHelper.dirExists(projectPath)
|
|
307
283
|
}
|
|
308
284
|
|
|
309
285
|
/**
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const fs = require('fs').promises
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const pathManager = require('./path-manager')
|
|
4
|
-
const { VERSION } = require('
|
|
4
|
+
const { VERSION } = require('../utils/version')
|
|
5
|
+
const dateHelper = require('../utils/date-helper')
|
|
6
|
+
const jsonlHelper = require('../utils/jsonl-helper')
|
|
7
|
+
const fileHelper = require('../utils/file-helper')
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* SessionManager - Manages temporal fragmentation of logs and progress data
|
|
@@ -54,18 +57,11 @@ class SessionManager {
|
|
|
54
57
|
const sessionPath = await this.getCurrentSession(projectId)
|
|
55
58
|
const filePath = path.join(sessionPath, filename)
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const existing = await fs.readFile(filePath, 'utf-8')
|
|
61
|
-
await fs.writeFile(filePath, existing + logLine, 'utf-8')
|
|
62
|
-
} catch {
|
|
63
|
-
await fs.writeFile(filePath, logLine, 'utf-8')
|
|
64
|
-
}
|
|
60
|
+
await jsonlHelper.appendJsonLine(filePath, entry)
|
|
65
61
|
|
|
66
62
|
await this._updateSessionMetadata(sessionPath, {
|
|
67
|
-
lastActivity:
|
|
68
|
-
entryCount: await
|
|
63
|
+
lastActivity: dateHelper.getTimestamp(),
|
|
64
|
+
entryCount: await jsonlHelper.countJsonLines(filePath),
|
|
69
65
|
})
|
|
70
66
|
}
|
|
71
67
|
|
|
@@ -81,19 +77,15 @@ class SessionManager {
|
|
|
81
77
|
const sessionPath = await this.getCurrentSession(projectId)
|
|
82
78
|
const filePath = path.join(sessionPath, filename)
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (filename === 'shipped.md') {
|
|
90
|
-
initialContent = '# SHIPPED 🚀\n\n'
|
|
91
|
-
}
|
|
92
|
-
await fs.writeFile(filePath, initialContent + content, 'utf-8')
|
|
80
|
+
const exists = await fileHelper.fileExists(filePath)
|
|
81
|
+
if (!exists && filename === 'shipped.md') {
|
|
82
|
+
await fileHelper.writeFile(filePath, '# SHIPPED 🚀\n\n' + content)
|
|
83
|
+
} else {
|
|
84
|
+
await fileHelper.appendToFile(filePath, content)
|
|
93
85
|
}
|
|
94
86
|
|
|
95
87
|
await this._updateSessionMetadata(sessionPath, {
|
|
96
|
-
lastActivity:
|
|
88
|
+
lastActivity: dateHelper.getTimestamp(),
|
|
97
89
|
})
|
|
98
90
|
}
|
|
99
91
|
|
|
@@ -108,12 +100,7 @@ class SessionManager {
|
|
|
108
100
|
const sessionPath = await this.getCurrentSession(projectId)
|
|
109
101
|
const filePath = path.join(sessionPath, filename)
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
113
|
-
return this._parseJsonLines(content)
|
|
114
|
-
} catch {
|
|
115
|
-
return []
|
|
116
|
-
}
|
|
103
|
+
return await jsonlHelper.readJsonLines(filePath)
|
|
117
104
|
}
|
|
118
105
|
|
|
119
106
|
/**
|
|
@@ -131,18 +118,13 @@ class SessionManager {
|
|
|
131
118
|
|
|
132
119
|
for (const session of sessions) {
|
|
133
120
|
const filePath = path.join(session.path, filename)
|
|
134
|
-
|
|
135
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
136
|
-
const entries = this._parseJsonLines(content)
|
|
121
|
+
const entries = await jsonlHelper.readJsonLines(filePath)
|
|
137
122
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
entries.forEach((entry) => {
|
|
124
|
+
entry._sessionDate = session.date
|
|
125
|
+
})
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
} catch {
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
127
|
+
allEntries.push(...entries)
|
|
146
128
|
}
|
|
147
129
|
|
|
148
130
|
return allEntries
|
|
@@ -163,13 +145,12 @@ class SessionManager {
|
|
|
163
145
|
|
|
164
146
|
for (const session of sessions) {
|
|
165
147
|
const filePath = path.join(session.path, filename)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
continue
|
|
148
|
+
const content = await fileHelper.readFile(filePath, '')
|
|
149
|
+
|
|
150
|
+
if (content.trim()) {
|
|
151
|
+
allContent.push(
|
|
152
|
+
`## Session: ${session.year}-${session.month}-${session.day}\n\n${content}`
|
|
153
|
+
)
|
|
173
154
|
}
|
|
174
155
|
}
|
|
175
156
|
|
|
@@ -186,8 +167,7 @@ class SessionManager {
|
|
|
186
167
|
*/
|
|
187
168
|
async getRecentLogs(projectId, days = 7, filename = 'context.jsonl') {
|
|
188
169
|
const toDate = new Date()
|
|
189
|
-
const fromDate =
|
|
190
|
-
fromDate.setDate(fromDate.getDate() - days)
|
|
170
|
+
const fromDate = dateHelper.getDaysAgo(days)
|
|
191
171
|
|
|
192
172
|
return await this.readSessionRange(projectId, fromDate, toDate, filename)
|
|
193
173
|
}
|
|
@@ -237,7 +217,7 @@ class SessionManager {
|
|
|
237
217
|
*/
|
|
238
218
|
async migrateLegacyLogs(projectId, legacyFilePath, sessionFilename) {
|
|
239
219
|
try {
|
|
240
|
-
const content = await
|
|
220
|
+
const content = await fileHelper.readFile(legacyFilePath)
|
|
241
221
|
|
|
242
222
|
if (sessionFilename.endsWith('.jsonl')) {
|
|
243
223
|
return await this._migrateLegacyJsonl(projectId, content, sessionFilename)
|
|
@@ -258,12 +238,12 @@ class SessionManager {
|
|
|
258
238
|
* @private
|
|
259
239
|
*/
|
|
260
240
|
async _migrateLegacyJsonl(projectId, content, sessionFilename) {
|
|
261
|
-
const entries =
|
|
241
|
+
const entries = jsonlHelper.parseJsonLines(content)
|
|
262
242
|
const sessionGroups = new Map()
|
|
263
243
|
|
|
264
244
|
for (const entry of entries) {
|
|
265
245
|
const date = new Date(entry.timestamp || entry.data?.timestamp || Date.now())
|
|
266
|
-
const dateKey =
|
|
246
|
+
const dateKey = dateHelper.getDateKey(date)
|
|
267
247
|
|
|
268
248
|
if (!sessionGroups.has(dateKey)) {
|
|
269
249
|
sessionGroups.set(dateKey, [])
|
|
@@ -278,8 +258,7 @@ class SessionManager {
|
|
|
278
258
|
const sessionPath = await pathManager.ensureSessionPath(projectId, date)
|
|
279
259
|
const filePath = path.join(sessionPath, sessionFilename)
|
|
280
260
|
|
|
281
|
-
|
|
282
|
-
await fs.writeFile(filePath, content, 'utf-8')
|
|
261
|
+
await jsonlHelper.writeJsonLines(filePath, groupEntries)
|
|
283
262
|
|
|
284
263
|
migratedCount += groupEntries.length
|
|
285
264
|
|
|
@@ -287,7 +266,7 @@ class SessionManager {
|
|
|
287
266
|
await this._updateSessionMetadata(sessionPath, {
|
|
288
267
|
entryCount: groupEntries.length,
|
|
289
268
|
migrated: true,
|
|
290
|
-
migratedAt:
|
|
269
|
+
migratedAt: dateHelper.getTimestamp(),
|
|
291
270
|
})
|
|
292
271
|
}
|
|
293
272
|
|
|
@@ -307,11 +286,11 @@ class SessionManager {
|
|
|
307
286
|
const sessionPath = await this.getCurrentSession(projectId)
|
|
308
287
|
const filePath = path.join(sessionPath, sessionFilename)
|
|
309
288
|
|
|
310
|
-
await
|
|
289
|
+
await fileHelper.writeFile(filePath, content)
|
|
311
290
|
|
|
312
291
|
await this._updateSessionMetadata(sessionPath, {
|
|
313
292
|
migrated: true,
|
|
314
|
-
migratedAt:
|
|
293
|
+
migratedAt: dateHelper.getTimestamp(),
|
|
315
294
|
})
|
|
316
295
|
|
|
317
296
|
return {
|
|
@@ -333,14 +312,11 @@ class SessionManager {
|
|
|
333
312
|
return this.sessionMetadataCache.get(sessionPath)
|
|
334
313
|
}
|
|
335
314
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const metadata = JSON.parse(content)
|
|
315
|
+
const metadata = await fileHelper.readJson(metadataPath, null)
|
|
316
|
+
if (metadata) {
|
|
339
317
|
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
340
|
-
return metadata
|
|
341
|
-
} catch {
|
|
342
|
-
return null
|
|
343
318
|
}
|
|
319
|
+
return metadata
|
|
344
320
|
}
|
|
345
321
|
|
|
346
322
|
/**
|
|
@@ -350,18 +326,16 @@ class SessionManager {
|
|
|
350
326
|
async _ensureSessionMetadata(sessionPath) {
|
|
351
327
|
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
352
328
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
} catch {
|
|
356
|
-
// Create initial metadata
|
|
329
|
+
const exists = await fileHelper.fileExists(metadataPath)
|
|
330
|
+
if (!exists) {
|
|
357
331
|
const metadata = {
|
|
358
|
-
created:
|
|
359
|
-
lastActivity:
|
|
332
|
+
created: dateHelper.getTimestamp(),
|
|
333
|
+
lastActivity: dateHelper.getTimestamp(),
|
|
360
334
|
entryCount: 0,
|
|
361
335
|
shipCount: 0,
|
|
362
336
|
version: VERSION,
|
|
363
337
|
}
|
|
364
|
-
await
|
|
338
|
+
await fileHelper.writeJson(metadataPath, metadata)
|
|
365
339
|
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
366
340
|
}
|
|
367
341
|
}
|
|
@@ -371,52 +345,21 @@ class SessionManager {
|
|
|
371
345
|
* @private
|
|
372
346
|
*/
|
|
373
347
|
async _updateSessionMetadata(sessionPath, updates) {
|
|
374
|
-
const metadata = await this._getSessionMetadata(sessionPath) || {}
|
|
348
|
+
const metadata = (await this._getSessionMetadata(sessionPath)) || {}
|
|
375
349
|
Object.assign(metadata, updates)
|
|
376
350
|
|
|
377
351
|
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
378
|
-
await
|
|
352
|
+
await fileHelper.writeJson(metadataPath, metadata)
|
|
379
353
|
|
|
380
354
|
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
381
355
|
}
|
|
382
356
|
|
|
383
|
-
/**
|
|
384
|
-
* Parse JSONL content
|
|
385
|
-
* @private
|
|
386
|
-
*/
|
|
387
|
-
_parseJsonLines(content) {
|
|
388
|
-
const lines = content.split('\n').filter(line => line.trim())
|
|
389
|
-
const entries = []
|
|
390
|
-
|
|
391
|
-
for (const line of lines) {
|
|
392
|
-
try {
|
|
393
|
-
entries.push(JSON.parse(line))
|
|
394
|
-
} catch {
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return entries
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Get line count from file
|
|
403
|
-
* @private
|
|
404
|
-
*/
|
|
405
|
-
async _getFileLineCount(filePath) {
|
|
406
|
-
try {
|
|
407
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
408
|
-
return content.split('\n').filter(line => line.trim()).length
|
|
409
|
-
} catch {
|
|
410
|
-
return 0
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
357
|
/**
|
|
415
358
|
* Get today's date key (YYYY-MM-DD)
|
|
416
359
|
* @private
|
|
417
360
|
*/
|
|
418
361
|
_getTodayKey() {
|
|
419
|
-
return
|
|
362
|
+
return dateHelper.getTodayKey()
|
|
420
363
|
}
|
|
421
364
|
|
|
422
365
|
/**
|
|
@@ -424,10 +367,7 @@ class SessionManager {
|
|
|
424
367
|
* @private
|
|
425
368
|
*/
|
|
426
369
|
_getDateKey(date) {
|
|
427
|
-
|
|
428
|
-
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
429
|
-
const day = date.getDate().toString().padStart(2, '0')
|
|
430
|
-
return `${year}-${month}-${day}`
|
|
370
|
+
return dateHelper.getDateKey(date)
|
|
431
371
|
}
|
|
432
372
|
|
|
433
373
|
clearCache() {
|