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,1016 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite Migration & Integration Tests (PRJ-303)
|
|
3
|
-
*
|
|
4
|
-
* Tests for:
|
|
5
|
-
* - Migration correctness (JSON → SQLite)
|
|
6
|
-
* - Concurrent access (WAL mode)
|
|
7
|
-
* - Query performance (SQLite vs JSON)
|
|
8
|
-
* - Graceful degradation
|
|
9
|
-
* - StorageManager + IndexStorage SQLite integration
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
13
|
-
import fs from 'node:fs/promises'
|
|
14
|
-
import os from 'node:os'
|
|
15
|
-
import path from 'node:path'
|
|
16
|
-
import pathManager from '../../infrastructure/path-manager'
|
|
17
|
-
import { prjctDb } from '../../storage/database'
|
|
18
|
-
import { indexStorage } from '../../storage/index-storage'
|
|
19
|
-
import { migrateJsonToSqlite } from '../../storage/migrate-json'
|
|
20
|
-
import { StorageManager } from '../../storage/storage-manager'
|
|
21
|
-
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Test Setup
|
|
24
|
-
// =============================================================================
|
|
25
|
-
|
|
26
|
-
let tmpRoot: string | null = null
|
|
27
|
-
let testProjectId: string
|
|
28
|
-
|
|
29
|
-
const originalGetGlobalProjectPath = pathManager.getGlobalProjectPath.bind(pathManager)
|
|
30
|
-
const originalGetStoragePath = pathManager.getStoragePath.bind(pathManager)
|
|
31
|
-
const originalGetFilePath = pathManager.getFilePath.bind(pathManager)
|
|
32
|
-
|
|
33
|
-
// Concrete StorageManager for testing
|
|
34
|
-
interface TestData {
|
|
35
|
-
value: string
|
|
36
|
-
count: number
|
|
37
|
-
items: string[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class TestStorageManager extends StorageManager<TestData> {
|
|
41
|
-
constructor() {
|
|
42
|
-
super('test-data.json')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
protected getLayer(): string {
|
|
46
|
-
return 'context'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
protected getDefault(): TestData {
|
|
50
|
-
return { value: '', count: 0, items: [] }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
protected toMarkdown(data: TestData): string {
|
|
54
|
-
return `# Test\nValue: ${data.value}`
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
protected getMdFilename(): string {
|
|
58
|
-
return 'test-data.md'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
protected getEventType(action: 'update' | 'create' | 'delete'): string {
|
|
62
|
-
return `test.${action}`
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function mockPaths() {
|
|
67
|
-
pathManager.getGlobalProjectPath = (projectId: string) => {
|
|
68
|
-
return path.join(tmpRoot!, projectId)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
pathManager.getStoragePath = (projectId: string, filename: string) => {
|
|
72
|
-
return path.join(tmpRoot!, projectId, 'storage', filename)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
pathManager.getFilePath = (projectId: string, layer: string, filename: string) => {
|
|
76
|
-
return path.join(tmpRoot!, projectId, layer, filename)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function restorePaths() {
|
|
81
|
-
pathManager.getGlobalProjectPath = originalGetGlobalProjectPath
|
|
82
|
-
pathManager.getStoragePath = originalGetStoragePath
|
|
83
|
-
pathManager.getFilePath = originalGetFilePath
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// =============================================================================
|
|
87
|
-
// Migration Correctness Tests
|
|
88
|
-
// =============================================================================
|
|
89
|
-
|
|
90
|
-
describe('SQLite Migration', () => {
|
|
91
|
-
beforeEach(async () => {
|
|
92
|
-
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-sqlite-test-'))
|
|
93
|
-
testProjectId = 'test-project-migration'
|
|
94
|
-
mockPaths()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
afterEach(async () => {
|
|
98
|
-
prjctDb.close()
|
|
99
|
-
restorePaths()
|
|
100
|
-
if (tmpRoot) {
|
|
101
|
-
await fs.rm(tmpRoot, { recursive: true, force: true })
|
|
102
|
-
tmpRoot = null
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
describe('migration correctness', () => {
|
|
107
|
-
it('should migrate state.json to kv_store', async () => {
|
|
108
|
-
// Create source JSON
|
|
109
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
110
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
111
|
-
|
|
112
|
-
const state = {
|
|
113
|
-
currentTask: {
|
|
114
|
-
id: 'task-1',
|
|
115
|
-
description: 'Test task',
|
|
116
|
-
type: 'feature',
|
|
117
|
-
status: 'active',
|
|
118
|
-
startedAt: '2026-01-01T00:00:00.000Z',
|
|
119
|
-
subtasks: [
|
|
120
|
-
{ id: 'st-1', description: 'Sub 1', status: 'completed' },
|
|
121
|
-
{ id: 'st-2', description: 'Sub 2', status: 'active' },
|
|
122
|
-
],
|
|
123
|
-
currentSubtaskIndex: 1,
|
|
124
|
-
branch: 'feature/test',
|
|
125
|
-
linearId: 'PRJ-100',
|
|
126
|
-
},
|
|
127
|
-
previousTask: null,
|
|
128
|
-
pausedTasks: [],
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
await fs.writeFile(
|
|
132
|
-
path.join(storagePath, 'state.json'),
|
|
133
|
-
JSON.stringify(state, null, 2),
|
|
134
|
-
'utf-8'
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
const result = await migrateJsonToSqlite(testProjectId)
|
|
138
|
-
|
|
139
|
-
expect(result.success).toBe(true)
|
|
140
|
-
expect(result.migratedFiles).toContain('state.json')
|
|
141
|
-
|
|
142
|
-
// Verify kv_store has the data
|
|
143
|
-
const doc = prjctDb.getDoc(testProjectId, 'state')
|
|
144
|
-
expect(doc).not.toBeNull()
|
|
145
|
-
expect((doc as typeof state).currentTask.id).toBe('task-1')
|
|
146
|
-
expect((doc as typeof state).currentTask.description).toBe('Test task')
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('should migrate state.json to normalized tasks table', async () => {
|
|
150
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
151
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
152
|
-
|
|
153
|
-
const state = {
|
|
154
|
-
currentTask: {
|
|
155
|
-
id: 'task-abc',
|
|
156
|
-
description: 'Normalized test',
|
|
157
|
-
type: 'bug',
|
|
158
|
-
status: 'active',
|
|
159
|
-
startedAt: '2026-01-01T00:00:00.000Z',
|
|
160
|
-
subtasks: [
|
|
161
|
-
{ id: 'st-1', description: 'First sub', status: 'completed', domain: 'backend' },
|
|
162
|
-
],
|
|
163
|
-
branch: 'fix/test',
|
|
164
|
-
linearId: 'PRJ-200',
|
|
165
|
-
},
|
|
166
|
-
previousTask: null,
|
|
167
|
-
pausedTasks: [],
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await fs.writeFile(
|
|
171
|
-
path.join(storagePath, 'state.json'),
|
|
172
|
-
JSON.stringify(state, null, 2),
|
|
173
|
-
'utf-8'
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
await migrateJsonToSqlite(testProjectId)
|
|
177
|
-
|
|
178
|
-
// Verify normalized tasks table
|
|
179
|
-
const task = prjctDb.get<{ id: string; description: string; type: string; status: string }>(
|
|
180
|
-
testProjectId,
|
|
181
|
-
'SELECT id, description, type, status FROM tasks WHERE id = ?',
|
|
182
|
-
'task-abc'
|
|
183
|
-
)
|
|
184
|
-
expect(task).not.toBeNull()
|
|
185
|
-
expect(task!.type).toBe('bug')
|
|
186
|
-
expect(task!.status).toBe('active')
|
|
187
|
-
|
|
188
|
-
// Verify subtasks table
|
|
189
|
-
const subtask = prjctDb.get<{ id: string; task_id: string; domain: string }>(
|
|
190
|
-
testProjectId,
|
|
191
|
-
'SELECT id, task_id, domain FROM subtasks WHERE id = ?',
|
|
192
|
-
'st-1'
|
|
193
|
-
)
|
|
194
|
-
expect(subtask).not.toBeNull()
|
|
195
|
-
expect(subtask!.task_id).toBe('task-abc')
|
|
196
|
-
expect(subtask!.domain).toBe('backend')
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('should migrate ideas.json to kv_store and ideas table', async () => {
|
|
200
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
201
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
202
|
-
|
|
203
|
-
const ideas = {
|
|
204
|
-
ideas: [
|
|
205
|
-
{
|
|
206
|
-
id: 'idea-1',
|
|
207
|
-
text: 'Add dark mode',
|
|
208
|
-
status: 'pending',
|
|
209
|
-
priority: 'high',
|
|
210
|
-
tags: ['ui', 'frontend'],
|
|
211
|
-
addedAt: '2026-01-15T00:00:00.000Z',
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
id: 'idea-2',
|
|
215
|
-
text: 'Improve caching',
|
|
216
|
-
status: 'converted',
|
|
217
|
-
priority: 'medium',
|
|
218
|
-
tags: ['performance'],
|
|
219
|
-
addedAt: '2026-01-16T00:00:00.000Z',
|
|
220
|
-
convertedTo: 'task-xyz',
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
await fs.writeFile(
|
|
226
|
-
path.join(storagePath, 'ideas.json'),
|
|
227
|
-
JSON.stringify(ideas, null, 2),
|
|
228
|
-
'utf-8'
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
await migrateJsonToSqlite(testProjectId)
|
|
232
|
-
|
|
233
|
-
// Verify kv_store
|
|
234
|
-
const doc = prjctDb.getDoc(testProjectId, 'ideas')
|
|
235
|
-
expect(doc).not.toBeNull()
|
|
236
|
-
|
|
237
|
-
// Verify normalized ideas table
|
|
238
|
-
const rows = prjctDb.query<{ id: string; text: string; priority: string }>(
|
|
239
|
-
testProjectId,
|
|
240
|
-
'SELECT id, text, priority FROM ideas ORDER BY id'
|
|
241
|
-
)
|
|
242
|
-
expect(rows).toHaveLength(2)
|
|
243
|
-
expect(rows[0].text).toBe('Add dark mode')
|
|
244
|
-
expect(rows[0].priority).toBe('high')
|
|
245
|
-
expect(rows[1].text).toBe('Improve caching')
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
it('should migrate index files to index_meta', async () => {
|
|
249
|
-
const indexPath = path.join(tmpRoot!, testProjectId, 'index')
|
|
250
|
-
await fs.mkdir(indexPath, { recursive: true })
|
|
251
|
-
|
|
252
|
-
const projectIndex = {
|
|
253
|
-
version: '1.0.0',
|
|
254
|
-
projectPath: '/test/project',
|
|
255
|
-
lastFullScan: '2026-01-01T00:00:00.000Z',
|
|
256
|
-
lastIncrementalUpdate: '',
|
|
257
|
-
languages: { TypeScript: { count: 10, totalLines: 500, totalSize: 25000 } },
|
|
258
|
-
configFiles: [],
|
|
259
|
-
directories: [],
|
|
260
|
-
relevantFiles: [],
|
|
261
|
-
patterns: [],
|
|
262
|
-
detectedStack: {
|
|
263
|
-
ecosystem: 'JavaScript',
|
|
264
|
-
frameworks: [],
|
|
265
|
-
hasTests: true,
|
|
266
|
-
hasDocker: false,
|
|
267
|
-
hasCi: false,
|
|
268
|
-
buildTool: 'bun',
|
|
269
|
-
},
|
|
270
|
-
totalFiles: 50,
|
|
271
|
-
totalSize: 100000,
|
|
272
|
-
totalLines: 5000,
|
|
273
|
-
scanDuration: 30,
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
await fs.writeFile(
|
|
277
|
-
path.join(indexPath, 'project-index.json'),
|
|
278
|
-
JSON.stringify(projectIndex, null, 2),
|
|
279
|
-
'utf-8'
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
// Need a state.json so migration doesn't short-circuit
|
|
283
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
284
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
285
|
-
await fs.writeFile(
|
|
286
|
-
path.join(storagePath, 'state.json'),
|
|
287
|
-
JSON.stringify({
|
|
288
|
-
currentTask: {
|
|
289
|
-
id: 'x',
|
|
290
|
-
description: 'x',
|
|
291
|
-
status: 'active',
|
|
292
|
-
startedAt: new Date().toISOString(),
|
|
293
|
-
},
|
|
294
|
-
previousTask: null,
|
|
295
|
-
pausedTasks: [],
|
|
296
|
-
}),
|
|
297
|
-
'utf-8'
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
await migrateJsonToSqlite(testProjectId)
|
|
301
|
-
|
|
302
|
-
// Verify index_meta
|
|
303
|
-
const row = prjctDb.get<{ data: string }>(
|
|
304
|
-
testProjectId,
|
|
305
|
-
'SELECT data FROM index_meta WHERE key = ?',
|
|
306
|
-
'project-index'
|
|
307
|
-
)
|
|
308
|
-
expect(row).not.toBeNull()
|
|
309
|
-
const parsed = JSON.parse(row!.data)
|
|
310
|
-
expect(parsed.totalFiles).toBe(50)
|
|
311
|
-
expect(parsed.languages.TypeScript.count).toBe(10)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
it('should migrate checksums to index_checksums table', async () => {
|
|
315
|
-
const indexPath = path.join(tmpRoot!, testProjectId, 'index')
|
|
316
|
-
await fs.mkdir(indexPath, { recursive: true })
|
|
317
|
-
|
|
318
|
-
const checksums = {
|
|
319
|
-
version: '1.0.0',
|
|
320
|
-
lastUpdated: '2026-01-01T00:00:00.000Z',
|
|
321
|
-
checksums: {
|
|
322
|
-
'src/index.ts': 'abc123',
|
|
323
|
-
'src/utils.ts': 'def456',
|
|
324
|
-
'package.json': 'ghi789',
|
|
325
|
-
},
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
await fs.writeFile(
|
|
329
|
-
path.join(indexPath, 'checksums.json'),
|
|
330
|
-
JSON.stringify(checksums, null, 2),
|
|
331
|
-
'utf-8'
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
// Need state.json
|
|
335
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
336
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
337
|
-
await fs.writeFile(
|
|
338
|
-
path.join(storagePath, 'state.json'),
|
|
339
|
-
JSON.stringify({
|
|
340
|
-
currentTask: {
|
|
341
|
-
id: 'x',
|
|
342
|
-
description: 'x',
|
|
343
|
-
status: 'active',
|
|
344
|
-
startedAt: new Date().toISOString(),
|
|
345
|
-
},
|
|
346
|
-
previousTask: null,
|
|
347
|
-
pausedTasks: [],
|
|
348
|
-
}),
|
|
349
|
-
'utf-8'
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
await migrateJsonToSqlite(testProjectId)
|
|
353
|
-
|
|
354
|
-
// Verify index_checksums table
|
|
355
|
-
const rows = prjctDb.query<{ path: string; checksum: string }>(
|
|
356
|
-
testProjectId,
|
|
357
|
-
'SELECT path, checksum FROM index_checksums ORDER BY path'
|
|
358
|
-
)
|
|
359
|
-
expect(rows).toHaveLength(3)
|
|
360
|
-
expect(rows[0].path).toBe('package.json')
|
|
361
|
-
expect(rows[0].checksum).toBe('ghi789')
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
it('should migrate events.jsonl to events table', async () => {
|
|
365
|
-
const memoryPath = path.join(tmpRoot!, testProjectId, 'memory')
|
|
366
|
-
await fs.mkdir(memoryPath, { recursive: true })
|
|
367
|
-
|
|
368
|
-
const events = [
|
|
369
|
-
'{"type":"task_started","taskId":"t1","timestamp":"2026-01-01T00:00:00.000Z"}',
|
|
370
|
-
'{"type":"subtask_completed","taskId":"t1","timestamp":"2026-01-01T01:00:00.000Z"}',
|
|
371
|
-
'{"type":"task_completed","taskId":"t1","timestamp":"2026-01-01T02:00:00.000Z"}',
|
|
372
|
-
]
|
|
373
|
-
await fs.writeFile(path.join(memoryPath, 'events.jsonl'), events.join('\n'), 'utf-8')
|
|
374
|
-
|
|
375
|
-
// Need state.json
|
|
376
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
377
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
378
|
-
await fs.writeFile(
|
|
379
|
-
path.join(storagePath, 'state.json'),
|
|
380
|
-
JSON.stringify({
|
|
381
|
-
currentTask: {
|
|
382
|
-
id: 'x',
|
|
383
|
-
description: 'x',
|
|
384
|
-
status: 'active',
|
|
385
|
-
startedAt: new Date().toISOString(),
|
|
386
|
-
},
|
|
387
|
-
previousTask: null,
|
|
388
|
-
pausedTasks: [],
|
|
389
|
-
}),
|
|
390
|
-
'utf-8'
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
await migrateJsonToSqlite(testProjectId)
|
|
394
|
-
|
|
395
|
-
const rows = prjctDb.query<{ type: string; task_id: string }>(
|
|
396
|
-
testProjectId,
|
|
397
|
-
'SELECT type, task_id FROM events ORDER BY id'
|
|
398
|
-
)
|
|
399
|
-
expect(rows).toHaveLength(3)
|
|
400
|
-
expect(rows[0].type).toBe('task_started')
|
|
401
|
-
expect(rows[2].type).toBe('task_completed')
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
it('should be idempotent (skip if already migrated)', async () => {
|
|
405
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
406
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
407
|
-
|
|
408
|
-
await fs.writeFile(
|
|
409
|
-
path.join(storagePath, 'state.json'),
|
|
410
|
-
JSON.stringify({
|
|
411
|
-
currentTask: {
|
|
412
|
-
id: 't1',
|
|
413
|
-
description: 'x',
|
|
414
|
-
status: 'active',
|
|
415
|
-
startedAt: new Date().toISOString(),
|
|
416
|
-
},
|
|
417
|
-
previousTask: null,
|
|
418
|
-
pausedTasks: [],
|
|
419
|
-
}),
|
|
420
|
-
'utf-8'
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
// First migration
|
|
424
|
-
const result1 = await migrateJsonToSqlite(testProjectId)
|
|
425
|
-
expect(result1.success).toBe(true)
|
|
426
|
-
expect(result1.migratedFiles.length).toBeGreaterThan(0)
|
|
427
|
-
|
|
428
|
-
// Second migration should short-circuit
|
|
429
|
-
const result2 = await migrateJsonToSqlite(testProjectId)
|
|
430
|
-
expect(result2.success).toBe(true)
|
|
431
|
-
expect(result2.migratedFiles).toHaveLength(0)
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
it('should create backup of original files', async () => {
|
|
435
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
436
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
437
|
-
|
|
438
|
-
await fs.writeFile(
|
|
439
|
-
path.join(storagePath, 'state.json'),
|
|
440
|
-
JSON.stringify({
|
|
441
|
-
currentTask: {
|
|
442
|
-
id: 'bk',
|
|
443
|
-
description: 'backup test',
|
|
444
|
-
status: 'active',
|
|
445
|
-
startedAt: new Date().toISOString(),
|
|
446
|
-
},
|
|
447
|
-
previousTask: null,
|
|
448
|
-
pausedTasks: [],
|
|
449
|
-
}),
|
|
450
|
-
'utf-8'
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
const result = await migrateJsonToSqlite(testProjectId)
|
|
454
|
-
|
|
455
|
-
expect(result.backupDir).not.toBeNull()
|
|
456
|
-
// Verify backup file exists
|
|
457
|
-
const backupFile = path.join(result.backupDir!, 'state.json')
|
|
458
|
-
const stat = await fs.stat(backupFile)
|
|
459
|
-
expect(stat.isFile()).toBe(true)
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
it('should delete JSON files after successful migration', async () => {
|
|
463
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
464
|
-
const indexPath = path.join(tmpRoot!, testProjectId, 'index')
|
|
465
|
-
const memoryPath = path.join(tmpRoot!, testProjectId, 'memory')
|
|
466
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
467
|
-
await fs.mkdir(indexPath, { recursive: true })
|
|
468
|
-
await fs.mkdir(memoryPath, { recursive: true })
|
|
469
|
-
|
|
470
|
-
// Create various JSON files
|
|
471
|
-
await fs.writeFile(
|
|
472
|
-
path.join(storagePath, 'state.json'),
|
|
473
|
-
JSON.stringify({
|
|
474
|
-
currentTask: {
|
|
475
|
-
id: 'del',
|
|
476
|
-
description: 'del',
|
|
477
|
-
status: 'active',
|
|
478
|
-
startedAt: new Date().toISOString(),
|
|
479
|
-
},
|
|
480
|
-
previousTask: null,
|
|
481
|
-
pausedTasks: [],
|
|
482
|
-
}),
|
|
483
|
-
'utf-8'
|
|
484
|
-
)
|
|
485
|
-
await fs.writeFile(
|
|
486
|
-
path.join(storagePath, 'queue.json'),
|
|
487
|
-
JSON.stringify({ tasks: [] }),
|
|
488
|
-
'utf-8'
|
|
489
|
-
)
|
|
490
|
-
await fs.writeFile(
|
|
491
|
-
path.join(indexPath, 'project-index.json'),
|
|
492
|
-
JSON.stringify({ version: '1.0.0', totalFiles: 1 }),
|
|
493
|
-
'utf-8'
|
|
494
|
-
)
|
|
495
|
-
await fs.writeFile(
|
|
496
|
-
path.join(memoryPath, 'events.jsonl'),
|
|
497
|
-
'{"type":"test","timestamp":"2026-01-01T00:00:00.000Z"}\n',
|
|
498
|
-
'utf-8'
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
const result = await migrateJsonToSqlite(testProjectId)
|
|
502
|
-
expect(result.success).toBe(true)
|
|
503
|
-
|
|
504
|
-
// Verify JSON files were deleted
|
|
505
|
-
await expect(fs.access(path.join(storagePath, 'state.json'))).rejects.toThrow()
|
|
506
|
-
await expect(fs.access(path.join(storagePath, 'queue.json'))).rejects.toThrow()
|
|
507
|
-
await expect(fs.access(path.join(indexPath, 'project-index.json'))).rejects.toThrow()
|
|
508
|
-
await expect(fs.access(path.join(memoryPath, 'events.jsonl'))).rejects.toThrow()
|
|
509
|
-
|
|
510
|
-
// Verify backup still exists
|
|
511
|
-
expect(result.backupDir).not.toBeNull()
|
|
512
|
-
const backupFile = path.join(result.backupDir!, 'state.json')
|
|
513
|
-
const stat = await fs.stat(backupFile)
|
|
514
|
-
expect(stat.isFile()).toBe(true)
|
|
515
|
-
|
|
516
|
-
// Verify data is accessible from SQLite
|
|
517
|
-
const doc = prjctDb.getDoc(testProjectId, 'state')
|
|
518
|
-
expect(doc).not.toBeNull()
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
it('should handle missing files gracefully', async () => {
|
|
522
|
-
// Create only storage dir, no files
|
|
523
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
524
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
525
|
-
|
|
526
|
-
const result = await migrateJsonToSqlite(testProjectId)
|
|
527
|
-
|
|
528
|
-
// Should succeed with skipped files
|
|
529
|
-
expect(result.errors).toHaveLength(0)
|
|
530
|
-
expect(result.skippedFiles.length).toBeGreaterThan(0)
|
|
531
|
-
})
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
// ===========================================================================
|
|
535
|
-
// Concurrent Access Tests (WAL Mode)
|
|
536
|
-
// ===========================================================================
|
|
537
|
-
|
|
538
|
-
describe('concurrent access', () => {
|
|
539
|
-
it('should handle multiple concurrent reads', async () => {
|
|
540
|
-
const manager = new TestStorageManager()
|
|
541
|
-
const data: TestData = { value: 'concurrent', count: 42, items: ['a', 'b'] }
|
|
542
|
-
await manager.write(testProjectId, data)
|
|
543
|
-
manager.clearCache()
|
|
544
|
-
|
|
545
|
-
// Fire off multiple concurrent reads
|
|
546
|
-
const reads = Array.from({ length: 10 }, () => manager.read(testProjectId))
|
|
547
|
-
const results = await Promise.all(reads)
|
|
548
|
-
|
|
549
|
-
for (const result of results) {
|
|
550
|
-
expect(result).toEqual(data)
|
|
551
|
-
}
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
it('should handle concurrent writes to different projects', async () => {
|
|
555
|
-
const manager = new TestStorageManager()
|
|
556
|
-
const projects = ['proj-a', 'proj-b', 'proj-c']
|
|
557
|
-
|
|
558
|
-
// Write to different projects concurrently
|
|
559
|
-
const writes = projects.map((id, i) =>
|
|
560
|
-
manager.write(id, { value: `project-${i}`, count: i, items: [] })
|
|
561
|
-
)
|
|
562
|
-
await Promise.all(writes)
|
|
563
|
-
|
|
564
|
-
// Verify each project has correct data
|
|
565
|
-
for (let i = 0; i < projects.length; i++) {
|
|
566
|
-
manager.clearCache(projects[i])
|
|
567
|
-
const result = await manager.read(projects[i])
|
|
568
|
-
expect(result.value).toBe(`project-${i}`)
|
|
569
|
-
expect(result.count).toBe(i)
|
|
570
|
-
}
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
it('should handle sequential updates consistently', async () => {
|
|
574
|
-
const manager = new TestStorageManager()
|
|
575
|
-
await manager.write(testProjectId, { value: 'start', count: 0, items: [] })
|
|
576
|
-
|
|
577
|
-
// Run sequential updates
|
|
578
|
-
for (let i = 1; i <= 10; i++) {
|
|
579
|
-
await manager.update(testProjectId, (current) => ({
|
|
580
|
-
...current,
|
|
581
|
-
count: current.count + 1,
|
|
582
|
-
items: [...current.items, `item-${i}`],
|
|
583
|
-
}))
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const result = await manager.read(testProjectId)
|
|
587
|
-
expect(result.count).toBe(10)
|
|
588
|
-
expect(result.items).toHaveLength(10)
|
|
589
|
-
})
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
// ===========================================================================
|
|
593
|
-
// Query Performance Tests
|
|
594
|
-
// ===========================================================================
|
|
595
|
-
|
|
596
|
-
describe('query performance', () => {
|
|
597
|
-
it('should perform SQLite reads efficiently', async () => {
|
|
598
|
-
const manager = new TestStorageManager()
|
|
599
|
-
const data: TestData = {
|
|
600
|
-
value: 'perf-test',
|
|
601
|
-
count: 100,
|
|
602
|
-
items: Array.from({ length: 50 }, (_, i) => `item-${i}`),
|
|
603
|
-
}
|
|
604
|
-
await manager.write(testProjectId, data)
|
|
605
|
-
|
|
606
|
-
// Benchmark SQLite read (direct)
|
|
607
|
-
const sqliteStart = performance.now()
|
|
608
|
-
for (let i = 0; i < 100; i++) {
|
|
609
|
-
prjctDb.getDoc(testProjectId, 'test-data')
|
|
610
|
-
}
|
|
611
|
-
const sqliteTime = performance.now() - sqliteStart
|
|
612
|
-
|
|
613
|
-
// Verify data is correct
|
|
614
|
-
const sqliteResult = prjctDb.getDoc<TestData>(testProjectId, 'test-data')
|
|
615
|
-
expect(sqliteResult).toEqual(data)
|
|
616
|
-
|
|
617
|
-
// Log for informational purposes
|
|
618
|
-
console.log(` SQLite: ${sqliteTime.toFixed(2)}ms (100 reads)`)
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
it('should handle indexed queries efficiently', async () => {
|
|
622
|
-
// Populate normalized tasks table
|
|
623
|
-
const storagePath = path.join(tmpRoot!, testProjectId, 'storage')
|
|
624
|
-
await fs.mkdir(storagePath, { recursive: true })
|
|
625
|
-
|
|
626
|
-
const state = {
|
|
627
|
-
currentTask: {
|
|
628
|
-
id: 'perf-task',
|
|
629
|
-
description: 'Performance test',
|
|
630
|
-
type: 'feature',
|
|
631
|
-
status: 'active',
|
|
632
|
-
startedAt: '2026-01-01T00:00:00.000Z',
|
|
633
|
-
subtasks: Array.from({ length: 20 }, (_, i) => ({
|
|
634
|
-
id: `st-${i}`,
|
|
635
|
-
description: `Subtask ${i}`,
|
|
636
|
-
status: i < 10 ? 'completed' : 'pending',
|
|
637
|
-
domain: i % 2 === 0 ? 'backend' : 'frontend',
|
|
638
|
-
})),
|
|
639
|
-
},
|
|
640
|
-
previousTask: null,
|
|
641
|
-
pausedTasks: [],
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
await fs.writeFile(
|
|
645
|
-
path.join(storagePath, 'state.json'),
|
|
646
|
-
JSON.stringify(state, null, 2),
|
|
647
|
-
'utf-8'
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
await migrateJsonToSqlite(testProjectId)
|
|
651
|
-
|
|
652
|
-
// Indexed query: find completed subtasks
|
|
653
|
-
const start = performance.now()
|
|
654
|
-
const completed = prjctDb.query<{ id: string }>(
|
|
655
|
-
testProjectId,
|
|
656
|
-
'SELECT id FROM subtasks WHERE status = ?',
|
|
657
|
-
'completed'
|
|
658
|
-
)
|
|
659
|
-
const queryTime = performance.now() - start
|
|
660
|
-
|
|
661
|
-
expect(completed).toHaveLength(10)
|
|
662
|
-
// Indexed query should be sub-millisecond
|
|
663
|
-
expect(queryTime).toBeLessThan(10)
|
|
664
|
-
})
|
|
665
|
-
})
|
|
666
|
-
|
|
667
|
-
// ===========================================================================
|
|
668
|
-
// StorageManager SQLite Integration
|
|
669
|
-
// ===========================================================================
|
|
670
|
-
|
|
671
|
-
describe('StorageManager SQLite integration', () => {
|
|
672
|
-
it('should write to SQLite only (no JSON file)', async () => {
|
|
673
|
-
const manager = new TestStorageManager()
|
|
674
|
-
const data: TestData = { value: 'sqlite-write', count: 7, items: ['x'] }
|
|
675
|
-
|
|
676
|
-
await manager.write(testProjectId, data)
|
|
677
|
-
|
|
678
|
-
// Verify SQLite has it
|
|
679
|
-
const sqliteData = prjctDb.getDoc<TestData>(testProjectId, 'test-data')
|
|
680
|
-
expect(sqliteData).toEqual(data)
|
|
681
|
-
|
|
682
|
-
// Verify JSON file does NOT exist
|
|
683
|
-
const jsonPath = pathManager.getStoragePath(testProjectId, 'test-data.json')
|
|
684
|
-
await expect(fs.access(jsonPath)).rejects.toThrow()
|
|
685
|
-
})
|
|
686
|
-
|
|
687
|
-
it('should read from SQLite', async () => {
|
|
688
|
-
const manager = new TestStorageManager()
|
|
689
|
-
const data: TestData = { value: 'sqlite-only', count: 3, items: [] }
|
|
690
|
-
|
|
691
|
-
await manager.write(testProjectId, data)
|
|
692
|
-
manager.clearCache()
|
|
693
|
-
|
|
694
|
-
const result = await manager.read(testProjectId)
|
|
695
|
-
expect(result).toEqual(data)
|
|
696
|
-
})
|
|
697
|
-
|
|
698
|
-
it('should return default when SQLite has no data', async () => {
|
|
699
|
-
const manager = new TestStorageManager()
|
|
700
|
-
const result = await manager.read('nonexistent-project')
|
|
701
|
-
|
|
702
|
-
expect(result).toEqual({ value: '', count: 0, items: [] })
|
|
703
|
-
})
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
// ===========================================================================
|
|
707
|
-
// IndexStorage SQLite Integration
|
|
708
|
-
// ===========================================================================
|
|
709
|
-
|
|
710
|
-
describe('IndexStorage SQLite integration', () => {
|
|
711
|
-
it('should write index to SQLite only (no JSON file)', async () => {
|
|
712
|
-
// Ensure project directory exists for DB creation
|
|
713
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
714
|
-
|
|
715
|
-
const projectIndex = {
|
|
716
|
-
version: '1.0.0',
|
|
717
|
-
projectPath: '/test',
|
|
718
|
-
lastFullScan: '2026-01-01T00:00:00.000Z',
|
|
719
|
-
lastIncrementalUpdate: '',
|
|
720
|
-
languages: {},
|
|
721
|
-
configFiles: [],
|
|
722
|
-
directories: [],
|
|
723
|
-
relevantFiles: [],
|
|
724
|
-
patterns: [],
|
|
725
|
-
detectedStack: {
|
|
726
|
-
ecosystem: 'JavaScript',
|
|
727
|
-
frameworks: [],
|
|
728
|
-
hasTests: false,
|
|
729
|
-
hasDocker: false,
|
|
730
|
-
hasCi: false,
|
|
731
|
-
buildTool: null,
|
|
732
|
-
},
|
|
733
|
-
totalFiles: 10,
|
|
734
|
-
totalSize: 1000,
|
|
735
|
-
totalLines: 100,
|
|
736
|
-
scanDuration: 5,
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
await indexStorage.writeIndex(testProjectId, projectIndex)
|
|
740
|
-
|
|
741
|
-
// Verify SQLite index_meta
|
|
742
|
-
const row = prjctDb.get<{ data: string }>(
|
|
743
|
-
testProjectId,
|
|
744
|
-
'SELECT data FROM index_meta WHERE key = ?',
|
|
745
|
-
'project-index'
|
|
746
|
-
)
|
|
747
|
-
expect(row).not.toBeNull()
|
|
748
|
-
const parsed = JSON.parse(row!.data)
|
|
749
|
-
expect(parsed.totalFiles).toBe(10)
|
|
750
|
-
|
|
751
|
-
// Verify JSON file does NOT exist
|
|
752
|
-
const jsonPath = path.join(indexStorage.getIndexPath(testProjectId), 'project-index.json')
|
|
753
|
-
await expect(fs.access(jsonPath)).rejects.toThrow()
|
|
754
|
-
})
|
|
755
|
-
|
|
756
|
-
it('should read index from SQLite', async () => {
|
|
757
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
758
|
-
|
|
759
|
-
const projectIndex = {
|
|
760
|
-
version: '1.0.0',
|
|
761
|
-
projectPath: '/test',
|
|
762
|
-
lastFullScan: '2026-01-01T00:00:00.000Z',
|
|
763
|
-
lastIncrementalUpdate: '',
|
|
764
|
-
languages: {},
|
|
765
|
-
configFiles: [],
|
|
766
|
-
directories: [],
|
|
767
|
-
relevantFiles: [],
|
|
768
|
-
patterns: [],
|
|
769
|
-
detectedStack: {
|
|
770
|
-
ecosystem: 'JavaScript',
|
|
771
|
-
frameworks: [],
|
|
772
|
-
hasTests: false,
|
|
773
|
-
hasDocker: false,
|
|
774
|
-
hasCi: false,
|
|
775
|
-
buildTool: null,
|
|
776
|
-
},
|
|
777
|
-
totalFiles: 20,
|
|
778
|
-
totalSize: 2000,
|
|
779
|
-
totalLines: 200,
|
|
780
|
-
scanDuration: 10,
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
await indexStorage.writeIndex(testProjectId, projectIndex)
|
|
784
|
-
|
|
785
|
-
const result = await indexStorage.readIndex(testProjectId)
|
|
786
|
-
expect(result).not.toBeNull()
|
|
787
|
-
expect(result!.totalFiles).toBe(20)
|
|
788
|
-
})
|
|
789
|
-
|
|
790
|
-
it('should write and read checksums via SQLite', async () => {
|
|
791
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
792
|
-
|
|
793
|
-
const checksums = {
|
|
794
|
-
version: '1.0.0',
|
|
795
|
-
lastUpdated: '2026-01-01T00:00:00.000Z',
|
|
796
|
-
checksums: { 'a.ts': 'hash1', 'b.ts': 'hash2' },
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
await indexStorage.writeChecksums(testProjectId, checksums)
|
|
800
|
-
|
|
801
|
-
const result = await indexStorage.readChecksums(testProjectId)
|
|
802
|
-
expect(result.checksums['a.ts']).toBe('hash1')
|
|
803
|
-
expect(result.checksums['b.ts']).toBe('hash2')
|
|
804
|
-
})
|
|
805
|
-
|
|
806
|
-
it('should write and read file scores via SQLite', async () => {
|
|
807
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
808
|
-
|
|
809
|
-
const scores = [
|
|
810
|
-
{ path: 'src/main.ts', score: 0.95, size: 1000, mtime: '2026-01-01T00:00:00.000Z' },
|
|
811
|
-
{ path: 'src/utils.ts', score: 0.7, size: 500, mtime: '2026-01-01T00:00:00.000Z' },
|
|
812
|
-
]
|
|
813
|
-
|
|
814
|
-
await indexStorage.writeScores(testProjectId, scores)
|
|
815
|
-
|
|
816
|
-
const result = await indexStorage.readScores(testProjectId)
|
|
817
|
-
expect(result).toHaveLength(2)
|
|
818
|
-
expect(result[0].path).toBe('src/main.ts')
|
|
819
|
-
expect(result[0].score).toBe(0.95)
|
|
820
|
-
})
|
|
821
|
-
|
|
822
|
-
it('should write and read domains via SQLite', async () => {
|
|
823
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
824
|
-
|
|
825
|
-
const domains = {
|
|
826
|
-
version: '1.0.0',
|
|
827
|
-
projectId: testProjectId,
|
|
828
|
-
domains: [
|
|
829
|
-
{
|
|
830
|
-
name: 'api',
|
|
831
|
-
description: 'API layer',
|
|
832
|
-
keywords: ['api'],
|
|
833
|
-
filePatterns: ['**/api/**'],
|
|
834
|
-
fileCount: 5,
|
|
835
|
-
},
|
|
836
|
-
],
|
|
837
|
-
discoveredAt: '2026-01-01T00:00:00.000Z',
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
await indexStorage.writeDomains(testProjectId, domains)
|
|
841
|
-
|
|
842
|
-
const result = await indexStorage.readDomains(testProjectId)
|
|
843
|
-
expect(result).not.toBeNull()
|
|
844
|
-
expect(result!.domains[0].name).toBe('api')
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
it('should write and read categories via SQLite', async () => {
|
|
848
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
849
|
-
|
|
850
|
-
const cache = {
|
|
851
|
-
version: '1.0.0',
|
|
852
|
-
lastUpdate: '2026-01-01T00:00:00.000Z',
|
|
853
|
-
fileCategories: [
|
|
854
|
-
{
|
|
855
|
-
path: 'src/api.ts',
|
|
856
|
-
categories: ['api', 'backend'],
|
|
857
|
-
primaryDomain: 'api',
|
|
858
|
-
confidence: 0.9,
|
|
859
|
-
categorizedAt: '2026-01-01T00:00:00.000Z',
|
|
860
|
-
method: 'heuristic' as const,
|
|
861
|
-
},
|
|
862
|
-
],
|
|
863
|
-
domainIndex: { api: ['src/api.ts'] },
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
await indexStorage.writeCategories(testProjectId, cache)
|
|
867
|
-
|
|
868
|
-
const result = await indexStorage.readCategories(testProjectId)
|
|
869
|
-
expect(result).not.toBeNull()
|
|
870
|
-
expect(result!.fileCategories[0].path).toBe('src/api.ts')
|
|
871
|
-
})
|
|
872
|
-
|
|
873
|
-
it('should clear SQLite on clearIndex', async () => {
|
|
874
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
875
|
-
|
|
876
|
-
await indexStorage.writeIndex(testProjectId, {
|
|
877
|
-
version: '1.0.0',
|
|
878
|
-
projectPath: '/test',
|
|
879
|
-
lastFullScan: '2026-01-01T00:00:00.000Z',
|
|
880
|
-
lastIncrementalUpdate: '',
|
|
881
|
-
languages: {},
|
|
882
|
-
configFiles: [],
|
|
883
|
-
directories: [],
|
|
884
|
-
relevantFiles: [],
|
|
885
|
-
patterns: [],
|
|
886
|
-
detectedStack: {
|
|
887
|
-
ecosystem: 'JavaScript',
|
|
888
|
-
frameworks: [],
|
|
889
|
-
hasTests: false,
|
|
890
|
-
hasDocker: false,
|
|
891
|
-
hasCi: false,
|
|
892
|
-
buildTool: null,
|
|
893
|
-
},
|
|
894
|
-
totalFiles: 1,
|
|
895
|
-
totalSize: 1,
|
|
896
|
-
totalLines: 1,
|
|
897
|
-
scanDuration: 1,
|
|
898
|
-
})
|
|
899
|
-
|
|
900
|
-
await indexStorage.clearIndex(testProjectId)
|
|
901
|
-
|
|
902
|
-
const sqliteRow = prjctDb.get<{ data: string }>(
|
|
903
|
-
testProjectId,
|
|
904
|
-
'SELECT data FROM index_meta WHERE key = ?',
|
|
905
|
-
'project-index'
|
|
906
|
-
)
|
|
907
|
-
expect(sqliteRow).toBeNull()
|
|
908
|
-
|
|
909
|
-
const result = await indexStorage.readIndex(testProjectId)
|
|
910
|
-
expect(result).toBeNull()
|
|
911
|
-
})
|
|
912
|
-
|
|
913
|
-
it('should return null for outdated index version', async () => {
|
|
914
|
-
// Ensure project directory exists for DB creation
|
|
915
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
916
|
-
// Write directly to SQLite with wrong version
|
|
917
|
-
const db = prjctDb.getDb(testProjectId)
|
|
918
|
-
db.prepare('INSERT OR REPLACE INTO index_meta (key, data, updated_at) VALUES (?, ?, ?)').run(
|
|
919
|
-
'project-index',
|
|
920
|
-
JSON.stringify({ version: '0.0.1', totalFiles: 5 }),
|
|
921
|
-
new Date().toISOString()
|
|
922
|
-
)
|
|
923
|
-
|
|
924
|
-
const result = await indexStorage.readIndex(testProjectId)
|
|
925
|
-
expect(result).toBeNull()
|
|
926
|
-
})
|
|
927
|
-
})
|
|
928
|
-
|
|
929
|
-
// ===========================================================================
|
|
930
|
-
// Database Manager Tests
|
|
931
|
-
// ===========================================================================
|
|
932
|
-
|
|
933
|
-
describe('database manager', () => {
|
|
934
|
-
it('should create tables on first access', async () => {
|
|
935
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
936
|
-
const db = prjctDb.getDb(testProjectId)
|
|
937
|
-
const tables = db
|
|
938
|
-
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
939
|
-
.all() as Array<{ name: string }>
|
|
940
|
-
|
|
941
|
-
const tableNames = tables.map((t) => t.name)
|
|
942
|
-
expect(tableNames).toContain('kv_store')
|
|
943
|
-
expect(tableNames).toContain('tasks')
|
|
944
|
-
expect(tableNames).toContain('subtasks')
|
|
945
|
-
expect(tableNames).toContain('events')
|
|
946
|
-
expect(tableNames).toContain('index_meta')
|
|
947
|
-
expect(tableNames).toContain('index_files')
|
|
948
|
-
expect(tableNames).toContain('index_checksums')
|
|
949
|
-
expect(tableNames).toContain('memory')
|
|
950
|
-
})
|
|
951
|
-
|
|
952
|
-
it('should track migrations', async () => {
|
|
953
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
954
|
-
prjctDb.getDb(testProjectId) // Ensure DB is initialized
|
|
955
|
-
const migrations = prjctDb.getMigrations(testProjectId)
|
|
956
|
-
expect(migrations.length).toBeGreaterThan(0)
|
|
957
|
-
expect(migrations[0].name).toBe('initial-schema')
|
|
958
|
-
})
|
|
959
|
-
|
|
960
|
-
it('should support document CRUD operations', async () => {
|
|
961
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
962
|
-
// Create
|
|
963
|
-
prjctDb.setDoc(testProjectId, 'test-key', { hello: 'world' })
|
|
964
|
-
expect(prjctDb.hasDoc(testProjectId, 'test-key')).toBe(true)
|
|
965
|
-
|
|
966
|
-
// Read
|
|
967
|
-
const doc = prjctDb.getDoc<{ hello: string }>(testProjectId, 'test-key')
|
|
968
|
-
expect(doc).not.toBeNull()
|
|
969
|
-
expect(doc!.hello).toBe('world')
|
|
970
|
-
|
|
971
|
-
// Update
|
|
972
|
-
prjctDb.setDoc(testProjectId, 'test-key', { hello: 'updated' })
|
|
973
|
-
const updated = prjctDb.getDoc<{ hello: string }>(testProjectId, 'test-key')
|
|
974
|
-
expect(updated!.hello).toBe('updated')
|
|
975
|
-
|
|
976
|
-
// Delete
|
|
977
|
-
prjctDb.deleteDoc(testProjectId, 'test-key')
|
|
978
|
-
expect(prjctDb.hasDoc(testProjectId, 'test-key')).toBe(false)
|
|
979
|
-
expect(prjctDb.getDoc(testProjectId, 'test-key')).toBeNull()
|
|
980
|
-
})
|
|
981
|
-
|
|
982
|
-
it('should support event log operations', async () => {
|
|
983
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
984
|
-
prjctDb.appendEvent(testProjectId, 'test.event', { key: 'value' }, 'task-1')
|
|
985
|
-
prjctDb.appendEvent(testProjectId, 'test.event', { key: 'value2' }, 'task-1')
|
|
986
|
-
prjctDb.appendEvent(testProjectId, 'other.event', { key: 'value3' })
|
|
987
|
-
|
|
988
|
-
const allEvents = prjctDb.getEvents(testProjectId)
|
|
989
|
-
expect(allEvents).toHaveLength(3)
|
|
990
|
-
|
|
991
|
-
const testEvents = prjctDb.getEvents(testProjectId, 'test.event')
|
|
992
|
-
expect(testEvents).toHaveLength(2)
|
|
993
|
-
})
|
|
994
|
-
|
|
995
|
-
it('should support transactions', async () => {
|
|
996
|
-
await fs.mkdir(path.join(tmpRoot!, testProjectId), { recursive: true })
|
|
997
|
-
const result = prjctDb.transaction(testProjectId, (db) => {
|
|
998
|
-
db.prepare('INSERT INTO kv_store (key, data, updated_at) VALUES (?, ?, ?)').run(
|
|
999
|
-
'tx-key-1',
|
|
1000
|
-
'"value1"',
|
|
1001
|
-
new Date().toISOString()
|
|
1002
|
-
)
|
|
1003
|
-
db.prepare('INSERT INTO kv_store (key, data, updated_at) VALUES (?, ?, ?)').run(
|
|
1004
|
-
'tx-key-2',
|
|
1005
|
-
'"value2"',
|
|
1006
|
-
new Date().toISOString()
|
|
1007
|
-
)
|
|
1008
|
-
return 'committed'
|
|
1009
|
-
})
|
|
1010
|
-
|
|
1011
|
-
expect(result).toBe('committed')
|
|
1012
|
-
expect(prjctDb.hasDoc(testProjectId, 'tx-key-1')).toBe(true)
|
|
1013
|
-
expect(prjctDb.hasDoc(testProjectId, 'tx-key-2')).toBe(true)
|
|
1014
|
-
})
|
|
1015
|
-
})
|
|
1016
|
-
})
|