prjct-cli 1.22.0 → 1.23.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 +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -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,1547 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory System
|
|
3
|
-
* Tracks user preferences, decisions, and learned patterns.
|
|
4
|
-
*
|
|
5
|
-
* Three-tier memory system:
|
|
6
|
-
* - Tier 1: Session (ephemeral) - single command context
|
|
7
|
-
* - Tier 2: Patterns (persistent) - learned preferences and decisions
|
|
8
|
-
* - Tier 3: History (JSONL) - append-only audit log
|
|
9
|
-
*
|
|
10
|
-
* @module agentic/memory-system
|
|
11
|
-
* @version 3.3
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from 'node:fs/promises'
|
|
15
|
-
import path from 'node:path'
|
|
16
|
-
import pathManager from '../infrastructure/path-manager'
|
|
17
|
-
import { generateUUID } from '../schemas'
|
|
18
|
-
import { isNotFoundError } from '../types/fs'
|
|
19
|
-
import { getTimestamp, getTodayKey } from '../utils/date-helper'
|
|
20
|
-
import { ensureDir } from '../utils/file-helper'
|
|
21
|
-
import { appendJsonLine, getLastJsonLines } from '../utils/jsonl-helper'
|
|
22
|
-
|
|
23
|
-
// Re-export types from canonical location
|
|
24
|
-
export type {
|
|
25
|
-
ConfidenceLevel,
|
|
26
|
-
Decision,
|
|
27
|
-
HistoryEntry,
|
|
28
|
-
HistoryEventType,
|
|
29
|
-
KnownDomain,
|
|
30
|
-
Memory,
|
|
31
|
-
MemoryContext,
|
|
32
|
-
MemoryContextParams,
|
|
33
|
-
MemoryDatabase,
|
|
34
|
-
MemoryRetrievalResult,
|
|
35
|
-
MemoryTag,
|
|
36
|
-
Patterns,
|
|
37
|
-
Preference,
|
|
38
|
-
RelevantMemoryQuery,
|
|
39
|
-
ScoredMemory,
|
|
40
|
-
TaskDomain,
|
|
41
|
-
Workflow,
|
|
42
|
-
} from '../types/memory'
|
|
43
|
-
|
|
44
|
-
export { calculateConfidence, KNOWN_DOMAINS, MEMORY_TAGS } from '../types/memory'
|
|
45
|
-
|
|
46
|
-
import type {
|
|
47
|
-
HistoryEntry,
|
|
48
|
-
HistoryEventType,
|
|
49
|
-
KnownDomain,
|
|
50
|
-
Memory,
|
|
51
|
-
MemoryContext,
|
|
52
|
-
MemoryDatabase,
|
|
53
|
-
MemoryRetrievalResult,
|
|
54
|
-
MemoryTag,
|
|
55
|
-
Patterns,
|
|
56
|
-
Preference,
|
|
57
|
-
RelevantMemoryQuery,
|
|
58
|
-
ScoredMemory,
|
|
59
|
-
TaskDomain,
|
|
60
|
-
Workflow,
|
|
61
|
-
} from '../types/memory'
|
|
62
|
-
|
|
63
|
-
import { calculateConfidence, KNOWN_DOMAINS, MEMORY_TAGS } from '../types/memory'
|
|
64
|
-
|
|
65
|
-
// =============================================================================
|
|
66
|
-
// Semantic Domain Mapping (PRJ-300)
|
|
67
|
-
// =============================================================================
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Map each known domain to its relevant MEMORY_TAGS.
|
|
71
|
-
* More comprehensive than the previous mapping — includes TECH_STACK and
|
|
72
|
-
* DEPENDENCIES where they apply.
|
|
73
|
-
*/
|
|
74
|
-
export const DOMAIN_TAG_MAP: Record<string, MemoryTag[]> = {
|
|
75
|
-
frontend: [
|
|
76
|
-
MEMORY_TAGS.CODE_STYLE,
|
|
77
|
-
MEMORY_TAGS.FILE_STRUCTURE,
|
|
78
|
-
MEMORY_TAGS.ARCHITECTURE,
|
|
79
|
-
MEMORY_TAGS.TECH_STACK,
|
|
80
|
-
],
|
|
81
|
-
backend: [
|
|
82
|
-
MEMORY_TAGS.CODE_STYLE,
|
|
83
|
-
MEMORY_TAGS.ARCHITECTURE,
|
|
84
|
-
MEMORY_TAGS.DEPENDENCIES,
|
|
85
|
-
MEMORY_TAGS.TECH_STACK,
|
|
86
|
-
],
|
|
87
|
-
devops: [
|
|
88
|
-
MEMORY_TAGS.SHIP_WORKFLOW,
|
|
89
|
-
MEMORY_TAGS.TEST_BEHAVIOR,
|
|
90
|
-
MEMORY_TAGS.DEPENDENCIES,
|
|
91
|
-
MEMORY_TAGS.ARCHITECTURE,
|
|
92
|
-
],
|
|
93
|
-
docs: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.NAMING_CONVENTION, MEMORY_TAGS.FILE_STRUCTURE],
|
|
94
|
-
testing: [MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.DEPENDENCIES],
|
|
95
|
-
database: [
|
|
96
|
-
MEMORY_TAGS.ARCHITECTURE,
|
|
97
|
-
MEMORY_TAGS.NAMING_CONVENTION,
|
|
98
|
-
MEMORY_TAGS.TECH_STACK,
|
|
99
|
-
MEMORY_TAGS.DEPENDENCIES,
|
|
100
|
-
],
|
|
101
|
-
general: Object.values(MEMORY_TAGS) as MemoryTag[],
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Semantic keywords for each domain.
|
|
106
|
-
* Used to resolve unknown domain strings (e.g., "uxui" → frontend)
|
|
107
|
-
* and for partial scoring when memory tags relate semantically.
|
|
108
|
-
* @see PRJ-300
|
|
109
|
-
*/
|
|
110
|
-
export const SEMANTIC_DOMAIN_KEYWORDS: Record<string, string[]> = {
|
|
111
|
-
frontend: [
|
|
112
|
-
'ui',
|
|
113
|
-
'ux',
|
|
114
|
-
'uxui',
|
|
115
|
-
'css',
|
|
116
|
-
'styling',
|
|
117
|
-
'component',
|
|
118
|
-
'layout',
|
|
119
|
-
'design',
|
|
120
|
-
'responsive',
|
|
121
|
-
'react',
|
|
122
|
-
'vue',
|
|
123
|
-
'svelte',
|
|
124
|
-
'angular',
|
|
125
|
-
'html',
|
|
126
|
-
'tailwind',
|
|
127
|
-
'sass',
|
|
128
|
-
'web',
|
|
129
|
-
'accessibility',
|
|
130
|
-
'a11y',
|
|
131
|
-
],
|
|
132
|
-
backend: [
|
|
133
|
-
'api',
|
|
134
|
-
'server',
|
|
135
|
-
'route',
|
|
136
|
-
'endpoint',
|
|
137
|
-
'rest',
|
|
138
|
-
'graphql',
|
|
139
|
-
'middleware',
|
|
140
|
-
'worker',
|
|
141
|
-
'queue',
|
|
142
|
-
'auth',
|
|
143
|
-
'hono',
|
|
144
|
-
'express',
|
|
145
|
-
'service',
|
|
146
|
-
'microservice',
|
|
147
|
-
],
|
|
148
|
-
devops: [
|
|
149
|
-
'ci',
|
|
150
|
-
'cd',
|
|
151
|
-
'docker',
|
|
152
|
-
'kubernetes',
|
|
153
|
-
'deploy',
|
|
154
|
-
'infra',
|
|
155
|
-
'infrastructure',
|
|
156
|
-
'monitoring',
|
|
157
|
-
'cloud',
|
|
158
|
-
'aws',
|
|
159
|
-
'gcp',
|
|
160
|
-
'azure',
|
|
161
|
-
'pipeline',
|
|
162
|
-
'helm',
|
|
163
|
-
'terraform',
|
|
164
|
-
],
|
|
165
|
-
docs: ['documentation', 'readme', 'guide', 'tutorial', 'wiki', 'changelog', 'jsdoc', 'typedoc'],
|
|
166
|
-
testing: [
|
|
167
|
-
'test',
|
|
168
|
-
'spec',
|
|
169
|
-
'e2e',
|
|
170
|
-
'unit',
|
|
171
|
-
'integration',
|
|
172
|
-
'coverage',
|
|
173
|
-
'mock',
|
|
174
|
-
'vitest',
|
|
175
|
-
'jest',
|
|
176
|
-
'playwright',
|
|
177
|
-
'cypress',
|
|
178
|
-
],
|
|
179
|
-
database: [
|
|
180
|
-
'db',
|
|
181
|
-
'sql',
|
|
182
|
-
'schema',
|
|
183
|
-
'migration',
|
|
184
|
-
'query',
|
|
185
|
-
'orm',
|
|
186
|
-
'prisma',
|
|
187
|
-
'mongo',
|
|
188
|
-
'postgres',
|
|
189
|
-
'redis',
|
|
190
|
-
'drizzle',
|
|
191
|
-
'sqlite',
|
|
192
|
-
],
|
|
193
|
-
general: [],
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Resolve a domain string to canonical known domain(s).
|
|
198
|
-
* Known domains pass through; unknown domains are matched via semantic keywords.
|
|
199
|
-
* Exported for testing.
|
|
200
|
-
* @see PRJ-300
|
|
201
|
-
*/
|
|
202
|
-
export function resolveCanonicalDomains(domain: string): KnownDomain[] {
|
|
203
|
-
// Exact match
|
|
204
|
-
if ((KNOWN_DOMAINS as readonly string[]).includes(domain)) {
|
|
205
|
-
return [domain as KnownDomain]
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Semantic resolution — find canonical domains whose keywords match
|
|
209
|
-
const normalized = domain.toLowerCase().replace(/[-_\s]/g, '')
|
|
210
|
-
const matches: KnownDomain[] = []
|
|
211
|
-
|
|
212
|
-
for (const [canonical, keywords] of Object.entries(SEMANTIC_DOMAIN_KEYWORDS)) {
|
|
213
|
-
if (canonical === 'general') continue
|
|
214
|
-
for (const kw of keywords) {
|
|
215
|
-
if (normalized.includes(kw) || kw.includes(normalized)) {
|
|
216
|
-
matches.push(canonical as KnownDomain)
|
|
217
|
-
break
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return matches.length > 0 ? matches : ['general']
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// =============================================================================
|
|
226
|
-
// Base Store
|
|
227
|
-
// =============================================================================
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Abstract base class for project-scoped, disk-backed stores with in-memory caching.
|
|
231
|
-
*
|
|
232
|
-
* Provides lazy loading, automatic directory creation on save, and project-scoped
|
|
233
|
-
* cache invalidation. Subclasses only need to define the filename, default data
|
|
234
|
-
* structure, and optionally a subdirectory or post-load normalization hook.
|
|
235
|
-
*
|
|
236
|
-
* Extended by {@link PatternStore} and {@link SemanticMemories}.
|
|
237
|
-
*
|
|
238
|
-
* @typeParam T - The shape of the stored data (e.g., `Patterns`, `MemoryDatabase`)
|
|
239
|
-
*
|
|
240
|
-
* @example
|
|
241
|
-
* ```ts
|
|
242
|
-
* class MyStore extends CachedStore<MyData> {
|
|
243
|
-
* protected getFilename() { return 'my-data.json' }
|
|
244
|
-
* protected getDefault() { return { items: [] } }
|
|
245
|
-
* }
|
|
246
|
-
*
|
|
247
|
-
* const store = new MyStore()
|
|
248
|
-
* const data = await store.load('project-id')
|
|
249
|
-
* ```
|
|
250
|
-
*/
|
|
251
|
-
export abstract class CachedStore<T> {
|
|
252
|
-
private _data: T | null = null
|
|
253
|
-
private _loaded: boolean = false
|
|
254
|
-
private _projectId: string | null = null
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Return the filename for this store (e.g., `'patterns.json'`).
|
|
258
|
-
* @returns The JSON filename used for disk persistence
|
|
259
|
-
*/
|
|
260
|
-
protected abstract getFilename(): string
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Return the default data structure when the file does not exist on disk.
|
|
264
|
-
* @returns A fresh default instance of `T`
|
|
265
|
-
*/
|
|
266
|
-
protected abstract getDefault(): T
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Optional subdirectory within the project's `memory/` folder.
|
|
270
|
-
* Override to nest the store file under a subfolder.
|
|
271
|
-
*
|
|
272
|
-
* @returns Subdirectory name, or `null` to store directly in `memory/`
|
|
273
|
-
*/
|
|
274
|
-
protected getSubdirectory(): string | null {
|
|
275
|
-
return null
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Build the full filesystem path for this store's JSON file.
|
|
280
|
-
*
|
|
281
|
-
* @param projectId - The project identifier used for path resolution
|
|
282
|
-
* @returns Absolute path to the store file
|
|
283
|
-
* (e.g., `~/.prjct-cli/projects/{id}/memory/patterns.json`)
|
|
284
|
-
*/
|
|
285
|
-
protected getPath(projectId: string): string {
|
|
286
|
-
const basePath = path.join(pathManager.getGlobalProjectPath(projectId), 'memory')
|
|
287
|
-
|
|
288
|
-
const subdir = this.getSubdirectory()
|
|
289
|
-
if (subdir) {
|
|
290
|
-
return path.join(basePath, subdir, this.getFilename())
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return path.join(basePath, this.getFilename())
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Load data from disk with project-scoped caching.
|
|
298
|
-
*
|
|
299
|
-
* Returns cached data immediately if already loaded for the same project.
|
|
300
|
-
* Otherwise reads from disk, falling back to {@link getDefault} when the
|
|
301
|
-
* file does not exist. Calls {@link afterLoad} after a successful disk read.
|
|
302
|
-
*
|
|
303
|
-
* @param projectId - The project identifier
|
|
304
|
-
* @returns The loaded (or cached) data
|
|
305
|
-
* @throws {Error} If the file read fails for reasons other than ENOENT
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* ```ts
|
|
309
|
-
* const patterns = await patternStore.load('my-project-id')
|
|
310
|
-
* ```
|
|
311
|
-
*/
|
|
312
|
-
async load(projectId: string): Promise<T> {
|
|
313
|
-
// Return cached if same project and loaded
|
|
314
|
-
if (this._loaded && this._data && this._projectId === projectId) {
|
|
315
|
-
return this._data
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Load from disk
|
|
319
|
-
const filePath = this.getPath(projectId)
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
323
|
-
this._data = JSON.parse(content) as T
|
|
324
|
-
// Allow subclasses to normalize data after load
|
|
325
|
-
this.afterLoad(this._data)
|
|
326
|
-
} catch (error) {
|
|
327
|
-
if (isNotFoundError(error)) {
|
|
328
|
-
this._data = this.getDefault()
|
|
329
|
-
} else {
|
|
330
|
-
throw error
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
this._loaded = true
|
|
335
|
-
this._projectId = projectId
|
|
336
|
-
|
|
337
|
-
return this._data
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Hook for subclasses to normalize or migrate data after loading from disk.
|
|
342
|
-
*
|
|
343
|
-
* Called once per disk read (not on cache hits). Override to ensure
|
|
344
|
-
* structural invariants — e.g., adding missing index keys.
|
|
345
|
-
*
|
|
346
|
-
* @param _data - The freshly loaded data to normalize (mutate in place)
|
|
347
|
-
*/
|
|
348
|
-
protected afterLoad(_data: T): void {
|
|
349
|
-
// Override in subclass if needed
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Persist the current in-memory data to disk.
|
|
354
|
-
*
|
|
355
|
-
* Creates parent directories automatically if they don't exist.
|
|
356
|
-
* No-op if no data has been loaded yet.
|
|
357
|
-
*
|
|
358
|
-
* @param projectId - The project identifier for path resolution
|
|
359
|
-
* @throws {Error} If the file write fails
|
|
360
|
-
*/
|
|
361
|
-
async save(projectId: string): Promise<void> {
|
|
362
|
-
if (!this._data) return
|
|
363
|
-
|
|
364
|
-
const filePath = this.getPath(projectId)
|
|
365
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
366
|
-
await fs.writeFile(filePath, JSON.stringify(this._data, null, 2), 'utf-8')
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Access the cached data without triggering a disk read.
|
|
371
|
-
*
|
|
372
|
-
* @returns The cached data, or `null` if nothing has been loaded
|
|
373
|
-
*/
|
|
374
|
-
protected getData(): T | null {
|
|
375
|
-
return this._data
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Replace the in-memory data directly. Does not persist to disk —
|
|
380
|
-
* call {@link save} afterwards if persistence is needed.
|
|
381
|
-
*
|
|
382
|
-
* @param data - The new data to cache
|
|
383
|
-
*/
|
|
384
|
-
protected setData(data: T): void {
|
|
385
|
-
this._data = data
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Atomically load, transform, and save data in one operation.
|
|
390
|
-
*
|
|
391
|
-
* @param projectId - The project identifier
|
|
392
|
-
* @param updater - Pure function that receives current data and returns updated data
|
|
393
|
-
* @returns The updated data after saving
|
|
394
|
-
* @throws {Error} If load or save fails
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```ts
|
|
398
|
-
* await store.update('my-project', (data) => ({
|
|
399
|
-
* ...data,
|
|
400
|
-
* count: data.count + 1,
|
|
401
|
-
* }))
|
|
402
|
-
* ```
|
|
403
|
-
*/
|
|
404
|
-
async update(projectId: string, updater: (data: T) => T): Promise<T> {
|
|
405
|
-
const data = await this.load(projectId)
|
|
406
|
-
const updated = updater(data)
|
|
407
|
-
this._data = updated
|
|
408
|
-
await this.save(projectId)
|
|
409
|
-
return updated
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Check whether data has been loaded into the cache.
|
|
414
|
-
*
|
|
415
|
-
* @param projectId - If provided, checks that data is loaded for this specific project.
|
|
416
|
-
* If omitted, returns `true` if any project's data is cached.
|
|
417
|
-
* @returns `true` if data is loaded (and matches the project, when specified)
|
|
418
|
-
*/
|
|
419
|
-
isLoaded(projectId?: string): boolean {
|
|
420
|
-
if (projectId) {
|
|
421
|
-
return this._loaded && this._projectId === projectId
|
|
422
|
-
}
|
|
423
|
-
return this._loaded
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Clear the in-memory cache, forcing a fresh disk read on the next {@link load} call.
|
|
428
|
-
* Does not delete or modify the file on disk.
|
|
429
|
-
*/
|
|
430
|
-
reset(): void {
|
|
431
|
-
this._data = null
|
|
432
|
-
this._loaded = false
|
|
433
|
-
this._projectId = null
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// =============================================================================
|
|
438
|
-
// Session Store (Tier 1)
|
|
439
|
-
// =============================================================================
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Session Memory - Tier 1
|
|
443
|
-
* Ephemeral, single command context.
|
|
444
|
-
*/
|
|
445
|
-
export class SessionStore {
|
|
446
|
-
private _sessionMemory: Map<string, { value: unknown; timestamp: number }> = new Map()
|
|
447
|
-
|
|
448
|
-
setSession(key: string, value: unknown): void {
|
|
449
|
-
this._sessionMemory.set(key, { value, timestamp: Date.now() })
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
getSession(key: string): unknown {
|
|
453
|
-
const entry = this._sessionMemory.get(key)
|
|
454
|
-
return entry?.value
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
clearSession(): void {
|
|
458
|
-
this._sessionMemory.clear()
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// =============================================================================
|
|
463
|
-
// History Store (Tier 3)
|
|
464
|
-
// =============================================================================
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* History - Tier 3
|
|
468
|
-
* Append-only JSONL audit log with temporal fragmentation.
|
|
469
|
-
*/
|
|
470
|
-
export class HistoryStore {
|
|
471
|
-
private _getSessionPath(projectId: string): string {
|
|
472
|
-
const now = new Date()
|
|
473
|
-
const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
|
474
|
-
const day = getTodayKey()
|
|
475
|
-
|
|
476
|
-
return path.join(
|
|
477
|
-
pathManager.getGlobalProjectPath(projectId),
|
|
478
|
-
'memory',
|
|
479
|
-
'sessions',
|
|
480
|
-
yearMonth,
|
|
481
|
-
`${day}.jsonl`
|
|
482
|
-
)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async appendHistory(
|
|
486
|
-
projectId: string,
|
|
487
|
-
entry: Record<string, unknown> & { type: HistoryEventType }
|
|
488
|
-
): Promise<void> {
|
|
489
|
-
const sessionPath = this._getSessionPath(projectId)
|
|
490
|
-
await ensureDir(path.dirname(sessionPath))
|
|
491
|
-
|
|
492
|
-
const logEntry: HistoryEntry = {
|
|
493
|
-
ts: getTimestamp(),
|
|
494
|
-
...entry,
|
|
495
|
-
type: entry.type,
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
await appendJsonLine(sessionPath, logEntry)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async getRecentHistory(projectId: string, limit: number = 20): Promise<HistoryEntry[]> {
|
|
502
|
-
const sessionPath = this._getSessionPath(projectId)
|
|
503
|
-
return getLastJsonLines<HistoryEntry>(sessionPath, limit)
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// =============================================================================
|
|
508
|
-
// Pattern Store (Tier 2)
|
|
509
|
-
// =============================================================================
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Patterns - Tier 2
|
|
513
|
-
* Persistent learned preferences and decisions.
|
|
514
|
-
*/
|
|
515
|
-
export class PatternStore extends CachedStore<Patterns> {
|
|
516
|
-
private static readonly MAX_CONTEXTS = 20
|
|
517
|
-
private static readonly ARCHIVE_AGE_DAYS = 90
|
|
518
|
-
|
|
519
|
-
protected getFilename(): string {
|
|
520
|
-
return 'patterns.json'
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
protected getDefault(): Patterns {
|
|
524
|
-
return {
|
|
525
|
-
version: 1,
|
|
526
|
-
decisions: {},
|
|
527
|
-
preferences: {},
|
|
528
|
-
workflows: {},
|
|
529
|
-
counters: {},
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
protected afterLoad(patterns: Patterns): void {
|
|
534
|
-
for (const decision of Object.values(patterns.decisions)) {
|
|
535
|
-
if (decision.contexts.length > PatternStore.MAX_CONTEXTS) {
|
|
536
|
-
decision.contexts = decision.contexts.slice(-PatternStore.MAX_CONTEXTS)
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Convenience alias for backward compatibility
|
|
542
|
-
async loadPatterns(projectId: string): Promise<Patterns> {
|
|
543
|
-
return this.load(projectId)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async savePatterns(projectId: string): Promise<void> {
|
|
547
|
-
return this.save(projectId)
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
async recordDecision(
|
|
551
|
-
projectId: string,
|
|
552
|
-
key: string,
|
|
553
|
-
value: string,
|
|
554
|
-
context: string = '',
|
|
555
|
-
options: { userConfirmed?: boolean } = {}
|
|
556
|
-
): Promise<void> {
|
|
557
|
-
const patterns = await this.load(projectId)
|
|
558
|
-
const now = getTimestamp()
|
|
559
|
-
|
|
560
|
-
if (!patterns.decisions[key]) {
|
|
561
|
-
patterns.decisions[key] = {
|
|
562
|
-
value,
|
|
563
|
-
count: 1,
|
|
564
|
-
firstSeen: now,
|
|
565
|
-
lastSeen: now,
|
|
566
|
-
confidence: options.userConfirmed ? 'high' : 'low',
|
|
567
|
-
contexts: [context].filter(Boolean),
|
|
568
|
-
userConfirmed: options.userConfirmed || false,
|
|
569
|
-
} as Patterns['decisions'][string]
|
|
570
|
-
} else {
|
|
571
|
-
const decision = patterns.decisions[key] as Patterns['decisions'][string] & {
|
|
572
|
-
userConfirmed?: boolean
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (decision.value === value) {
|
|
576
|
-
decision.count++
|
|
577
|
-
decision.lastSeen = now
|
|
578
|
-
if (context && !decision.contexts.includes(context)) {
|
|
579
|
-
decision.contexts.push(context)
|
|
580
|
-
if (decision.contexts.length > PatternStore.MAX_CONTEXTS) {
|
|
581
|
-
decision.contexts = decision.contexts.slice(-PatternStore.MAX_CONTEXTS)
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (options.userConfirmed) {
|
|
585
|
-
decision.userConfirmed = true
|
|
586
|
-
}
|
|
587
|
-
decision.confidence = calculateConfidence(decision.count, decision.userConfirmed)
|
|
588
|
-
} else {
|
|
589
|
-
decision.value = value
|
|
590
|
-
decision.count = 1
|
|
591
|
-
decision.lastSeen = now
|
|
592
|
-
decision.userConfirmed = options.userConfirmed || false
|
|
593
|
-
decision.confidence = options.userConfirmed ? 'high' : 'low'
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
await this.save(projectId)
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async confirmDecision(projectId: string, key: string): Promise<boolean> {
|
|
601
|
-
const patterns = await this.load(projectId)
|
|
602
|
-
const decision = patterns.decisions[key] as
|
|
603
|
-
| (Patterns['decisions'][string] & { userConfirmed?: boolean })
|
|
604
|
-
| undefined
|
|
605
|
-
if (!decision) return false
|
|
606
|
-
|
|
607
|
-
decision.userConfirmed = true
|
|
608
|
-
decision.confidence = 'high'
|
|
609
|
-
decision.lastSeen = getTimestamp()
|
|
610
|
-
await this.save(projectId)
|
|
611
|
-
return true
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
async getDecision(
|
|
615
|
-
projectId: string,
|
|
616
|
-
key: string
|
|
617
|
-
): Promise<{ value: string; confidence: string } | null> {
|
|
618
|
-
const patterns = await this.load(projectId)
|
|
619
|
-
const decision = patterns.decisions[key]
|
|
620
|
-
|
|
621
|
-
if (!decision) return null
|
|
622
|
-
if (decision.confidence === 'low') return null
|
|
623
|
-
|
|
624
|
-
return { value: decision.value, confidence: decision.confidence }
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
async hasPattern(projectId: string, key: string): Promise<boolean> {
|
|
628
|
-
const decision = await this.getDecision(projectId, key)
|
|
629
|
-
return decision !== null
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
async recordWorkflow(
|
|
633
|
-
projectId: string,
|
|
634
|
-
workflowName: string,
|
|
635
|
-
pattern: Record<string, unknown>
|
|
636
|
-
): Promise<void> {
|
|
637
|
-
const patterns = await this.load(projectId)
|
|
638
|
-
const now = getTimestamp()
|
|
639
|
-
|
|
640
|
-
if (!patterns.workflows[workflowName]) {
|
|
641
|
-
patterns.workflows[workflowName] = {
|
|
642
|
-
...pattern,
|
|
643
|
-
count: 1,
|
|
644
|
-
firstSeen: now,
|
|
645
|
-
lastSeen: now,
|
|
646
|
-
confidence: 'low',
|
|
647
|
-
userConfirmed: false,
|
|
648
|
-
}
|
|
649
|
-
} else {
|
|
650
|
-
const workflow = patterns.workflows[workflowName]
|
|
651
|
-
workflow.count++
|
|
652
|
-
workflow.lastSeen = now
|
|
653
|
-
workflow.confidence = calculateConfidence(workflow.count, workflow.userConfirmed)
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
await this.save(projectId)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async confirmWorkflow(projectId: string, workflowName: string): Promise<boolean> {
|
|
660
|
-
const patterns = await this.load(projectId)
|
|
661
|
-
const workflow = patterns.workflows[workflowName]
|
|
662
|
-
if (!workflow) return false
|
|
663
|
-
|
|
664
|
-
workflow.userConfirmed = true
|
|
665
|
-
workflow.confidence = 'high'
|
|
666
|
-
workflow.lastSeen = getTimestamp()
|
|
667
|
-
await this.save(projectId)
|
|
668
|
-
return true
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
async getWorkflow(projectId: string, workflowName: string): Promise<Workflow | null> {
|
|
672
|
-
const patterns = await this.load(projectId)
|
|
673
|
-
const workflow = patterns.workflows[workflowName]
|
|
674
|
-
|
|
675
|
-
if (!workflow || workflow.count < 3) return null
|
|
676
|
-
return workflow
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async setPreference(
|
|
680
|
-
projectId: string,
|
|
681
|
-
key: string,
|
|
682
|
-
value: Preference['value'],
|
|
683
|
-
options: { userConfirmed?: boolean } = {}
|
|
684
|
-
): Promise<void> {
|
|
685
|
-
const patterns = await this.load(projectId)
|
|
686
|
-
const existing = patterns.preferences[key]
|
|
687
|
-
const observationCount = existing ? existing.observationCount + 1 : 1
|
|
688
|
-
const userConfirmed = options.userConfirmed || existing?.userConfirmed || false
|
|
689
|
-
|
|
690
|
-
patterns.preferences[key] = {
|
|
691
|
-
value,
|
|
692
|
-
updatedAt: getTimestamp(),
|
|
693
|
-
confidence: calculateConfidence(observationCount, userConfirmed),
|
|
694
|
-
observationCount,
|
|
695
|
-
userConfirmed,
|
|
696
|
-
}
|
|
697
|
-
await this.save(projectId)
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
async confirmPreference(projectId: string, key: string): Promise<boolean> {
|
|
701
|
-
const patterns = await this.load(projectId)
|
|
702
|
-
const pref = patterns.preferences[key]
|
|
703
|
-
if (!pref) return false
|
|
704
|
-
|
|
705
|
-
pref.userConfirmed = true
|
|
706
|
-
pref.confidence = 'high'
|
|
707
|
-
pref.updatedAt = getTimestamp()
|
|
708
|
-
await this.save(projectId)
|
|
709
|
-
return true
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
async getPreference(
|
|
713
|
-
projectId: string,
|
|
714
|
-
key: string,
|
|
715
|
-
defaultValue: unknown = null
|
|
716
|
-
): Promise<unknown> {
|
|
717
|
-
const patterns = await this.load(projectId)
|
|
718
|
-
return patterns.preferences[key]?.value ?? defaultValue
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
async getPatternsSummary(projectId: string) {
|
|
722
|
-
const patterns = await this.load(projectId)
|
|
723
|
-
|
|
724
|
-
return {
|
|
725
|
-
decisions: Object.keys(patterns.decisions).length,
|
|
726
|
-
learnedDecisions: Object.values(patterns.decisions).filter((d) => d.confidence !== 'low')
|
|
727
|
-
.length,
|
|
728
|
-
workflows: Object.keys(patterns.workflows).length,
|
|
729
|
-
preferences: Object.keys(patterns.preferences).length,
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
private _getArchivePath(projectId: string): string {
|
|
734
|
-
const basePath = path.join(pathManager.getGlobalProjectPath(projectId), 'memory')
|
|
735
|
-
return path.join(basePath, 'patterns-archive.json')
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
async archiveStaleDecisions(projectId: string): Promise<number> {
|
|
739
|
-
const patterns = await this.load(projectId)
|
|
740
|
-
const now = Date.now()
|
|
741
|
-
const cutoff = PatternStore.ARCHIVE_AGE_DAYS * 24 * 60 * 60 * 1000
|
|
742
|
-
|
|
743
|
-
const staleKeys: string[] = []
|
|
744
|
-
for (const [key, decision] of Object.entries(patterns.decisions)) {
|
|
745
|
-
const lastSeenMs = new Date(decision.lastSeen).getTime()
|
|
746
|
-
if (now - lastSeenMs > cutoff) {
|
|
747
|
-
staleKeys.push(key)
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
if (staleKeys.length === 0) return 0
|
|
752
|
-
|
|
753
|
-
// Load or create archive
|
|
754
|
-
const archivePath = this._getArchivePath(projectId)
|
|
755
|
-
let archive: Record<string, unknown> = {}
|
|
756
|
-
try {
|
|
757
|
-
const content = await fs.readFile(archivePath, 'utf-8')
|
|
758
|
-
archive = JSON.parse(content)
|
|
759
|
-
} catch (error) {
|
|
760
|
-
if (!isNotFoundError(error)) throw error
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Move stale decisions to archive
|
|
764
|
-
for (const key of staleKeys) {
|
|
765
|
-
archive[key] = patterns.decisions[key]
|
|
766
|
-
delete patterns.decisions[key]
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Save archive and pruned patterns
|
|
770
|
-
await fs.mkdir(path.dirname(archivePath), { recursive: true })
|
|
771
|
-
await fs.writeFile(archivePath, JSON.stringify(archive, null, 2), 'utf-8')
|
|
772
|
-
await this.save(projectId)
|
|
773
|
-
|
|
774
|
-
return staleKeys.length
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// =============================================================================
|
|
779
|
-
// Semantic Memories
|
|
780
|
-
// =============================================================================
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Semantic Memories
|
|
784
|
-
* P3.3: Tagged, searchable, CRUD memory operations.
|
|
785
|
-
*/
|
|
786
|
-
export class SemanticMemories extends CachedStore<MemoryDatabase> {
|
|
787
|
-
protected getFilename(): string {
|
|
788
|
-
return 'memories.json'
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
protected getDefault(): MemoryDatabase {
|
|
792
|
-
return {
|
|
793
|
-
version: 1,
|
|
794
|
-
memories: [],
|
|
795
|
-
index: this._createEmptyIndex(),
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
protected afterLoad(db: MemoryDatabase): void {
|
|
800
|
-
this._normalizeIndex(db)
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
private _createEmptyIndex(): Record<string, string[]> {
|
|
804
|
-
const tags = Object.values(MEMORY_TAGS)
|
|
805
|
-
const index: Record<string, string[]> = {}
|
|
806
|
-
for (const tag of tags) index[tag] = []
|
|
807
|
-
return index
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
private _normalizeIndex(db: MemoryDatabase): void {
|
|
811
|
-
// Reason: older persisted files may not include newer tags; ensure all tags are present.
|
|
812
|
-
const tags = Object.values(MEMORY_TAGS)
|
|
813
|
-
for (const tag of tags) {
|
|
814
|
-
if (!db.index[tag]) db.index[tag] = []
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
private _coerceTags(tags: string[]): MemoryTag[] {
|
|
819
|
-
const allowed = new Set<MemoryTag>(Object.values(MEMORY_TAGS) as MemoryTag[])
|
|
820
|
-
return tags.filter((t): t is MemoryTag => allowed.has(t as MemoryTag))
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Convenience alias for backward compatibility
|
|
824
|
-
async loadMemories(projectId: string): Promise<MemoryDatabase> {
|
|
825
|
-
return this.load(projectId)
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
async saveMemories(projectId: string): Promise<void> {
|
|
829
|
-
return this.save(projectId)
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
async createMemory(
|
|
833
|
-
projectId: string,
|
|
834
|
-
{
|
|
835
|
-
title,
|
|
836
|
-
content,
|
|
837
|
-
tags = [],
|
|
838
|
-
userTriggered = false,
|
|
839
|
-
}: { title: string; content: string; tags?: string[]; userTriggered?: boolean }
|
|
840
|
-
): Promise<string> {
|
|
841
|
-
const db = await this.load(projectId)
|
|
842
|
-
const parsedTags = this._coerceTags(tags)
|
|
843
|
-
const now = getTimestamp()
|
|
844
|
-
|
|
845
|
-
const memory: Memory = {
|
|
846
|
-
id: generateUUID(),
|
|
847
|
-
title,
|
|
848
|
-
content,
|
|
849
|
-
tags: parsedTags,
|
|
850
|
-
userTriggered,
|
|
851
|
-
createdAt: now,
|
|
852
|
-
updatedAt: now,
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
db.memories.push(memory)
|
|
856
|
-
|
|
857
|
-
for (const tag of parsedTags) {
|
|
858
|
-
db.index[tag].push(memory.id)
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
await this.save(projectId)
|
|
862
|
-
return memory.id
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
async updateMemory(
|
|
866
|
-
projectId: string,
|
|
867
|
-
memoryId: string,
|
|
868
|
-
updates: { title?: string; content?: string; tags?: string[] }
|
|
869
|
-
): Promise<boolean> {
|
|
870
|
-
const db = await this.load(projectId)
|
|
871
|
-
|
|
872
|
-
const index = db.memories.findIndex((m) => m.id === memoryId)
|
|
873
|
-
if (index === -1) return false
|
|
874
|
-
|
|
875
|
-
const memory = db.memories[index]
|
|
876
|
-
const oldTags = memory.tags || []
|
|
877
|
-
|
|
878
|
-
if (updates.title) memory.title = updates.title
|
|
879
|
-
if (updates.content) memory.content = updates.content
|
|
880
|
-
if (updates.tags) {
|
|
881
|
-
const newTags = this._coerceTags(updates.tags)
|
|
882
|
-
for (const tag of oldTags) {
|
|
883
|
-
db.index[tag] = db.index[tag].filter((id: string) => id !== memoryId)
|
|
884
|
-
}
|
|
885
|
-
for (const tag of newTags) {
|
|
886
|
-
db.index[tag].push(memoryId)
|
|
887
|
-
}
|
|
888
|
-
memory.tags = newTags
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
memory.updatedAt = getTimestamp()
|
|
892
|
-
await this.save(projectId)
|
|
893
|
-
return true
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
async deleteMemory(projectId: string, memoryId: string): Promise<boolean> {
|
|
897
|
-
const db = await this.load(projectId)
|
|
898
|
-
|
|
899
|
-
const index = db.memories.findIndex((m) => m.id === memoryId)
|
|
900
|
-
if (index === -1) return false
|
|
901
|
-
|
|
902
|
-
const memory = db.memories[index]
|
|
903
|
-
|
|
904
|
-
for (const tag of memory.tags || []) {
|
|
905
|
-
if (db.index[tag]) {
|
|
906
|
-
db.index[tag] = db.index[tag].filter((id) => id !== memoryId)
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
db.memories.splice(index, 1)
|
|
911
|
-
await this.save(projectId)
|
|
912
|
-
return true
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
async findByTags(
|
|
916
|
-
projectId: string,
|
|
917
|
-
tags: string[],
|
|
918
|
-
matchAll: boolean = false
|
|
919
|
-
): Promise<Memory[]> {
|
|
920
|
-
const db = await this.load(projectId)
|
|
921
|
-
const parsedTags = this._coerceTags(tags)
|
|
922
|
-
|
|
923
|
-
if (matchAll) {
|
|
924
|
-
return db.memories.filter((m) => parsedTags.every((tag) => (m.tags || []).includes(tag)))
|
|
925
|
-
} else {
|
|
926
|
-
const matchingIds = new Set<string>()
|
|
927
|
-
for (const tag of parsedTags) {
|
|
928
|
-
const ids = db.index[tag]
|
|
929
|
-
for (const id of ids) {
|
|
930
|
-
matchingIds.add(id)
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
return db.memories.filter((m) => matchingIds.has(m.id))
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
async searchMemories(projectId: string, query: string): Promise<Memory[]> {
|
|
938
|
-
const db = await this.load(projectId)
|
|
939
|
-
const queryLower = query.toLowerCase()
|
|
940
|
-
|
|
941
|
-
return db.memories.filter(
|
|
942
|
-
(m) =>
|
|
943
|
-
m.title.toLowerCase().includes(queryLower) || m.content.toLowerCase().includes(queryLower)
|
|
944
|
-
)
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
async getRelevantMemories(
|
|
948
|
-
projectId: string,
|
|
949
|
-
context: MemoryContext,
|
|
950
|
-
limit: number = 5
|
|
951
|
-
): Promise<Memory[]> {
|
|
952
|
-
const db = await this.load(projectId)
|
|
953
|
-
|
|
954
|
-
const scored = db.memories.map((memory) => {
|
|
955
|
-
let score = 0
|
|
956
|
-
|
|
957
|
-
const contextTags = this._extractContextTags(context)
|
|
958
|
-
for (const tag of memory.tags || []) {
|
|
959
|
-
if (contextTags.includes(tag)) score += 10
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const age = Date.now() - new Date(memory.updatedAt).getTime()
|
|
963
|
-
const daysSinceUpdate = age / (1000 * 60 * 60 * 24)
|
|
964
|
-
score += Math.max(0, 5 - daysSinceUpdate)
|
|
965
|
-
|
|
966
|
-
if (memory.userTriggered) score += 5
|
|
967
|
-
|
|
968
|
-
const keywords = this._extractKeywords(context)
|
|
969
|
-
for (const keyword of keywords) {
|
|
970
|
-
if (memory.content.toLowerCase().includes(keyword)) score += 2
|
|
971
|
-
if (memory.title.toLowerCase().includes(keyword)) score += 3
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
return { ...memory, _score: score }
|
|
975
|
-
})
|
|
976
|
-
|
|
977
|
-
return scored
|
|
978
|
-
.filter((m) => m._score > 0)
|
|
979
|
-
.sort((a, b) => b._score - a._score)
|
|
980
|
-
.slice(0, limit)
|
|
981
|
-
.map(({ _score, ...memory }) => memory as Memory)
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* Enhanced memory retrieval with domain-based filtering and metrics.
|
|
986
|
-
* Implements selective memory retrieval based on task relevance.
|
|
987
|
-
* @see PRJ-107
|
|
988
|
-
*/
|
|
989
|
-
async getRelevantMemoriesWithMetrics(
|
|
990
|
-
projectId: string,
|
|
991
|
-
query: RelevantMemoryQuery
|
|
992
|
-
): Promise<MemoryRetrievalResult> {
|
|
993
|
-
const db = await this.load(projectId)
|
|
994
|
-
const totalMemories = db.memories.length
|
|
995
|
-
|
|
996
|
-
if (totalMemories === 0) {
|
|
997
|
-
return {
|
|
998
|
-
memories: [],
|
|
999
|
-
metrics: {
|
|
1000
|
-
totalMemories: 0,
|
|
1001
|
-
memoriesConsidered: 0,
|
|
1002
|
-
memoriesReturned: 0,
|
|
1003
|
-
filteringRatio: 0,
|
|
1004
|
-
avgRelevanceScore: 0,
|
|
1005
|
-
},
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
const maxResults = query.maxResults ?? 10
|
|
1010
|
-
const minRelevance = query.minRelevance ?? 10
|
|
1011
|
-
|
|
1012
|
-
// Score all memories
|
|
1013
|
-
const scored: ScoredMemory[] = db.memories.map((memory) => {
|
|
1014
|
-
const breakdown = {
|
|
1015
|
-
domainMatch: 0,
|
|
1016
|
-
tagMatch: 0,
|
|
1017
|
-
recency: 0,
|
|
1018
|
-
confidence: 0,
|
|
1019
|
-
keywords: 0,
|
|
1020
|
-
userTriggered: 0,
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
// Domain match scoring (0-25 points) — semantic matching (PRJ-300)
|
|
1024
|
-
if (query.taskDomain) {
|
|
1025
|
-
breakdown.domainMatch = this._getSemanticDomainScore(query.taskDomain, memory.tags || [])
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// Tag match from command context (0-20 points)
|
|
1029
|
-
if (query.commandName) {
|
|
1030
|
-
const commandTags = this._getCommandTags(query.commandName)
|
|
1031
|
-
const matchingTags = (memory.tags || []).filter((tag) => commandTags.includes(tag))
|
|
1032
|
-
breakdown.tagMatch = Math.min(20, matchingTags.length * 8)
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// Recency scoring (0-15 points)
|
|
1036
|
-
const age = Date.now() - new Date(memory.updatedAt).getTime()
|
|
1037
|
-
const daysSinceUpdate = age / (1000 * 60 * 60 * 24)
|
|
1038
|
-
breakdown.recency = Math.max(0, Math.round(15 - daysSinceUpdate * 0.5))
|
|
1039
|
-
|
|
1040
|
-
// Confidence scoring (0-20 points) - PRJ-104 integration
|
|
1041
|
-
if (memory.confidence) {
|
|
1042
|
-
breakdown.confidence =
|
|
1043
|
-
memory.confidence === 'high' ? 20 : memory.confidence === 'medium' ? 12 : 5
|
|
1044
|
-
} else if (memory.observationCount) {
|
|
1045
|
-
// Fallback to observation count
|
|
1046
|
-
breakdown.confidence = Math.min(20, memory.observationCount * 3)
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
// Keyword matching (0-15 points)
|
|
1050
|
-
if (query.taskDescription) {
|
|
1051
|
-
const keywords = this._extractKeywordsFromText(query.taskDescription)
|
|
1052
|
-
let keywordScore = 0
|
|
1053
|
-
for (const keyword of keywords) {
|
|
1054
|
-
if (memory.content.toLowerCase().includes(keyword)) keywordScore += 2
|
|
1055
|
-
if (memory.title.toLowerCase().includes(keyword)) keywordScore += 3
|
|
1056
|
-
}
|
|
1057
|
-
breakdown.keywords = Math.min(15, keywordScore)
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
// User triggered bonus (0-5 points)
|
|
1061
|
-
if (memory.userTriggered) {
|
|
1062
|
-
breakdown.userTriggered = 5
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
const relevanceScore =
|
|
1066
|
-
breakdown.domainMatch +
|
|
1067
|
-
breakdown.tagMatch +
|
|
1068
|
-
breakdown.recency +
|
|
1069
|
-
breakdown.confidence +
|
|
1070
|
-
breakdown.keywords +
|
|
1071
|
-
breakdown.userTriggered
|
|
1072
|
-
|
|
1073
|
-
return {
|
|
1074
|
-
...memory,
|
|
1075
|
-
relevanceScore,
|
|
1076
|
-
scoreBreakdown: breakdown,
|
|
1077
|
-
}
|
|
1078
|
-
})
|
|
1079
|
-
|
|
1080
|
-
// Filter by minimum relevance
|
|
1081
|
-
const considered = scored.filter((m) => m.relevanceScore >= minRelevance)
|
|
1082
|
-
|
|
1083
|
-
// Sort by relevance and take top N
|
|
1084
|
-
const sorted = considered.sort((a, b) => b.relevanceScore - a.relevanceScore)
|
|
1085
|
-
const returned = sorted.slice(0, maxResults)
|
|
1086
|
-
|
|
1087
|
-
// Calculate average relevance
|
|
1088
|
-
const avgRelevanceScore =
|
|
1089
|
-
returned.length > 0
|
|
1090
|
-
? Math.round(returned.reduce((sum, m) => sum + m.relevanceScore, 0) / returned.length)
|
|
1091
|
-
: 0
|
|
1092
|
-
|
|
1093
|
-
return {
|
|
1094
|
-
memories: returned,
|
|
1095
|
-
metrics: {
|
|
1096
|
-
totalMemories,
|
|
1097
|
-
memoriesConsidered: considered.length,
|
|
1098
|
-
memoriesReturned: returned.length,
|
|
1099
|
-
filteringRatio: totalMemories > 0 ? returned.length / totalMemories : 0,
|
|
1100
|
-
avgRelevanceScore,
|
|
1101
|
-
},
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Compute semantic domain match score (0-25 points).
|
|
1107
|
-
*
|
|
1108
|
-
* Two-pass scoring:
|
|
1109
|
-
* 1. Exact MEMORY_TAG match: memory tag in domain's tag list → 10 pts each
|
|
1110
|
-
* 2. Semantic match: memory tag relates to domain via keywords → 5 pts each
|
|
1111
|
-
*
|
|
1112
|
-
* Unknown domains are resolved to canonical domain(s) via SEMANTIC_DOMAIN_KEYWORDS.
|
|
1113
|
-
* @see PRJ-107, PRJ-300
|
|
1114
|
-
*/
|
|
1115
|
-
private _getSemanticDomainScore(domain: TaskDomain, memoryTags: string[]): number {
|
|
1116
|
-
// Resolve to canonical domain(s)
|
|
1117
|
-
const canonicals = this._resolveCanonicalDomains(domain)
|
|
1118
|
-
if (canonicals.length === 0) return 0
|
|
1119
|
-
|
|
1120
|
-
// Collect all relevant MEMORY_TAGS for the canonical domains
|
|
1121
|
-
const relevantTags = new Set<string>()
|
|
1122
|
-
for (const canonical of canonicals) {
|
|
1123
|
-
const tags = DOMAIN_TAG_MAP[canonical]
|
|
1124
|
-
if (tags) {
|
|
1125
|
-
for (const tag of tags) relevantTags.add(tag)
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Collect semantic keywords for the canonical domains
|
|
1130
|
-
const domainKeywords = new Set<string>()
|
|
1131
|
-
for (const canonical of canonicals) {
|
|
1132
|
-
const keywords = SEMANTIC_DOMAIN_KEYWORDS[canonical]
|
|
1133
|
-
if (keywords) {
|
|
1134
|
-
for (const kw of keywords) domainKeywords.add(kw)
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
let score = 0
|
|
1139
|
-
|
|
1140
|
-
for (const tag of memoryTags) {
|
|
1141
|
-
// Pass 1: exact MEMORY_TAG match (10 pts)
|
|
1142
|
-
if (relevantTags.has(tag)) {
|
|
1143
|
-
score += 10
|
|
1144
|
-
continue
|
|
1145
|
-
}
|
|
1146
|
-
// Pass 2: semantic keyword match (5 pts)
|
|
1147
|
-
const normalized = tag.toLowerCase().replace(/[-_\s]/g, '')
|
|
1148
|
-
for (const kw of domainKeywords) {
|
|
1149
|
-
if (normalized.includes(kw) || kw.includes(normalized)) {
|
|
1150
|
-
score += 5
|
|
1151
|
-
break
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
return Math.min(25, score)
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
/**
|
|
1160
|
-
* Resolve a domain string to canonical known domain(s).
|
|
1161
|
-
* Delegates to module-level resolveCanonicalDomains().
|
|
1162
|
-
* @see PRJ-300
|
|
1163
|
-
*/
|
|
1164
|
-
private _resolveCanonicalDomains(domain: TaskDomain): KnownDomain[] {
|
|
1165
|
-
return resolveCanonicalDomains(domain)
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Map command to relevant memory tags.
|
|
1170
|
-
* @see PRJ-107
|
|
1171
|
-
*/
|
|
1172
|
-
private _getCommandTags(commandName: string): MemoryTag[] {
|
|
1173
|
-
const commandTags: Record<string, MemoryTag[]> = {
|
|
1174
|
-
ship: [MEMORY_TAGS.COMMIT_STYLE, MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR],
|
|
1175
|
-
feature: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
1176
|
-
done: [MEMORY_TAGS.SHIP_WORKFLOW],
|
|
1177
|
-
analyze: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE],
|
|
1178
|
-
spec: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
1179
|
-
task: [MEMORY_TAGS.BRANCH_NAMING, MEMORY_TAGS.CODE_STYLE],
|
|
1180
|
-
sync: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.DEPENDENCIES],
|
|
1181
|
-
test: [MEMORY_TAGS.TEST_BEHAVIOR],
|
|
1182
|
-
bug: [MEMORY_TAGS.CODE_STYLE, MEMORY_TAGS.TEST_BEHAVIOR],
|
|
1183
|
-
}
|
|
1184
|
-
return commandTags[commandName] || []
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
* Extract keywords from text for matching.
|
|
1189
|
-
*/
|
|
1190
|
-
private _extractKeywordsFromText(text: string): string[] {
|
|
1191
|
-
const words = text.toLowerCase().split(/\s+/)
|
|
1192
|
-
const stopWords = new Set([
|
|
1193
|
-
'the',
|
|
1194
|
-
'a',
|
|
1195
|
-
'an',
|
|
1196
|
-
'is',
|
|
1197
|
-
'are',
|
|
1198
|
-
'to',
|
|
1199
|
-
'for',
|
|
1200
|
-
'and',
|
|
1201
|
-
'or',
|
|
1202
|
-
'in',
|
|
1203
|
-
'on',
|
|
1204
|
-
'at',
|
|
1205
|
-
'by',
|
|
1206
|
-
'with',
|
|
1207
|
-
'from',
|
|
1208
|
-
'as',
|
|
1209
|
-
'it',
|
|
1210
|
-
'this',
|
|
1211
|
-
'that',
|
|
1212
|
-
'be',
|
|
1213
|
-
'have',
|
|
1214
|
-
'has',
|
|
1215
|
-
])
|
|
1216
|
-
return words.filter((w) => w.length > 2 && !stopWords.has(w))
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
private _extractContextTags(context: MemoryContext): string[] {
|
|
1220
|
-
const tags: string[] = []
|
|
1221
|
-
|
|
1222
|
-
const commandTags: Record<string, string[]> = {
|
|
1223
|
-
ship: [MEMORY_TAGS.COMMIT_STYLE, MEMORY_TAGS.SHIP_WORKFLOW, MEMORY_TAGS.TEST_BEHAVIOR],
|
|
1224
|
-
feature: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
1225
|
-
done: [MEMORY_TAGS.SHIP_WORKFLOW],
|
|
1226
|
-
analyze: [MEMORY_TAGS.TECH_STACK, MEMORY_TAGS.ARCHITECTURE],
|
|
1227
|
-
spec: [MEMORY_TAGS.ARCHITECTURE, MEMORY_TAGS.CODE_STYLE],
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
if (context.commandName && commandTags[context.commandName]) {
|
|
1231
|
-
tags.push(...commandTags[context.commandName])
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
return tags
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
private _extractKeywords(context: MemoryContext): string[] {
|
|
1238
|
-
const keywords: string[] = []
|
|
1239
|
-
|
|
1240
|
-
if (context.params?.description) {
|
|
1241
|
-
keywords.push(...(context.params.description as string).toLowerCase().split(/\s+/))
|
|
1242
|
-
}
|
|
1243
|
-
if (context.params?.feature) {
|
|
1244
|
-
keywords.push(...(context.params.feature as string).toLowerCase().split(/\s+/))
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
const stopWords = ['the', 'a', 'an', 'is', 'are', 'to', 'for', 'and', 'or', 'in']
|
|
1248
|
-
return keywords.filter((k) => k.length > 2 && !stopWords.includes(k))
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
async autoRemember(
|
|
1252
|
-
projectId: string,
|
|
1253
|
-
decisionType: string,
|
|
1254
|
-
value: string,
|
|
1255
|
-
context: string = ''
|
|
1256
|
-
): Promise<void> {
|
|
1257
|
-
const tagMap: Record<string, string[]> = {
|
|
1258
|
-
commit_footer: [MEMORY_TAGS.COMMIT_STYLE],
|
|
1259
|
-
branch_naming: [MEMORY_TAGS.BRANCH_NAMING],
|
|
1260
|
-
test_before_ship: [MEMORY_TAGS.TEST_BEHAVIOR, MEMORY_TAGS.SHIP_WORKFLOW],
|
|
1261
|
-
preferred_agent: [MEMORY_TAGS.AGENT_PREFERENCE],
|
|
1262
|
-
code_style: [MEMORY_TAGS.CODE_STYLE],
|
|
1263
|
-
verbosity: [MEMORY_TAGS.OUTPUT_VERBOSITY],
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
const tags = tagMap[decisionType] || []
|
|
1267
|
-
|
|
1268
|
-
const existing = await this.searchMemories(projectId, decisionType)
|
|
1269
|
-
if (existing.length > 0) {
|
|
1270
|
-
await this.updateMemory(projectId, existing[0].id, {
|
|
1271
|
-
content: `${decisionType}: ${value}`,
|
|
1272
|
-
tags,
|
|
1273
|
-
})
|
|
1274
|
-
} else {
|
|
1275
|
-
await this.createMemory(projectId, {
|
|
1276
|
-
title: `Preference: ${decisionType}`,
|
|
1277
|
-
content: `${decisionType}: ${value}${context ? `\nContext: ${context}` : ''}`,
|
|
1278
|
-
tags,
|
|
1279
|
-
userTriggered: true,
|
|
1280
|
-
})
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
async getAllMemories(projectId: string): Promise<Memory[]> {
|
|
1285
|
-
const db = await this.load(projectId)
|
|
1286
|
-
return db.memories
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
async getMemoryStats(projectId: string) {
|
|
1290
|
-
const db = await this.load(projectId)
|
|
1291
|
-
|
|
1292
|
-
const tagCounts: Record<string, number> = {}
|
|
1293
|
-
for (const [tag, ids] of Object.entries(db.index)) {
|
|
1294
|
-
tagCounts[tag] = ids.length
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
return {
|
|
1298
|
-
totalMemories: db.memories.length,
|
|
1299
|
-
userTriggered: db.memories.filter((m) => m.userTriggered).length,
|
|
1300
|
-
tagCounts,
|
|
1301
|
-
oldestMemory: db.memories[0]?.createdAt,
|
|
1302
|
-
newestMemory: db.memories[db.memories.length - 1]?.createdAt,
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// =============================================================================
|
|
1308
|
-
// Memory System (Main Class)
|
|
1309
|
-
// =============================================================================
|
|
1310
|
-
|
|
1311
|
-
/**
|
|
1312
|
-
* Three-tier memory system for learning user patterns.
|
|
1313
|
-
* Tier 1: Session (ephemeral), Tier 2: Patterns (persistent), Tier 3: History (JSONL)
|
|
1314
|
-
*/
|
|
1315
|
-
export class MemorySystem {
|
|
1316
|
-
private _semanticMemories: SemanticMemories
|
|
1317
|
-
private _patternStore: PatternStore
|
|
1318
|
-
private _historyStore: HistoryStore
|
|
1319
|
-
private _sessionStore: SessionStore
|
|
1320
|
-
|
|
1321
|
-
constructor() {
|
|
1322
|
-
this._semanticMemories = new SemanticMemories()
|
|
1323
|
-
this._patternStore = new PatternStore()
|
|
1324
|
-
this._historyStore = new HistoryStore()
|
|
1325
|
-
this._sessionStore = new SessionStore()
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// ===========================================================================
|
|
1329
|
-
// P3.3: SEMANTIC MEMORIES
|
|
1330
|
-
// ===========================================================================
|
|
1331
|
-
|
|
1332
|
-
loadMemories(projectId: string) {
|
|
1333
|
-
return this._semanticMemories.loadMemories(projectId)
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
saveMemories(projectId: string) {
|
|
1337
|
-
return this._semanticMemories.saveMemories(projectId)
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
createMemory(
|
|
1341
|
-
projectId: string,
|
|
1342
|
-
options: { title: string; content: string; tags?: string[]; userTriggered?: boolean }
|
|
1343
|
-
): Promise<string> {
|
|
1344
|
-
return this._semanticMemories.createMemory(projectId, options)
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
updateMemory(
|
|
1348
|
-
projectId: string,
|
|
1349
|
-
memoryId: string,
|
|
1350
|
-
updates: { title?: string; content?: string; tags?: string[] }
|
|
1351
|
-
): Promise<boolean> {
|
|
1352
|
-
return this._semanticMemories.updateMemory(projectId, memoryId, updates)
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
deleteMemory(projectId: string, memoryId: string): Promise<boolean> {
|
|
1356
|
-
return this._semanticMemories.deleteMemory(projectId, memoryId)
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
findByTags(projectId: string, tags: string[], matchAll?: boolean): Promise<Memory[]> {
|
|
1360
|
-
return this._semanticMemories.findByTags(projectId, tags, matchAll)
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
searchMemories(projectId: string, query: string): Promise<Memory[]> {
|
|
1364
|
-
return this._semanticMemories.searchMemories(projectId, query)
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
getRelevantMemories(
|
|
1368
|
-
projectId: string,
|
|
1369
|
-
context: MemoryContext,
|
|
1370
|
-
limit?: number
|
|
1371
|
-
): Promise<Memory[]> {
|
|
1372
|
-
return this._semanticMemories.getRelevantMemories(projectId, context, limit)
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
autoRemember(
|
|
1376
|
-
projectId: string,
|
|
1377
|
-
decisionType: string,
|
|
1378
|
-
value: string,
|
|
1379
|
-
context?: string
|
|
1380
|
-
): Promise<void> {
|
|
1381
|
-
return this._semanticMemories.autoRemember(projectId, decisionType, value, context)
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
getAllMemories(projectId: string): Promise<Memory[]> {
|
|
1385
|
-
return this._semanticMemories.getAllMemories(projectId)
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
getMemoryStats(projectId: string) {
|
|
1389
|
-
return this._semanticMemories.getMemoryStats(projectId)
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
/**
|
|
1393
|
-
* Get relevant memories with domain-based filtering and metrics.
|
|
1394
|
-
* Implements selective memory retrieval based on task relevance.
|
|
1395
|
-
* @see PRJ-107
|
|
1396
|
-
*/
|
|
1397
|
-
getRelevantMemoriesWithMetrics(
|
|
1398
|
-
projectId: string,
|
|
1399
|
-
query: RelevantMemoryQuery
|
|
1400
|
-
): Promise<MemoryRetrievalResult> {
|
|
1401
|
-
return this._semanticMemories.getRelevantMemoriesWithMetrics(projectId, query)
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
// ===========================================================================
|
|
1405
|
-
// TIER 1: Session Memory
|
|
1406
|
-
// ===========================================================================
|
|
1407
|
-
|
|
1408
|
-
setSession(key: string, value: unknown): void {
|
|
1409
|
-
this._sessionStore.setSession(key, value)
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
getSession(key: string): unknown {
|
|
1413
|
-
return this._sessionStore.getSession(key)
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
clearSession(): void {
|
|
1417
|
-
this._sessionStore.clearSession()
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
// ===========================================================================
|
|
1421
|
-
// TIER 2: Patterns
|
|
1422
|
-
// ===========================================================================
|
|
1423
|
-
|
|
1424
|
-
loadPatterns(projectId: string) {
|
|
1425
|
-
return this._patternStore.loadPatterns(projectId)
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
savePatterns(projectId: string) {
|
|
1429
|
-
return this._patternStore.savePatterns(projectId)
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
recordDecision(projectId: string, key: string, value: string, context?: string): Promise<void> {
|
|
1433
|
-
return this._patternStore.recordDecision(projectId, key, value, context)
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
getDecision(
|
|
1437
|
-
projectId: string,
|
|
1438
|
-
key: string
|
|
1439
|
-
): Promise<{ value: string; confidence: string } | null> {
|
|
1440
|
-
return this._patternStore.getDecision(projectId, key)
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
hasPattern(projectId: string, key: string): Promise<boolean> {
|
|
1444
|
-
return this._patternStore.hasPattern(projectId, key)
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
recordWorkflow(
|
|
1448
|
-
projectId: string,
|
|
1449
|
-
workflowName: string,
|
|
1450
|
-
pattern: Record<string, unknown>
|
|
1451
|
-
): Promise<void> {
|
|
1452
|
-
return this._patternStore.recordWorkflow(projectId, workflowName, pattern)
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
getWorkflow(projectId: string, workflowName: string): Promise<Workflow | null> {
|
|
1456
|
-
return this._patternStore.getWorkflow(projectId, workflowName)
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
setPreference(
|
|
1460
|
-
projectId: string,
|
|
1461
|
-
key: string,
|
|
1462
|
-
value: Preference['value'],
|
|
1463
|
-
options?: { userConfirmed?: boolean }
|
|
1464
|
-
): Promise<void> {
|
|
1465
|
-
return this._patternStore.setPreference(projectId, key, value, options)
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
getPreference(projectId: string, key: string, defaultValue?: unknown): Promise<unknown> {
|
|
1469
|
-
return this._patternStore.getPreference(projectId, key, defaultValue)
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
confirmPreference(projectId: string, key: string): Promise<boolean> {
|
|
1473
|
-
return this._patternStore.confirmPreference(projectId, key)
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
confirmDecision(projectId: string, key: string): Promise<boolean> {
|
|
1477
|
-
return this._patternStore.confirmDecision(projectId, key)
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
confirmWorkflow(projectId: string, workflowName: string): Promise<boolean> {
|
|
1481
|
-
return this._patternStore.confirmWorkflow(projectId, workflowName)
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
getPatternsSummary(projectId: string) {
|
|
1485
|
-
return this._patternStore.getPatternsSummary(projectId)
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
archiveStaleDecisions(projectId: string): Promise<number> {
|
|
1489
|
-
return this._patternStore.archiveStaleDecisions(projectId)
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
// ===========================================================================
|
|
1493
|
-
// TIER 3: History
|
|
1494
|
-
// ===========================================================================
|
|
1495
|
-
|
|
1496
|
-
appendHistory(
|
|
1497
|
-
projectId: string,
|
|
1498
|
-
entry: Record<string, unknown> & { type: HistoryEventType }
|
|
1499
|
-
): Promise<void> {
|
|
1500
|
-
return this._historyStore.appendHistory(projectId, entry)
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
getRecentHistory(projectId: string, limit?: number) {
|
|
1504
|
-
return this._historyStore.getRecentHistory(projectId, limit)
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// ===========================================================================
|
|
1508
|
-
// CONVENIENCE: Combined operations
|
|
1509
|
-
// ===========================================================================
|
|
1510
|
-
|
|
1511
|
-
async getSmartDecision(projectId: string, key: string): Promise<string | null> {
|
|
1512
|
-
const sessionValue = this.getSession(`decision:${key}`)
|
|
1513
|
-
if (sessionValue !== undefined) return sessionValue as string
|
|
1514
|
-
|
|
1515
|
-
const pattern = await this.getDecision(projectId, key)
|
|
1516
|
-
if (pattern) return pattern.value
|
|
1517
|
-
|
|
1518
|
-
return null
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
async learnDecision(
|
|
1522
|
-
projectId: string,
|
|
1523
|
-
key: string,
|
|
1524
|
-
value: string,
|
|
1525
|
-
context: string = ''
|
|
1526
|
-
): Promise<void> {
|
|
1527
|
-
this.setSession(`decision:${key}`, value)
|
|
1528
|
-
await this.recordDecision(projectId, key, value, context)
|
|
1529
|
-
await this.appendHistory(projectId, { type: 'decision', key, value, context })
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
/**
|
|
1533
|
-
* Reset internal state (for testing)
|
|
1534
|
-
*/
|
|
1535
|
-
resetState(): void {
|
|
1536
|
-
this._sessionStore.clearSession()
|
|
1537
|
-
this._semanticMemories.reset()
|
|
1538
|
-
this._patternStore.reset()
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
// =============================================================================
|
|
1543
|
-
// Default Export
|
|
1544
|
-
// =============================================================================
|
|
1545
|
-
|
|
1546
|
-
const memorySystem = new MemorySystem()
|
|
1547
|
-
export default memorySystem
|