prjct-cli 0.20.0 → 0.21.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 +24 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/integrations/notion/client.ts +323 -0
- package/core/integrations/notion/index.ts +43 -0
- package/core/integrations/notion/setup.ts +230 -0
- package/core/integrations/notion/sync.ts +311 -0
- package/core/integrations/notion/templates.ts +234 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/plugin/builtin/notion.ts +178 -0
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +70 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +396 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/types/integrations.ts +57 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/init.md +43 -0
- package/templates/commands/notion-setup.md +191 -0
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +13 -2
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +1 -1
- package/templates/commands/test.md +23 -4
- package/templates/mcp-config.json +28 -0
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from 'path'
|
|
7
|
-
import pathManager from '../path-manager'
|
|
8
|
-
import * as dateHelper from '
|
|
9
|
-
import * as jsonlHelper from '
|
|
10
|
-
import * as fileHelper from '
|
|
11
|
-
import type { SessionEntry,
|
|
7
|
+
import pathManager from '../infrastructure/path-manager'
|
|
8
|
+
import * as dateHelper from '../utils/date-helper'
|
|
9
|
+
import * as jsonlHelper from '../utils/jsonl-helper'
|
|
10
|
+
import * as fileHelper from '../utils/file-helper'
|
|
11
|
+
import type { SessionEntry, SessionMigrationResult, SessionLogMetadata } from '../types'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Migrate legacy JSONL file
|
|
@@ -17,9 +17,9 @@ export async function migrateLegacyJsonl(
|
|
|
17
17
|
projectId: string,
|
|
18
18
|
content: string,
|
|
19
19
|
sessionFilename: string,
|
|
20
|
-
updateMetadata: (sessionPath: string, updates: Partial<
|
|
20
|
+
updateMetadata: (sessionPath: string, updates: Partial<SessionLogMetadata>) => Promise<void>,
|
|
21
21
|
ensureMetadata: (sessionPath: string) => Promise<void>
|
|
22
|
-
): Promise<
|
|
22
|
+
): Promise<SessionMigrationResult> {
|
|
23
23
|
const entries = jsonlHelper.parseJsonLines(content) as SessionEntry[]
|
|
24
24
|
const sessionGroups = new Map<string, SessionEntry[]>()
|
|
25
25
|
|
|
@@ -68,8 +68,8 @@ export async function migrateLegacyMarkdown(
|
|
|
68
68
|
sessionPath: string,
|
|
69
69
|
content: string,
|
|
70
70
|
sessionFilename: string,
|
|
71
|
-
updateMetadata: (sessionPath: string, updates: Partial<
|
|
72
|
-
): Promise<
|
|
71
|
+
updateMetadata: (sessionPath: string, updates: Partial<SessionLogMetadata>) => Promise<void>
|
|
72
|
+
): Promise<SessionMigrationResult> {
|
|
73
73
|
const filePath = path.join(sessionPath, sessionFilename)
|
|
74
74
|
|
|
75
75
|
await fileHelper.writeFile(filePath, content)
|
package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts}
RENAMED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SessionLogManager Class
|
|
3
3
|
* Manages temporal fragmentation of logs and progress data.
|
|
4
|
+
* Writes to sessions/YYYY-MM/DD/ structure with auto-rotation.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import path from 'path'
|
|
7
|
-
import pathManager from '../path-manager'
|
|
8
|
-
import { VERSION } from '
|
|
9
|
-
import * as dateHelper from '
|
|
10
|
-
import * as jsonlHelper from '
|
|
11
|
-
import * as fileHelper from '
|
|
12
|
-
import { migrateLegacyJsonl, migrateLegacyMarkdown } from './migration'
|
|
13
|
-
import type { SessionEntry,
|
|
14
|
-
|
|
15
|
-
export class
|
|
8
|
+
import pathManager from '../infrastructure/path-manager'
|
|
9
|
+
import { VERSION } from '../utils/version'
|
|
10
|
+
import * as dateHelper from '../utils/date-helper'
|
|
11
|
+
import * as jsonlHelper from '../utils/jsonl-helper'
|
|
12
|
+
import * as fileHelper from '../utils/file-helper'
|
|
13
|
+
import { migrateLegacyJsonl, migrateLegacyMarkdown } from './log-migration'
|
|
14
|
+
import type { SessionEntry, SessionLogMetadata, SessionStats, SessionMigrationResult } from '../types'
|
|
15
|
+
|
|
16
|
+
export class SessionLogManager {
|
|
16
17
|
private currentSessionCache: Map<string, string>
|
|
17
|
-
private sessionMetadataCache: Map<string,
|
|
18
|
+
private sessionMetadataCache: Map<string, SessionLogMetadata>
|
|
18
19
|
|
|
19
20
|
constructor() {
|
|
20
21
|
this.currentSessionCache = new Map()
|
|
@@ -34,7 +35,7 @@ export class SessionManager {
|
|
|
34
35
|
const sessionPath = await pathManager.ensureSessionPath(projectId)
|
|
35
36
|
this.currentSessionCache.set(cacheKey, sessionPath)
|
|
36
37
|
|
|
37
|
-
await this.
|
|
38
|
+
await this._ensureSessionLogMetadata(sessionPath)
|
|
38
39
|
|
|
39
40
|
return sessionPath
|
|
40
41
|
}
|
|
@@ -53,7 +54,7 @@ export class SessionManager {
|
|
|
53
54
|
// Use automatic rotation to prevent large files (>10MB)
|
|
54
55
|
await jsonlHelper.appendJsonLineWithRotation(filePath, entry, 10)
|
|
55
56
|
|
|
56
|
-
await this.
|
|
57
|
+
await this._updateSessionLogMetadata(sessionPath, {
|
|
57
58
|
lastActivity: dateHelper.getTimestamp(),
|
|
58
59
|
entryCount: await jsonlHelper.countJsonLines(filePath),
|
|
59
60
|
})
|
|
@@ -73,7 +74,7 @@ export class SessionManager {
|
|
|
73
74
|
await fileHelper.appendToFile(filePath, content)
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
await this.
|
|
77
|
+
await this._updateSessionLogMetadata(sessionPath, {
|
|
77
78
|
lastActivity: dateHelper.getTimestamp(),
|
|
78
79
|
})
|
|
79
80
|
}
|
|
@@ -177,7 +178,7 @@ export class SessionManager {
|
|
|
177
178
|
let activeDays = 0
|
|
178
179
|
|
|
179
180
|
for (const session of sessions) {
|
|
180
|
-
const metadata = await this.
|
|
181
|
+
const metadata = await this._getSessionLogMetadata(session.path)
|
|
181
182
|
if (metadata) {
|
|
182
183
|
totalEntries += metadata.entryCount || 0
|
|
183
184
|
totalShips += metadata.shipCount || 0
|
|
@@ -203,7 +204,7 @@ export class SessionManager {
|
|
|
203
204
|
projectId: string,
|
|
204
205
|
legacyFilePath: string,
|
|
205
206
|
sessionFilename: string
|
|
206
|
-
): Promise<
|
|
207
|
+
): Promise<SessionMigrationResult> {
|
|
207
208
|
try {
|
|
208
209
|
const content = await fileHelper.readFile(legacyFilePath)
|
|
209
210
|
|
|
@@ -212,8 +213,8 @@ export class SessionManager {
|
|
|
212
213
|
projectId,
|
|
213
214
|
content,
|
|
214
215
|
sessionFilename,
|
|
215
|
-
(sp, u) => this.
|
|
216
|
-
(sp) => this.
|
|
216
|
+
(sp, u) => this._updateSessionLogMetadata(sp, u),
|
|
217
|
+
(sp) => this._ensureSessionLogMetadata(sp)
|
|
217
218
|
)
|
|
218
219
|
} else {
|
|
219
220
|
const sessionPath = await this.getCurrentSession(projectId)
|
|
@@ -221,7 +222,7 @@ export class SessionManager {
|
|
|
221
222
|
sessionPath,
|
|
222
223
|
content,
|
|
223
224
|
sessionFilename,
|
|
224
|
-
(sp, u) => this.
|
|
225
|
+
(sp, u) => this._updateSessionLogMetadata(sp, u)
|
|
225
226
|
)
|
|
226
227
|
}
|
|
227
228
|
} catch (error) {
|
|
@@ -236,14 +237,14 @@ export class SessionManager {
|
|
|
236
237
|
/**
|
|
237
238
|
* Get session metadata
|
|
238
239
|
*/
|
|
239
|
-
private async
|
|
240
|
+
private async _getSessionLogMetadata(sessionPath: string): Promise<SessionLogMetadata | null> {
|
|
240
241
|
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
241
242
|
|
|
242
243
|
if (this.sessionMetadataCache.has(sessionPath)) {
|
|
243
244
|
return this.sessionMetadataCache.get(sessionPath)!
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
const metadata = await fileHelper.readJson<
|
|
247
|
+
const metadata = await fileHelper.readJson<SessionLogMetadata>(metadataPath, null)
|
|
247
248
|
if (metadata) {
|
|
248
249
|
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
249
250
|
}
|
|
@@ -253,12 +254,12 @@ export class SessionManager {
|
|
|
253
254
|
/**
|
|
254
255
|
* Ensure session metadata exists
|
|
255
256
|
*/
|
|
256
|
-
private async
|
|
257
|
+
private async _ensureSessionLogMetadata(sessionPath: string): Promise<void> {
|
|
257
258
|
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
258
259
|
|
|
259
260
|
const exists = await fileHelper.fileExists(metadataPath)
|
|
260
261
|
if (!exists) {
|
|
261
|
-
const metadata:
|
|
262
|
+
const metadata: SessionLogMetadata = {
|
|
262
263
|
created: dateHelper.getTimestamp(),
|
|
263
264
|
lastActivity: dateHelper.getTimestamp(),
|
|
264
265
|
entryCount: 0,
|
|
@@ -273,11 +274,11 @@ export class SessionManager {
|
|
|
273
274
|
/**
|
|
274
275
|
* Update session metadata
|
|
275
276
|
*/
|
|
276
|
-
private async
|
|
277
|
+
private async _updateSessionLogMetadata(
|
|
277
278
|
sessionPath: string,
|
|
278
|
-
updates: Partial<
|
|
279
|
+
updates: Partial<SessionLogMetadata>
|
|
279
280
|
): Promise<void> {
|
|
280
|
-
const metadata = (await this.
|
|
281
|
+
const metadata = (await this._getSessionLogMetadata(sessionPath)) || {}
|
|
281
282
|
Object.assign(metadata, updates)
|
|
282
283
|
|
|
283
284
|
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* TaskSessionManager Class
|
|
3
|
+
* Manages task lifecycle: create, pause, resume, complete
|
|
4
|
+
* Tracks metrics, timeline, and archives completed sessions.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import fs from 'fs/promises'
|
|
@@ -10,12 +11,12 @@ import { promisify } from 'util'
|
|
|
10
11
|
import pathManager from '../infrastructure/path-manager'
|
|
11
12
|
import configManager from '../infrastructure/config-manager'
|
|
12
13
|
import { emit } from '../bus'
|
|
13
|
-
import type { Session, SessionMetrics } from '
|
|
14
|
+
import type { Session, SessionMetrics } from '../types'
|
|
14
15
|
import { generateId, calculateDuration, formatDuration } from './utils'
|
|
15
16
|
|
|
16
17
|
const execAsync = promisify(exec)
|
|
17
18
|
|
|
18
|
-
export class
|
|
19
|
+
export class TaskSessionManager {
|
|
19
20
|
private projectPath: string
|
|
20
21
|
private projectId: string | null
|
|
21
22
|
private sessionDir: string | null
|
|
@@ -89,7 +90,9 @@ export class SessionManager {
|
|
|
89
90
|
completedAt: null,
|
|
90
91
|
duration: 0,
|
|
91
92
|
metrics: {
|
|
93
|
+
filesCreated: 0,
|
|
92
94
|
filesChanged: 0,
|
|
95
|
+
filesModified: 0,
|
|
93
96
|
linesAdded: 0,
|
|
94
97
|
linesRemoved: 0,
|
|
95
98
|
commits: 0,
|
package/core/session/utils.ts
CHANGED
|
@@ -7,24 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { StorageManager } from './storage-manager'
|
|
9
9
|
import { generateUUID } from '../schemas'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export type IdeaPriority = 'low' | 'medium' | 'high'
|
|
13
|
-
|
|
14
|
-
export interface Idea {
|
|
15
|
-
id: string
|
|
16
|
-
text: string
|
|
17
|
-
status: IdeaStatus
|
|
18
|
-
priority: IdeaPriority
|
|
19
|
-
tags: string[]
|
|
20
|
-
addedAt: string
|
|
21
|
-
convertedTo?: string // featureId if converted
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface IdeasJson {
|
|
25
|
-
ideas: Idea[]
|
|
26
|
-
lastUpdated: string
|
|
27
|
-
}
|
|
10
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
11
|
+
import type { Idea, IdeasJson, IdeaStatus, IdeaPriority } from '../types'
|
|
28
12
|
|
|
29
13
|
class IdeasStorage extends StorageManager<IdeasJson> {
|
|
30
14
|
constructor() {
|
|
@@ -127,12 +111,12 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
127
111
|
status: 'pending',
|
|
128
112
|
priority: options.priority || 'medium',
|
|
129
113
|
tags: options.tags || [],
|
|
130
|
-
addedAt:
|
|
114
|
+
addedAt: getTimestamp()
|
|
131
115
|
}
|
|
132
116
|
|
|
133
117
|
await this.update(projectId, (data) => ({
|
|
134
118
|
ideas: [idea, ...data.ideas], // Prepend new ideas
|
|
135
|
-
lastUpdated:
|
|
119
|
+
lastUpdated: getTimestamp()
|
|
136
120
|
}))
|
|
137
121
|
|
|
138
122
|
// Publish event
|
|
@@ -167,7 +151,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
167
151
|
? { ...i, status: 'converted' as IdeaStatus, convertedTo: featureId }
|
|
168
152
|
: i
|
|
169
153
|
),
|
|
170
|
-
lastUpdated:
|
|
154
|
+
lastUpdated: getTimestamp()
|
|
171
155
|
}))
|
|
172
156
|
|
|
173
157
|
await this.publishEvent(projectId, 'idea.converted', {
|
|
@@ -184,7 +168,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
184
168
|
ideas: data.ideas.map(i =>
|
|
185
169
|
i.id === ideaId ? { ...i, status: 'archived' as IdeaStatus } : i
|
|
186
170
|
),
|
|
187
|
-
lastUpdated:
|
|
171
|
+
lastUpdated: getTimestamp()
|
|
188
172
|
}))
|
|
189
173
|
|
|
190
174
|
await this.publishEvent(projectId, 'idea.archived', { ideaId })
|
|
@@ -202,7 +186,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
202
186
|
ideas: data.ideas.map(i =>
|
|
203
187
|
i.id === ideaId ? { ...i, priority } : i
|
|
204
188
|
),
|
|
205
|
-
lastUpdated:
|
|
189
|
+
lastUpdated: getTimestamp()
|
|
206
190
|
}))
|
|
207
191
|
}
|
|
208
192
|
|
|
@@ -220,7 +204,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
220
204
|
? { ...i, tags: [...new Set([...i.tags, ...tags])] }
|
|
221
205
|
: i
|
|
222
206
|
),
|
|
223
|
-
lastUpdated:
|
|
207
|
+
lastUpdated: getTimestamp()
|
|
224
208
|
}))
|
|
225
209
|
}
|
|
226
210
|
|
|
@@ -230,7 +214,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
230
214
|
async removeIdea(projectId: string, ideaId: string): Promise<void> {
|
|
231
215
|
await this.update(projectId, (data) => ({
|
|
232
216
|
ideas: data.ideas.filter(i => i.id !== ideaId),
|
|
233
|
-
lastUpdated:
|
|
217
|
+
lastUpdated: getTimestamp()
|
|
234
218
|
}))
|
|
235
219
|
}
|
|
236
220
|
|
|
@@ -266,7 +250,7 @@ class IdeasStorage extends StorageManager<IdeasJson> {
|
|
|
266
250
|
|
|
267
251
|
await this.update(projectId, (d) => ({
|
|
268
252
|
ideas: d.ideas.filter(i => !toRemove.has(i.id)),
|
|
269
|
-
lastUpdated:
|
|
253
|
+
lastUpdated: getTimestamp()
|
|
270
254
|
}))
|
|
271
255
|
|
|
272
256
|
return { removed }
|
package/core/storage/index.ts
CHANGED
|
@@ -38,167 +38,19 @@
|
|
|
38
38
|
export { StorageManager } from './storage-manager'
|
|
39
39
|
export { stateStorage } from './state-storage'
|
|
40
40
|
export { queueStorage } from './queue-storage'
|
|
41
|
-
export { ideasStorage
|
|
42
|
-
export { shippedStorage
|
|
41
|
+
export { ideasStorage } from './ideas-storage'
|
|
42
|
+
export { shippedStorage } from './shipped-storage'
|
|
43
43
|
|
|
44
44
|
// ========== GRANULAR STORAGE (Legacy) ==========
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
class FileStorage implements Storage {
|
|
60
|
-
private projectId: string
|
|
61
|
-
private basePath: string
|
|
62
|
-
|
|
63
|
-
constructor(projectId: string) {
|
|
64
|
-
this.projectId = projectId
|
|
65
|
-
this.basePath = path.join(os.homedir(), '.prjct-cli/projects', projectId, 'data')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Convert path array to file path
|
|
70
|
-
* ["task", "abc123"] → data/tasks/abc123.json
|
|
71
|
-
* ["project"] → data/project.json
|
|
72
|
-
*/
|
|
73
|
-
private pathToFile(pathArray: string[]): string {
|
|
74
|
-
if (pathArray.length === 1) {
|
|
75
|
-
return path.join(this.basePath, `${pathArray[0]}.json`)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Pluralize first segment for directory
|
|
79
|
-
const dir = pathArray[0] + 's'
|
|
80
|
-
const rest = pathArray.slice(1)
|
|
81
|
-
const filename = rest.join('/') + '.json'
|
|
82
|
-
|
|
83
|
-
return path.join(this.basePath, dir, filename)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async write<T>(pathArray: string[], data: T): Promise<void> {
|
|
87
|
-
const filePath = this.pathToFile(pathArray)
|
|
88
|
-
|
|
89
|
-
// Ensure directory exists
|
|
90
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
91
|
-
|
|
92
|
-
// Write data
|
|
93
|
-
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
|
|
94
|
-
|
|
95
|
-
// Publish event for sync
|
|
96
|
-
eventBus.publish({
|
|
97
|
-
type: inferEventType(pathArray, 'write'),
|
|
98
|
-
path: pathArray,
|
|
99
|
-
data,
|
|
100
|
-
timestamp: new Date().toISOString(),
|
|
101
|
-
projectId: this.projectId
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// Update index if it's a collection item
|
|
105
|
-
if (pathArray.length === 2) {
|
|
106
|
-
await this.updateIndex(pathArray[0], pathArray[1], 'add')
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async read<T>(pathArray: string[]): Promise<T | null> {
|
|
111
|
-
const filePath = this.pathToFile(pathArray)
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
115
|
-
return JSON.parse(content) as T
|
|
116
|
-
} catch {
|
|
117
|
-
return null
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async list(prefix: string[]): Promise<string[][]> {
|
|
122
|
-
const dir = path.join(this.basePath, prefix[0] + 's')
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const files = await fs.readdir(dir)
|
|
126
|
-
return files
|
|
127
|
-
.filter(f => f.endsWith('.json') && f !== 'index.json')
|
|
128
|
-
.map(f => [...prefix, f.replace('.json', '')])
|
|
129
|
-
} catch {
|
|
130
|
-
return []
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async delete(pathArray: string[]): Promise<void> {
|
|
135
|
-
const filePath = this.pathToFile(pathArray)
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
await fs.unlink(filePath)
|
|
139
|
-
|
|
140
|
-
// Publish event for sync
|
|
141
|
-
eventBus.publish({
|
|
142
|
-
type: inferEventType(pathArray, 'delete'),
|
|
143
|
-
path: pathArray,
|
|
144
|
-
data: null,
|
|
145
|
-
timestamp: new Date().toISOString(),
|
|
146
|
-
projectId: this.projectId
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// Update index if it's a collection item
|
|
150
|
-
if (pathArray.length === 2) {
|
|
151
|
-
await this.updateIndex(pathArray[0], pathArray[1], 'remove')
|
|
152
|
-
}
|
|
153
|
-
} catch {
|
|
154
|
-
// File doesn't exist, ignore
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async exists(pathArray: string[]): Promise<boolean> {
|
|
159
|
-
const filePath = this.pathToFile(pathArray)
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
await fs.access(filePath)
|
|
163
|
-
return true
|
|
164
|
-
} catch {
|
|
165
|
-
return false
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Update collection index
|
|
171
|
-
*/
|
|
172
|
-
private async updateIndex(collection: string, id: string, action: 'add' | 'remove'): Promise<void> {
|
|
173
|
-
const indexPath = path.join(this.basePath, collection + 's', 'index.json')
|
|
174
|
-
|
|
175
|
-
let index: { ids: string[]; updatedAt: string } = { ids: [], updatedAt: '' }
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const content = await fs.readFile(indexPath, 'utf-8')
|
|
179
|
-
index = JSON.parse(content)
|
|
180
|
-
} catch {
|
|
181
|
-
// Index doesn't exist yet
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (action === 'add' && !index.ids.includes(id)) {
|
|
185
|
-
index.ids.push(id)
|
|
186
|
-
} else if (action === 'remove') {
|
|
187
|
-
index.ids = index.ids.filter(i => i !== id)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
index.updatedAt = new Date().toISOString()
|
|
191
|
-
|
|
192
|
-
await fs.mkdir(path.dirname(indexPath), { recursive: true })
|
|
193
|
-
await fs.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8')
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get storage instance for a project
|
|
199
|
-
*/
|
|
200
|
-
export function getStorage(projectId: string): Storage {
|
|
201
|
-
return new FileStorage(projectId)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export default { getStorage }
|
|
45
|
+
export { getStorage, default } from './storage'
|
|
46
|
+
|
|
47
|
+
// Re-export types from canonical location
|
|
48
|
+
export type {
|
|
49
|
+
Storage,
|
|
50
|
+
Idea,
|
|
51
|
+
IdeasJson,
|
|
52
|
+
IdeaStatus,
|
|
53
|
+
IdeaPriority,
|
|
54
|
+
ShippedFeature,
|
|
55
|
+
ShippedJson,
|
|
56
|
+
} from '../types'
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { StorageManager } from './storage-manager'
|
|
9
9
|
import { generateUUID } from '../schemas'
|
|
10
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
10
11
|
import type { QueueJson, QueueTask, Priority, TaskType, TaskSection } from '../schemas/state'
|
|
11
12
|
|
|
12
13
|
class QueueStorage extends StorageManager<QueueJson> {
|
|
@@ -125,13 +126,13 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
125
126
|
const newTask: QueueTask = {
|
|
126
127
|
...task,
|
|
127
128
|
id: generateUUID(),
|
|
128
|
-
createdAt:
|
|
129
|
+
createdAt: getTimestamp(),
|
|
129
130
|
completed: false
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
await this.update(projectId, (queue) => ({
|
|
133
134
|
tasks: [...queue.tasks, newTask],
|
|
134
|
-
lastUpdated:
|
|
135
|
+
lastUpdated: getTimestamp()
|
|
135
136
|
}))
|
|
136
137
|
|
|
137
138
|
// Publish incremental event
|
|
@@ -152,7 +153,7 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
152
153
|
projectId: string,
|
|
153
154
|
tasks: Omit<QueueTask, 'id' | 'createdAt' | 'completed' | 'completedAt'>[]
|
|
154
155
|
): Promise<QueueTask[]> {
|
|
155
|
-
const now =
|
|
156
|
+
const now = getTimestamp()
|
|
156
157
|
const newTasks: QueueTask[] = tasks.map(task => ({
|
|
157
158
|
...task,
|
|
158
159
|
id: generateUUID(),
|
|
@@ -180,7 +181,7 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
180
181
|
async removeTask(projectId: string, taskId: string): Promise<void> {
|
|
181
182
|
await this.update(projectId, (queue) => ({
|
|
182
183
|
tasks: queue.tasks.filter(t => t.id !== taskId),
|
|
183
|
-
lastUpdated:
|
|
184
|
+
lastUpdated: getTimestamp()
|
|
184
185
|
}))
|
|
185
186
|
|
|
186
187
|
await this.publishEvent(projectId, 'queue.task_removed', { taskId })
|
|
@@ -198,20 +199,21 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
198
199
|
completedTask = {
|
|
199
200
|
...t,
|
|
200
201
|
completed: true,
|
|
201
|
-
completedAt:
|
|
202
|
+
completedAt: getTimestamp()
|
|
202
203
|
}
|
|
203
204
|
return completedTask
|
|
204
205
|
}
|
|
205
206
|
return t
|
|
206
207
|
})
|
|
207
|
-
return { tasks, lastUpdated:
|
|
208
|
+
return { tasks, lastUpdated: getTimestamp() }
|
|
208
209
|
})
|
|
209
210
|
|
|
210
211
|
if (completedTask) {
|
|
212
|
+
const task = completedTask as QueueTask
|
|
211
213
|
await this.publishEvent(projectId, 'queue.task_completed', {
|
|
212
214
|
taskId,
|
|
213
|
-
description:
|
|
214
|
-
completedAt:
|
|
215
|
+
description: task.description,
|
|
216
|
+
completedAt: task.completedAt
|
|
215
217
|
})
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -230,7 +232,7 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
230
232
|
tasks: queue.tasks.map(t =>
|
|
231
233
|
t.id === taskId ? { ...t, section } : t
|
|
232
234
|
),
|
|
233
|
-
lastUpdated:
|
|
235
|
+
lastUpdated: getTimestamp()
|
|
234
236
|
}))
|
|
235
237
|
}
|
|
236
238
|
|
|
@@ -246,7 +248,7 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
246
248
|
tasks: queue.tasks.map(t =>
|
|
247
249
|
t.id === taskId ? { ...t, priority } : t
|
|
248
250
|
),
|
|
249
|
-
lastUpdated:
|
|
251
|
+
lastUpdated: getTimestamp()
|
|
250
252
|
}))
|
|
251
253
|
}
|
|
252
254
|
|
|
@@ -259,7 +261,7 @@ class QueueStorage extends StorageManager<QueueJson> {
|
|
|
259
261
|
|
|
260
262
|
await this.update(projectId, (q) => ({
|
|
261
263
|
tasks: q.tasks.filter(t => !t.completed),
|
|
262
|
-
lastUpdated:
|
|
264
|
+
lastUpdated: getTimestamp()
|
|
263
265
|
}))
|
|
264
266
|
|
|
265
267
|
return completedCount
|
|
@@ -7,21 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { StorageManager } from './storage-manager'
|
|
9
9
|
import { generateUUID } from '../schemas'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
id: string
|
|
13
|
-
name: string
|
|
14
|
-
shippedAt: string
|
|
15
|
-
version: string
|
|
16
|
-
description?: string
|
|
17
|
-
tasks?: string[] // Task IDs that were part of this ship
|
|
18
|
-
duration?: string // How long it took
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ShippedJson {
|
|
22
|
-
shipped: ShippedFeature[]
|
|
23
|
-
lastUpdated: string
|
|
24
|
-
}
|
|
10
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
11
|
+
import type { ShippedFeature, ShippedJson } from '../types'
|
|
25
12
|
|
|
26
13
|
class ShippedStorage extends StorageManager<ShippedJson> {
|
|
27
14
|
constructor() {
|
|
@@ -139,12 +126,12 @@ class ShippedStorage extends StorageManager<ShippedJson> {
|
|
|
139
126
|
const shipped: ShippedFeature = {
|
|
140
127
|
...feature,
|
|
141
128
|
id: generateUUID(),
|
|
142
|
-
shippedAt:
|
|
129
|
+
shippedAt: getTimestamp()
|
|
143
130
|
}
|
|
144
131
|
|
|
145
132
|
await this.update(projectId, (data) => ({
|
|
146
133
|
shipped: [shipped, ...data.shipped], // Prepend
|
|
147
|
-
lastUpdated:
|
|
134
|
+
lastUpdated: getTimestamp()
|
|
148
135
|
}))
|
|
149
136
|
|
|
150
137
|
// Publish event
|