prjct-cli 1.22.0 → 1.24.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 +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SessionLogManager Class
|
|
3
|
-
* Manages temporal fragmentation of logs and progress data.
|
|
4
|
-
* Writes to sessions/YYYY-MM/DD/ structure with auto-rotation.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import path from 'node:path'
|
|
8
|
-
import pathManager from '../infrastructure/path-manager'
|
|
9
|
-
import type {
|
|
10
|
-
SessionEntry,
|
|
11
|
-
SessionLogMetadata,
|
|
12
|
-
SessionMigrationResult,
|
|
13
|
-
SessionStats,
|
|
14
|
-
} from '../types'
|
|
15
|
-
import { getErrorMessage } from '../types/fs'
|
|
16
|
-
import { TTLCache } from '../utils/cache'
|
|
17
|
-
import * as dateHelper from '../utils/date-helper'
|
|
18
|
-
import * as fileHelper from '../utils/file-helper'
|
|
19
|
-
import * as jsonlHelper from '../utils/jsonl-helper'
|
|
20
|
-
import { VERSION } from '../utils/version'
|
|
21
|
-
import { migrateLegacyJsonl, migrateLegacyMarkdown } from './log-migration'
|
|
22
|
-
|
|
23
|
-
export class SessionLogManager {
|
|
24
|
-
private currentSessionCache: TTLCache<string>
|
|
25
|
-
private sessionMetadataCache: TTLCache<SessionLogMetadata>
|
|
26
|
-
|
|
27
|
-
constructor() {
|
|
28
|
-
this.currentSessionCache = new TTLCache<string>({ maxSize: 50, ttl: 3_600_000 })
|
|
29
|
-
this.sessionMetadataCache = new TTLCache<SessionLogMetadata>({ maxSize: 50, ttl: 3_600_000 })
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get or create current session directory for a project
|
|
34
|
-
*/
|
|
35
|
-
async getCurrentSession(projectId: string): Promise<string> {
|
|
36
|
-
const cacheKey = `${projectId}-${this._getTodayKey()}`
|
|
37
|
-
|
|
38
|
-
const cached = this.currentSessionCache.get(cacheKey)
|
|
39
|
-
if (cached !== null) {
|
|
40
|
-
return cached
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const sessionPath = await pathManager.ensureSessionPath(projectId)
|
|
44
|
-
this.currentSessionCache.set(cacheKey, sessionPath)
|
|
45
|
-
|
|
46
|
-
await this._ensureSessionLogMetadata(sessionPath)
|
|
47
|
-
|
|
48
|
-
return sessionPath
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Write log entry to current session
|
|
53
|
-
*/
|
|
54
|
-
async writeToSession(
|
|
55
|
-
projectId: string,
|
|
56
|
-
entry: SessionEntry,
|
|
57
|
-
filename: string = 'context.jsonl'
|
|
58
|
-
): Promise<void> {
|
|
59
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
60
|
-
const filePath = path.join(sessionPath, filename)
|
|
61
|
-
|
|
62
|
-
// Use automatic rotation to prevent large files (>10MB)
|
|
63
|
-
await jsonlHelper.appendJsonLineWithRotation(filePath, entry, 10)
|
|
64
|
-
|
|
65
|
-
await this._updateSessionLogMetadata(sessionPath, {
|
|
66
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
67
|
-
entryCount: await jsonlHelper.countJsonLines(filePath),
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Append content to a session file (for markdown files like shipped.md)
|
|
73
|
-
*/
|
|
74
|
-
async appendToSession(projectId: string, content: string, filename: string): Promise<void> {
|
|
75
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
76
|
-
const filePath = path.join(sessionPath, filename)
|
|
77
|
-
|
|
78
|
-
const exists = await fileHelper.fileExists(filePath)
|
|
79
|
-
if (!exists && filename === 'shipped.md') {
|
|
80
|
-
await fileHelper.writeFile(filePath, `# SHIPPED 🚀\n\n${content}`)
|
|
81
|
-
} else {
|
|
82
|
-
await fileHelper.appendToFile(filePath, content)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await this._updateSessionLogMetadata(sessionPath, {
|
|
86
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Read logs from current session
|
|
92
|
-
* Uses streaming for large files (>50MB)
|
|
93
|
-
*/
|
|
94
|
-
async readCurrentSession<T = SessionEntry>(
|
|
95
|
-
projectId: string,
|
|
96
|
-
filename: string = 'context.jsonl',
|
|
97
|
-
maxLines: number = 1000
|
|
98
|
-
): Promise<T[]> {
|
|
99
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
100
|
-
const filePath = path.join(sessionPath, filename)
|
|
101
|
-
|
|
102
|
-
// Check file size and warn if large
|
|
103
|
-
const { isLarge } = await jsonlHelper.checkFileSizeWarning(filePath, 50)
|
|
104
|
-
|
|
105
|
-
if (isLarge) {
|
|
106
|
-
// Use streaming for large files
|
|
107
|
-
return (await jsonlHelper.readJsonLinesStreaming(filePath, maxLines)) as T[]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Use normal read for small files
|
|
111
|
-
return (await jsonlHelper.readJsonLines(filePath)) as T[]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Read logs from a specific date range
|
|
116
|
-
*/
|
|
117
|
-
async readSessionRange<T = SessionEntry>(
|
|
118
|
-
projectId: string,
|
|
119
|
-
fromDate: Date,
|
|
120
|
-
toDate: Date = new Date(),
|
|
121
|
-
filename: string = 'context.jsonl'
|
|
122
|
-
): Promise<T[]> {
|
|
123
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
124
|
-
const allEntries: T[] = []
|
|
125
|
-
|
|
126
|
-
for (const session of sessions) {
|
|
127
|
-
const filePath = path.join(session.path, filename)
|
|
128
|
-
const entries = (await jsonlHelper.readJsonLines(filePath)) as (T & { _sessionDate?: Date })[]
|
|
129
|
-
|
|
130
|
-
entries.forEach((entry) => {
|
|
131
|
-
entry._sessionDate = session.date
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
allEntries.push(...entries)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return allEntries
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Read markdown content from sessions in date range
|
|
142
|
-
*/
|
|
143
|
-
async readMarkdownRange(
|
|
144
|
-
projectId: string,
|
|
145
|
-
fromDate: Date,
|
|
146
|
-
toDate: Date,
|
|
147
|
-
filename: string
|
|
148
|
-
): Promise<string> {
|
|
149
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
150
|
-
const allContent: string[] = []
|
|
151
|
-
|
|
152
|
-
for (const session of sessions) {
|
|
153
|
-
const filePath = path.join(session.path, filename)
|
|
154
|
-
const content = await fileHelper.readFile(filePath, '')
|
|
155
|
-
|
|
156
|
-
if (content.trim()) {
|
|
157
|
-
allContent.push(`## Session: ${session.year}-${session.month}-${session.day}\n\n${content}`)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return allContent.join('\n---\n\n')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get recent logs (last N days)
|
|
166
|
-
*/
|
|
167
|
-
async getRecentLogs<T = SessionEntry>(
|
|
168
|
-
projectId: string,
|
|
169
|
-
days: number = 7,
|
|
170
|
-
filename: string = 'context.jsonl'
|
|
171
|
-
): Promise<T[]> {
|
|
172
|
-
const toDate = new Date()
|
|
173
|
-
const fromDate = dateHelper.getDaysAgo(days)
|
|
174
|
-
|
|
175
|
-
return await this.readSessionRange<T>(projectId, fromDate, toDate, filename)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Get session statistics
|
|
180
|
-
*/
|
|
181
|
-
async getSessionStats(projectId: string, fromDate: Date, toDate: Date): Promise<SessionStats> {
|
|
182
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
183
|
-
|
|
184
|
-
let totalEntries = 0
|
|
185
|
-
let totalShips = 0
|
|
186
|
-
let activeDays = 0
|
|
187
|
-
|
|
188
|
-
for (const session of sessions) {
|
|
189
|
-
const metadata = await this._getSessionLogMetadata(session.path)
|
|
190
|
-
if (metadata) {
|
|
191
|
-
totalEntries += metadata.entryCount || 0
|
|
192
|
-
totalShips += metadata.shipCount || 0
|
|
193
|
-
if (metadata.entryCount && metadata.entryCount > 0) {
|
|
194
|
-
activeDays++
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
totalSessions: sessions.length,
|
|
201
|
-
activeDays,
|
|
202
|
-
totalEntries,
|
|
203
|
-
totalShips,
|
|
204
|
-
averageEntriesPerDay: activeDays > 0 ? Math.round(totalEntries / activeDays) : 0,
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Migrate legacy single-file logs to session structure
|
|
210
|
-
*/
|
|
211
|
-
async migrateLegacyLogs(
|
|
212
|
-
projectId: string,
|
|
213
|
-
legacyFilePath: string,
|
|
214
|
-
sessionFilename: string
|
|
215
|
-
): Promise<SessionMigrationResult> {
|
|
216
|
-
try {
|
|
217
|
-
const content = await fileHelper.readFile(legacyFilePath)
|
|
218
|
-
|
|
219
|
-
if (sessionFilename.endsWith('.jsonl')) {
|
|
220
|
-
return await migrateLegacyJsonl(
|
|
221
|
-
projectId,
|
|
222
|
-
content,
|
|
223
|
-
sessionFilename,
|
|
224
|
-
(sp, u) => this._updateSessionLogMetadata(sp, u),
|
|
225
|
-
(sp) => this._ensureSessionLogMetadata(sp)
|
|
226
|
-
)
|
|
227
|
-
} else {
|
|
228
|
-
const sessionPath = await this.getCurrentSession(projectId)
|
|
229
|
-
return await migrateLegacyMarkdown(sessionPath, content, sessionFilename, (sp, u) =>
|
|
230
|
-
this._updateSessionLogMetadata(sp, u)
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
} catch (error) {
|
|
234
|
-
return {
|
|
235
|
-
success: false,
|
|
236
|
-
message: `Migration failed: ${getErrorMessage(error)}`,
|
|
237
|
-
entriesMigrated: 0,
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Get session metadata
|
|
244
|
-
*/
|
|
245
|
-
private async _getSessionLogMetadata(sessionPath: string): Promise<SessionLogMetadata | null> {
|
|
246
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
247
|
-
|
|
248
|
-
const cached = this.sessionMetadataCache.get(sessionPath)
|
|
249
|
-
if (cached !== null) {
|
|
250
|
-
return cached
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const metadata = await fileHelper.readJson<SessionLogMetadata>(metadataPath, null)
|
|
254
|
-
if (metadata) {
|
|
255
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
256
|
-
}
|
|
257
|
-
return metadata
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Ensure session metadata exists
|
|
262
|
-
*/
|
|
263
|
-
private async _ensureSessionLogMetadata(sessionPath: string): Promise<void> {
|
|
264
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
265
|
-
|
|
266
|
-
const exists = await fileHelper.fileExists(metadataPath)
|
|
267
|
-
if (!exists) {
|
|
268
|
-
const metadata: SessionLogMetadata = {
|
|
269
|
-
created: dateHelper.getTimestamp(),
|
|
270
|
-
lastActivity: dateHelper.getTimestamp(),
|
|
271
|
-
entryCount: 0,
|
|
272
|
-
shipCount: 0,
|
|
273
|
-
version: VERSION,
|
|
274
|
-
}
|
|
275
|
-
await fileHelper.writeJson(metadataPath, metadata)
|
|
276
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Update session metadata
|
|
282
|
-
*/
|
|
283
|
-
private async _updateSessionLogMetadata(
|
|
284
|
-
sessionPath: string,
|
|
285
|
-
updates: Partial<SessionLogMetadata>
|
|
286
|
-
): Promise<void> {
|
|
287
|
-
const metadata = (await this._getSessionLogMetadata(sessionPath)) || {}
|
|
288
|
-
Object.assign(metadata, updates)
|
|
289
|
-
|
|
290
|
-
const metadataPath = path.join(sessionPath, 'session-meta.json')
|
|
291
|
-
await fileHelper.writeJson(metadataPath, metadata)
|
|
292
|
-
|
|
293
|
-
this.sessionMetadataCache.set(sessionPath, metadata)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get today's date key (YYYY-MM-DD)
|
|
298
|
-
*/
|
|
299
|
-
private _getTodayKey(): string {
|
|
300
|
-
return dateHelper.getTodayKey()
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
clearCache(): void {
|
|
304
|
-
this.currentSessionCache.clear()
|
|
305
|
-
this.sessionMetadataCache.clear()
|
|
306
|
-
}
|
|
307
|
-
}
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskSessionManager Class
|
|
3
|
-
* Manages task lifecycle: create, pause, resume, complete
|
|
4
|
-
* Tracks metrics, timeline, and archives completed sessions.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { exec } from 'node:child_process'
|
|
8
|
-
import fs from 'node:fs/promises'
|
|
9
|
-
import path from 'node:path'
|
|
10
|
-
import { promisify } from 'node:util'
|
|
11
|
-
import { emit } from '../bus'
|
|
12
|
-
import configManager from '../infrastructure/config-manager'
|
|
13
|
-
import pathManager from '../infrastructure/path-manager'
|
|
14
|
-
import type { Session, SessionMetrics } from '../types'
|
|
15
|
-
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
16
|
-
import { calculateDuration, formatDuration, generateId } from './utils'
|
|
17
|
-
|
|
18
|
-
const execAsync = promisify(exec)
|
|
19
|
-
|
|
20
|
-
export class TaskSessionManager {
|
|
21
|
-
private projectPath: string
|
|
22
|
-
private projectId: string | null
|
|
23
|
-
private sessionDir: string | null
|
|
24
|
-
private initialized: boolean
|
|
25
|
-
|
|
26
|
-
constructor(projectPath: string) {
|
|
27
|
-
this.projectPath = projectPath
|
|
28
|
-
this.projectId = null
|
|
29
|
-
this.sessionDir = null
|
|
30
|
-
this.initialized = false
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Initialize session manager for project
|
|
35
|
-
*/
|
|
36
|
-
async initialize(): Promise<void> {
|
|
37
|
-
this.projectId = await configManager.getProjectId(this.projectPath)
|
|
38
|
-
if (!this.projectId) {
|
|
39
|
-
throw new Error('No prjct project found. Run /p:init first.')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const globalPath = pathManager.getGlobalProjectPath(this.projectId)
|
|
43
|
-
this.sessionDir = path.join(globalPath, 'sessions')
|
|
44
|
-
|
|
45
|
-
await fs.mkdir(this.sessionDir, { recursive: true })
|
|
46
|
-
this.initialized = true
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Generate unique session ID
|
|
51
|
-
*/
|
|
52
|
-
generateId(): string {
|
|
53
|
-
return generateId()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get current active session
|
|
58
|
-
*/
|
|
59
|
-
async getCurrent(): Promise<Session | null> {
|
|
60
|
-
if (!this.initialized) await this.initialize()
|
|
61
|
-
|
|
62
|
-
const currentPath = path.join(this.sessionDir!, 'current.json')
|
|
63
|
-
try {
|
|
64
|
-
const content = await fs.readFile(currentPath, 'utf-8')
|
|
65
|
-
return JSON.parse(content)
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (isNotFoundError(error) || error instanceof SyntaxError) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
throw error
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create a new session
|
|
76
|
-
*/
|
|
77
|
-
async create(task: string): Promise<Session> {
|
|
78
|
-
if (!this.initialized) await this.initialize()
|
|
79
|
-
|
|
80
|
-
// Check if there's already an active session
|
|
81
|
-
const current = await this.getCurrent()
|
|
82
|
-
if (current && current.status === 'active') {
|
|
83
|
-
throw new Error(`Session already active: "${current.task}". Use /p:done or /p:pause first.`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const now = new Date().toISOString()
|
|
87
|
-
const session: Session = {
|
|
88
|
-
id: this.generateId(),
|
|
89
|
-
projectId: this.projectId!,
|
|
90
|
-
task,
|
|
91
|
-
status: 'active',
|
|
92
|
-
startedAt: now,
|
|
93
|
-
pausedAt: null,
|
|
94
|
-
completedAt: null,
|
|
95
|
-
duration: 0,
|
|
96
|
-
metrics: {
|
|
97
|
-
filesCreated: 0,
|
|
98
|
-
filesChanged: 0,
|
|
99
|
-
filesModified: 0,
|
|
100
|
-
linesAdded: 0,
|
|
101
|
-
linesRemoved: 0,
|
|
102
|
-
commits: 0,
|
|
103
|
-
snapshots: [],
|
|
104
|
-
},
|
|
105
|
-
timeline: [{ type: 'start', at: now }],
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Save as current session
|
|
109
|
-
await this.saveCurrent(session)
|
|
110
|
-
|
|
111
|
-
// Log to session history
|
|
112
|
-
await this.logEvent('session_started', { sessionId: session.id, task })
|
|
113
|
-
|
|
114
|
-
// Emit event for plugins
|
|
115
|
-
await emit.sessionStarted({
|
|
116
|
-
sessionId: session.id,
|
|
117
|
-
task,
|
|
118
|
-
projectId: this.projectId,
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
return session
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Resume a paused session or continue active session
|
|
126
|
-
*/
|
|
127
|
-
async resume(task: string | null = null): Promise<Session> {
|
|
128
|
-
if (!this.initialized) await this.initialize()
|
|
129
|
-
|
|
130
|
-
const current = await this.getCurrent()
|
|
131
|
-
|
|
132
|
-
// If task provided and different from current, create new session
|
|
133
|
-
if (task && (!current || current.task !== task)) {
|
|
134
|
-
return this.create(task)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// If no current session, need a task
|
|
138
|
-
if (!current) {
|
|
139
|
-
if (!task) {
|
|
140
|
-
throw new Error('No active session. Provide a task to start one.')
|
|
141
|
-
}
|
|
142
|
-
return this.create(task)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// If already active, just return it
|
|
146
|
-
if (current.status === 'active') {
|
|
147
|
-
return current
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Resume paused session
|
|
151
|
-
const now = new Date().toISOString()
|
|
152
|
-
current.status = 'active'
|
|
153
|
-
current.timeline.push({ type: 'resume', at: now })
|
|
154
|
-
|
|
155
|
-
await this.saveCurrent(current)
|
|
156
|
-
await this.logEvent('session_resumed', { sessionId: current.id })
|
|
157
|
-
|
|
158
|
-
// Emit event for plugins
|
|
159
|
-
await emit.sessionResumed({
|
|
160
|
-
sessionId: current.id,
|
|
161
|
-
task: current.task,
|
|
162
|
-
projectId: this.projectId,
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
return current
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Pause current session
|
|
170
|
-
*/
|
|
171
|
-
async pause(): Promise<Session> {
|
|
172
|
-
if (!this.initialized) await this.initialize()
|
|
173
|
-
|
|
174
|
-
const current = await this.getCurrent()
|
|
175
|
-
if (!current) {
|
|
176
|
-
throw new Error('No active session to pause.')
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (current.status === 'paused') {
|
|
180
|
-
return current // Already paused
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const now = new Date().toISOString()
|
|
184
|
-
current.status = 'paused'
|
|
185
|
-
current.pausedAt = now
|
|
186
|
-
current.duration = calculateDuration(current)
|
|
187
|
-
current.timeline.push({ type: 'pause', at: now })
|
|
188
|
-
|
|
189
|
-
await this.saveCurrent(current)
|
|
190
|
-
await this.logEvent('session_paused', { sessionId: current.id, duration: current.duration })
|
|
191
|
-
|
|
192
|
-
// Emit event for plugins
|
|
193
|
-
await emit.sessionPaused({
|
|
194
|
-
sessionId: current.id,
|
|
195
|
-
task: current.task,
|
|
196
|
-
duration: current.duration,
|
|
197
|
-
projectId: this.projectId,
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
return current
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Complete current session
|
|
205
|
-
*/
|
|
206
|
-
async complete(): Promise<Session> {
|
|
207
|
-
if (!this.initialized) await this.initialize()
|
|
208
|
-
|
|
209
|
-
const current = await this.getCurrent()
|
|
210
|
-
if (!current) {
|
|
211
|
-
throw new Error('No active session to complete.')
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const now = new Date().toISOString()
|
|
215
|
-
current.status = 'completed'
|
|
216
|
-
current.completedAt = now
|
|
217
|
-
current.duration = calculateDuration(current)
|
|
218
|
-
current.metrics = await this.calculateMetrics(current)
|
|
219
|
-
current.timeline.push({ type: 'complete', at: now })
|
|
220
|
-
|
|
221
|
-
// Archive session
|
|
222
|
-
await this.archive(current)
|
|
223
|
-
|
|
224
|
-
// Clear current
|
|
225
|
-
await this.clearCurrent()
|
|
226
|
-
|
|
227
|
-
// Log completion
|
|
228
|
-
await this.logEvent('session_completed', {
|
|
229
|
-
sessionId: current.id,
|
|
230
|
-
task: current.task,
|
|
231
|
-
duration: current.duration,
|
|
232
|
-
metrics: current.metrics,
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
// Emit event for plugins
|
|
236
|
-
await emit.sessionCompleted({
|
|
237
|
-
sessionId: current.id,
|
|
238
|
-
task: current.task,
|
|
239
|
-
duration: current.duration,
|
|
240
|
-
metrics: current.metrics,
|
|
241
|
-
projectId: this.projectId,
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
return current
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Calculate total duration in seconds
|
|
249
|
-
*/
|
|
250
|
-
calculateDuration(session: Session): number {
|
|
251
|
-
return calculateDuration(session)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Calculate metrics for session
|
|
256
|
-
*/
|
|
257
|
-
async calculateMetrics(session: Session): Promise<SessionMetrics> {
|
|
258
|
-
const metrics = { ...session.metrics }
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
// Get git stats since session start
|
|
262
|
-
const since = session.startedAt.split('T')[0]
|
|
263
|
-
|
|
264
|
-
// Count commits
|
|
265
|
-
const { stdout: commitCount } = await execAsync(
|
|
266
|
-
`git rev-list --count --since="${since}" HEAD 2>/dev/null || echo "0"`,
|
|
267
|
-
{ cwd: this.projectPath }
|
|
268
|
-
)
|
|
269
|
-
metrics.commits = parseInt(commitCount.trim(), 10) || 0
|
|
270
|
-
|
|
271
|
-
// Get diff stats
|
|
272
|
-
const { stdout: diffStat } = await execAsync(
|
|
273
|
-
`git diff --stat HEAD~${Math.max(metrics.commits, 1)} 2>/dev/null || echo ""`,
|
|
274
|
-
{ cwd: this.projectPath }
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
// Parse diff stats
|
|
278
|
-
const lines = diffStat.split('\n')
|
|
279
|
-
const summaryLine = lines[lines.length - 2] || ''
|
|
280
|
-
const match = summaryLine.match(
|
|
281
|
-
/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
if (match) {
|
|
285
|
-
metrics.filesChanged = parseInt(match[1], 10) || 0
|
|
286
|
-
metrics.linesAdded = parseInt(match[2], 10) || 0
|
|
287
|
-
metrics.linesRemoved = parseInt(match[3], 10) || 0
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
// Keep existing metrics if git fails (not a repo, git not installed, etc.)
|
|
291
|
-
// This is expected in non-git projects
|
|
292
|
-
if (!isNotFoundError(error)) {
|
|
293
|
-
// Log unexpected errors but don't fail
|
|
294
|
-
console.error(`Metrics calculation warning: ${getErrorMessage(error)}`)
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return metrics
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Save current session
|
|
303
|
-
*/
|
|
304
|
-
async saveCurrent(session: Session): Promise<void> {
|
|
305
|
-
const currentPath = path.join(this.sessionDir!, 'current.json')
|
|
306
|
-
await fs.writeFile(currentPath, JSON.stringify(session, null, 2))
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Clear current session file
|
|
311
|
-
*/
|
|
312
|
-
async clearCurrent(): Promise<void> {
|
|
313
|
-
const currentPath = path.join(this.sessionDir!, 'current.json')
|
|
314
|
-
try {
|
|
315
|
-
await fs.unlink(currentPath)
|
|
316
|
-
} catch (error) {
|
|
317
|
-
// File might not exist - that's ok
|
|
318
|
-
if (!isNotFoundError(error)) {
|
|
319
|
-
throw error
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Archive completed session
|
|
326
|
-
*/
|
|
327
|
-
async archive(session: Session): Promise<void> {
|
|
328
|
-
const date = new Date(session.completedAt!)
|
|
329
|
-
const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
|
|
330
|
-
const archiveDir = path.join(this.sessionDir!, 'archive', yearMonth)
|
|
331
|
-
|
|
332
|
-
await fs.mkdir(archiveDir, { recursive: true })
|
|
333
|
-
|
|
334
|
-
const archivePath = path.join(archiveDir, `${session.id}.json`)
|
|
335
|
-
await fs.writeFile(archivePath, JSON.stringify(session, null, 2))
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Get session history
|
|
340
|
-
*/
|
|
341
|
-
async getHistory(limit: number = 10): Promise<Session[]> {
|
|
342
|
-
if (!this.initialized) await this.initialize()
|
|
343
|
-
|
|
344
|
-
const sessions: Session[] = []
|
|
345
|
-
const archiveDir = path.join(this.sessionDir!, 'archive')
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
const months = await fs.readdir(archiveDir)
|
|
349
|
-
const sortedMonths = months.sort().reverse()
|
|
350
|
-
|
|
351
|
-
for (const month of sortedMonths) {
|
|
352
|
-
if (sessions.length >= limit) break
|
|
353
|
-
|
|
354
|
-
const monthDir = path.join(archiveDir, month)
|
|
355
|
-
const files = await fs.readdir(monthDir)
|
|
356
|
-
|
|
357
|
-
for (const file of files.sort().reverse()) {
|
|
358
|
-
if (sessions.length >= limit) break
|
|
359
|
-
if (!file.endsWith('.json')) continue
|
|
360
|
-
|
|
361
|
-
const content = await fs.readFile(path.join(monthDir, file), 'utf-8')
|
|
362
|
-
sessions.push(JSON.parse(content))
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
} catch (error) {
|
|
366
|
-
// Archive might not exist yet
|
|
367
|
-
if (!isNotFoundError(error) && !(error instanceof SyntaxError)) {
|
|
368
|
-
throw error
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return sessions
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Log event to memory
|
|
377
|
-
*/
|
|
378
|
-
async logEvent(action: string, data: Record<string, unknown>): Promise<void> {
|
|
379
|
-
const globalPath = pathManager.getGlobalProjectPath(this.projectId!)
|
|
380
|
-
const memoryPath = path.join(globalPath, 'memory', 'context.jsonl')
|
|
381
|
-
|
|
382
|
-
const entry = `${JSON.stringify({
|
|
383
|
-
timestamp: new Date().toISOString(),
|
|
384
|
-
action,
|
|
385
|
-
...data,
|
|
386
|
-
})}\n`
|
|
387
|
-
|
|
388
|
-
try {
|
|
389
|
-
await fs.appendFile(memoryPath, entry)
|
|
390
|
-
} catch (error) {
|
|
391
|
-
// Memory file might not exist - that's ok
|
|
392
|
-
if (!isNotFoundError(error)) {
|
|
393
|
-
throw error
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Format duration as human readable
|
|
400
|
-
*/
|
|
401
|
-
static formatDuration(seconds: number): string {
|
|
402
|
-
return formatDuration(seconds)
|
|
403
|
-
}
|
|
404
|
-
}
|