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
package/core/domain/bm25.ts
DELETED
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BM25 Text Search Index
|
|
3
|
-
*
|
|
4
|
-
* Implements the Okapi BM25 ranking algorithm for file relevance scoring.
|
|
5
|
-
* Indexes files by extracting tokens from:
|
|
6
|
-
* - Function names, class names, export names
|
|
7
|
-
* - Import paths
|
|
8
|
-
* - Comments and JSDoc
|
|
9
|
-
* - File path segments
|
|
10
|
-
*
|
|
11
|
-
* Zero API calls — pure math on filesystem data.
|
|
12
|
-
*
|
|
13
|
-
* @module domain/bm25
|
|
14
|
-
* @version 1.0.0
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import fs from 'node:fs/promises'
|
|
18
|
-
import path from 'node:path'
|
|
19
|
-
import prjctDb from '../storage/database'
|
|
20
|
-
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Types
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
export interface BM25Document {
|
|
26
|
-
path: string
|
|
27
|
-
tokens: string[]
|
|
28
|
-
length: number
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface BM25Index {
|
|
32
|
-
/** Map of file path → token list and document length */
|
|
33
|
-
documents: Record<string, { tokens: string[]; length: number }>
|
|
34
|
-
/** Inverted index: token → list of (path, term frequency) */
|
|
35
|
-
invertedIndex: Record<string, Array<{ path: string; tf: number }>>
|
|
36
|
-
/** Average document length across all documents */
|
|
37
|
-
avgDocLength: number
|
|
38
|
-
/** Total number of indexed documents */
|
|
39
|
-
totalDocs: number
|
|
40
|
-
/** Build timestamp */
|
|
41
|
-
builtAt: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface BM25Score {
|
|
45
|
-
path: string
|
|
46
|
-
score: number
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// =============================================================================
|
|
50
|
-
// Constants
|
|
51
|
-
// =============================================================================
|
|
52
|
-
|
|
53
|
-
/** BM25 tuning: term frequency saturation */
|
|
54
|
-
const K1 = 1.2
|
|
55
|
-
/** BM25 tuning: document length normalization */
|
|
56
|
-
const B = 0.75
|
|
57
|
-
|
|
58
|
-
/** File extensions to index */
|
|
59
|
-
const INDEXABLE_EXTENSIONS = new Set([
|
|
60
|
-
'.ts',
|
|
61
|
-
'.tsx',
|
|
62
|
-
'.js',
|
|
63
|
-
'.jsx',
|
|
64
|
-
'.mjs',
|
|
65
|
-
'.cjs',
|
|
66
|
-
'.py',
|
|
67
|
-
'.go',
|
|
68
|
-
'.rs',
|
|
69
|
-
'.java',
|
|
70
|
-
'.cs',
|
|
71
|
-
'.rb',
|
|
72
|
-
'.php',
|
|
73
|
-
'.vue',
|
|
74
|
-
'.svelte',
|
|
75
|
-
])
|
|
76
|
-
|
|
77
|
-
/** Common stop words to exclude from indexing */
|
|
78
|
-
const STOP_WORDS = new Set([
|
|
79
|
-
'the',
|
|
80
|
-
'a',
|
|
81
|
-
'an',
|
|
82
|
-
'is',
|
|
83
|
-
'are',
|
|
84
|
-
'was',
|
|
85
|
-
'were',
|
|
86
|
-
'be',
|
|
87
|
-
'been',
|
|
88
|
-
'being',
|
|
89
|
-
'have',
|
|
90
|
-
'has',
|
|
91
|
-
'had',
|
|
92
|
-
'do',
|
|
93
|
-
'does',
|
|
94
|
-
'did',
|
|
95
|
-
'will',
|
|
96
|
-
'would',
|
|
97
|
-
'could',
|
|
98
|
-
'should',
|
|
99
|
-
'may',
|
|
100
|
-
'might',
|
|
101
|
-
'shall',
|
|
102
|
-
'can',
|
|
103
|
-
'of',
|
|
104
|
-
'in',
|
|
105
|
-
'to',
|
|
106
|
-
'for',
|
|
107
|
-
'with',
|
|
108
|
-
'on',
|
|
109
|
-
'at',
|
|
110
|
-
'from',
|
|
111
|
-
'by',
|
|
112
|
-
'as',
|
|
113
|
-
'or',
|
|
114
|
-
'and',
|
|
115
|
-
'but',
|
|
116
|
-
'if',
|
|
117
|
-
'not',
|
|
118
|
-
'no',
|
|
119
|
-
'so',
|
|
120
|
-
'up',
|
|
121
|
-
'out',
|
|
122
|
-
'this',
|
|
123
|
-
'that',
|
|
124
|
-
'it',
|
|
125
|
-
'its',
|
|
126
|
-
'all',
|
|
127
|
-
'any',
|
|
128
|
-
// Code noise
|
|
129
|
-
'import',
|
|
130
|
-
'export',
|
|
131
|
-
'default',
|
|
132
|
-
'const',
|
|
133
|
-
'let',
|
|
134
|
-
'var',
|
|
135
|
-
'function',
|
|
136
|
-
'class',
|
|
137
|
-
'interface',
|
|
138
|
-
'type',
|
|
139
|
-
'return',
|
|
140
|
-
'new',
|
|
141
|
-
'true',
|
|
142
|
-
'false',
|
|
143
|
-
'null',
|
|
144
|
-
'undefined',
|
|
145
|
-
'void',
|
|
146
|
-
'async',
|
|
147
|
-
'await',
|
|
148
|
-
'static',
|
|
149
|
-
'public',
|
|
150
|
-
'private',
|
|
151
|
-
'protected',
|
|
152
|
-
'readonly',
|
|
153
|
-
'string',
|
|
154
|
-
'number',
|
|
155
|
-
'boolean',
|
|
156
|
-
'object',
|
|
157
|
-
'array',
|
|
158
|
-
])
|
|
159
|
-
|
|
160
|
-
/** Directories to skip during indexing */
|
|
161
|
-
const SKIP_DIRS = new Set([
|
|
162
|
-
'node_modules',
|
|
163
|
-
'.git',
|
|
164
|
-
'dist',
|
|
165
|
-
'build',
|
|
166
|
-
'out',
|
|
167
|
-
'.next',
|
|
168
|
-
'coverage',
|
|
169
|
-
'.cache',
|
|
170
|
-
'.turbo',
|
|
171
|
-
'.vercel',
|
|
172
|
-
'__pycache__',
|
|
173
|
-
'vendor',
|
|
174
|
-
'target',
|
|
175
|
-
])
|
|
176
|
-
|
|
177
|
-
// =============================================================================
|
|
178
|
-
// Tokenization
|
|
179
|
-
// =============================================================================
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Split camelCase/PascalCase identifiers into words.
|
|
183
|
-
* e.g., "getUserById" → ["get", "user", "by", "id"]
|
|
184
|
-
*/
|
|
185
|
-
function splitIdentifier(identifier: string): string[] {
|
|
186
|
-
return identifier
|
|
187
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
188
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
189
|
-
.replace(/[-_./]/g, ' ')
|
|
190
|
-
.toLowerCase()
|
|
191
|
-
.split(/\s+/)
|
|
192
|
-
.filter((w) => w.length > 1)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Extract tokens from a file's content and path.
|
|
197
|
-
*
|
|
198
|
-
* Extracts:
|
|
199
|
-
* - Path segments (e.g., "core/domain/bm25.ts" → ["core", "domain", "bm25"])
|
|
200
|
-
* - Function/class/interface/type names (split camelCase)
|
|
201
|
-
* - Import sources (split on / and -)
|
|
202
|
-
* - Single-line and multi-line comments
|
|
203
|
-
* - JSDoc content
|
|
204
|
-
*/
|
|
205
|
-
export function tokenizeFile(content: string, filePath: string): string[] {
|
|
206
|
-
const tokens: string[] = []
|
|
207
|
-
|
|
208
|
-
// 1. Path segments (weighted: appear in every query match)
|
|
209
|
-
const pathParts = filePath
|
|
210
|
-
.replace(/\.[^.]+$/, '') // remove extension
|
|
211
|
-
.split(/[/\\]/)
|
|
212
|
-
.filter(Boolean)
|
|
213
|
-
for (const part of pathParts) {
|
|
214
|
-
tokens.push(...splitIdentifier(part))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 2. Export names: export function/class/interface/type/const
|
|
218
|
-
const exportPatterns = [
|
|
219
|
-
/export\s+(?:async\s+)?function\s+(\w+)/g,
|
|
220
|
-
/export\s+class\s+(\w+)/g,
|
|
221
|
-
/export\s+interface\s+(\w+)/g,
|
|
222
|
-
/export\s+type\s+(\w+)/g,
|
|
223
|
-
/export\s+(?:const|let|var)\s+(\w+)/g,
|
|
224
|
-
/export\s+default\s+(?:class|function)\s+(\w+)/g,
|
|
225
|
-
]
|
|
226
|
-
|
|
227
|
-
for (const pattern of exportPatterns) {
|
|
228
|
-
let match: RegExpExecArray | null
|
|
229
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
230
|
-
if (match[1]) {
|
|
231
|
-
tokens.push(...splitIdentifier(match[1]))
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 3. Non-exported function/class/interface names
|
|
237
|
-
const declPatterns = [
|
|
238
|
-
/(?:async\s+)?function\s+(\w+)/g,
|
|
239
|
-
/class\s+(\w+)/g,
|
|
240
|
-
/interface\s+(\w+)/g,
|
|
241
|
-
/type\s+(\w+)\s*=/g,
|
|
242
|
-
]
|
|
243
|
-
|
|
244
|
-
for (const pattern of declPatterns) {
|
|
245
|
-
let match: RegExpExecArray | null
|
|
246
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
247
|
-
if (match[1]) {
|
|
248
|
-
tokens.push(...splitIdentifier(match[1]))
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 4. Import sources
|
|
254
|
-
const importPattern = /(?:from|import)\s+['"]([^'"]+)['"]/g
|
|
255
|
-
let importMatch: RegExpExecArray | null
|
|
256
|
-
while ((importMatch = importPattern.exec(content)) !== null) {
|
|
257
|
-
const source = importMatch[1]
|
|
258
|
-
if (source.startsWith('.') || source.startsWith('@/')) {
|
|
259
|
-
// Internal import — extract path tokens
|
|
260
|
-
tokens.push(...splitIdentifier(source))
|
|
261
|
-
} else {
|
|
262
|
-
// External package — use package name
|
|
263
|
-
const pkgName = source.startsWith('@')
|
|
264
|
-
? source.split('/').slice(0, 2).join('/')
|
|
265
|
-
: source.split('/')[0]
|
|
266
|
-
tokens.push(...splitIdentifier(pkgName))
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// 5. Comments (single-line)
|
|
271
|
-
const singleLineComments = /\/\/\s*(.+)/g
|
|
272
|
-
let commentMatch: RegExpExecArray | null
|
|
273
|
-
while ((commentMatch = singleLineComments.exec(content)) !== null) {
|
|
274
|
-
const words = commentMatch[1]
|
|
275
|
-
.toLowerCase()
|
|
276
|
-
.split(/\s+/)
|
|
277
|
-
.filter((w) => w.length > 2)
|
|
278
|
-
tokens.push(...words)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// 6. JSDoc / multi-line comments — extract meaningful words
|
|
282
|
-
const multiLineComments = /\/\*\*?([\s\S]*?)\*\//g
|
|
283
|
-
let multiMatch: RegExpExecArray | null
|
|
284
|
-
while ((multiMatch = multiLineComments.exec(content)) !== null) {
|
|
285
|
-
const words = multiMatch[1]
|
|
286
|
-
.replace(/@\w+/g, '') // strip JSDoc tags
|
|
287
|
-
.replace(/\*/g, '')
|
|
288
|
-
.toLowerCase()
|
|
289
|
-
.split(/\s+/)
|
|
290
|
-
.filter((w) => w.length > 2 && /^[a-z]+$/.test(w))
|
|
291
|
-
tokens.push(...words)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Filter: remove stop words, short tokens, non-alpha
|
|
295
|
-
return tokens.filter((t) => t.length > 1 && !STOP_WORDS.has(t) && /^[a-z][a-z0-9]*$/.test(t))
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Tokenize a query string (task description).
|
|
300
|
-
*/
|
|
301
|
-
export function tokenizeQuery(query: string): string[] {
|
|
302
|
-
return query
|
|
303
|
-
.split(/\s+/)
|
|
304
|
-
.flatMap((word) => splitIdentifier(word))
|
|
305
|
-
.filter((t) => t.length > 1 && !STOP_WORDS.has(t) && /^[a-z][a-z0-9]*$/.test(t))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// =============================================================================
|
|
309
|
-
// Index Building
|
|
310
|
-
// =============================================================================
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Recursively list all indexable files in a project.
|
|
314
|
-
*/
|
|
315
|
-
async function listFiles(dir: string, projectPath: string): Promise<string[]> {
|
|
316
|
-
const files: string[] = []
|
|
317
|
-
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
318
|
-
|
|
319
|
-
for (const entry of entries) {
|
|
320
|
-
if (SKIP_DIRS.has(entry.name)) continue
|
|
321
|
-
|
|
322
|
-
const fullPath = path.join(dir, entry.name)
|
|
323
|
-
if (entry.isDirectory()) {
|
|
324
|
-
files.push(...(await listFiles(fullPath, projectPath)))
|
|
325
|
-
} else if (entry.isFile()) {
|
|
326
|
-
const ext = path.extname(entry.name).toLowerCase()
|
|
327
|
-
if (INDEXABLE_EXTENSIONS.has(ext)) {
|
|
328
|
-
files.push(path.relative(projectPath, fullPath))
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return files
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Build a BM25 index for all files in a project.
|
|
338
|
-
*
|
|
339
|
-
* Performance target: <5 seconds for 500-file project.
|
|
340
|
-
*/
|
|
341
|
-
export async function buildIndex(projectPath: string): Promise<BM25Index> {
|
|
342
|
-
const files = await listFiles(projectPath, projectPath)
|
|
343
|
-
|
|
344
|
-
const documents: BM25Index['documents'] = {}
|
|
345
|
-
const invertedIndex: BM25Index['invertedIndex'] = {}
|
|
346
|
-
let totalLength = 0
|
|
347
|
-
|
|
348
|
-
// Process files in parallel batches of 50
|
|
349
|
-
const BATCH_SIZE = 50
|
|
350
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
351
|
-
const batch = files.slice(i, i + BATCH_SIZE)
|
|
352
|
-
const results = await Promise.all(
|
|
353
|
-
batch.map(async (filePath) => {
|
|
354
|
-
try {
|
|
355
|
-
const content = await fs.readFile(path.join(projectPath, filePath), 'utf-8')
|
|
356
|
-
const tokens = tokenizeFile(content, filePath)
|
|
357
|
-
return { filePath, tokens }
|
|
358
|
-
} catch {
|
|
359
|
-
return { filePath, tokens: [] as string[] }
|
|
360
|
-
}
|
|
361
|
-
})
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
for (const { filePath, tokens } of results) {
|
|
365
|
-
if (tokens.length === 0) continue
|
|
366
|
-
|
|
367
|
-
documents[filePath] = { tokens, length: tokens.length }
|
|
368
|
-
totalLength += tokens.length
|
|
369
|
-
|
|
370
|
-
// Build term frequency map for this document
|
|
371
|
-
const tfMap = new Map<string, number>()
|
|
372
|
-
for (const token of tokens) {
|
|
373
|
-
tfMap.set(token, (tfMap.get(token) || 0) + 1)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Add to inverted index
|
|
377
|
-
for (const [token, tf] of tfMap) {
|
|
378
|
-
if (!invertedIndex[token]) {
|
|
379
|
-
invertedIndex[token] = []
|
|
380
|
-
}
|
|
381
|
-
invertedIndex[token].push({ path: filePath, tf })
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const totalDocs = Object.keys(documents).length
|
|
387
|
-
|
|
388
|
-
return {
|
|
389
|
-
documents,
|
|
390
|
-
invertedIndex,
|
|
391
|
-
avgDocLength: totalDocs > 0 ? totalLength / totalDocs : 0,
|
|
392
|
-
totalDocs,
|
|
393
|
-
builtAt: new Date().toISOString(),
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// =============================================================================
|
|
398
|
-
// BM25 Scoring
|
|
399
|
-
// =============================================================================
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Calculate IDF (Inverse Document Frequency) for a term.
|
|
403
|
-
*/
|
|
404
|
-
function idf(docFrequency: number, totalDocs: number): number {
|
|
405
|
-
return Math.log((totalDocs - docFrequency + 0.5) / (docFrequency + 0.5) + 1)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Score all documents against a query using BM25.
|
|
410
|
-
*
|
|
411
|
-
* Performance target: <50ms per query.
|
|
412
|
-
*
|
|
413
|
-
* @returns Sorted array of (path, score) tuples, highest score first.
|
|
414
|
-
*/
|
|
415
|
-
export function score(query: string, index: BM25Index): BM25Score[] {
|
|
416
|
-
const queryTokens = tokenizeQuery(query)
|
|
417
|
-
if (queryTokens.length === 0) return []
|
|
418
|
-
|
|
419
|
-
const scores = new Map<string, number>()
|
|
420
|
-
|
|
421
|
-
for (const token of queryTokens) {
|
|
422
|
-
const postings = index.invertedIndex[token]
|
|
423
|
-
if (!postings) continue
|
|
424
|
-
|
|
425
|
-
const tokenIdf = idf(postings.length, index.totalDocs)
|
|
426
|
-
|
|
427
|
-
for (const { path: docPath, tf } of postings) {
|
|
428
|
-
const doc = index.documents[docPath]
|
|
429
|
-
if (!doc) continue
|
|
430
|
-
|
|
431
|
-
// BM25 term score
|
|
432
|
-
const numerator = tf * (K1 + 1)
|
|
433
|
-
const denominator = tf + K1 * (1 - B + B * (doc.length / index.avgDocLength))
|
|
434
|
-
const termScore = tokenIdf * (numerator / denominator)
|
|
435
|
-
|
|
436
|
-
scores.set(docPath, (scores.get(docPath) || 0) + termScore)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Sort by score descending
|
|
441
|
-
return Array.from(scores.entries())
|
|
442
|
-
.map(([p, s]) => ({ path: p, score: s }))
|
|
443
|
-
.sort((a, b) => b.score - a.score)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// =============================================================================
|
|
447
|
-
// SQLite Persistence
|
|
448
|
-
// =============================================================================
|
|
449
|
-
|
|
450
|
-
const INDEX_KEY = 'bm25-index'
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Save a BM25 index to SQLite.
|
|
454
|
-
*/
|
|
455
|
-
export function saveIndex(projectId: string, index: BM25Index): void {
|
|
456
|
-
// Store only the inverted index + metadata (not raw tokens, to save space)
|
|
457
|
-
const storable = {
|
|
458
|
-
invertedIndex: index.invertedIndex,
|
|
459
|
-
avgDocLength: index.avgDocLength,
|
|
460
|
-
totalDocs: index.totalDocs,
|
|
461
|
-
builtAt: index.builtAt,
|
|
462
|
-
// Store document lengths (needed for scoring) but not full token lists
|
|
463
|
-
docLengths: Object.fromEntries(Object.entries(index.documents).map(([p, d]) => [p, d.length])),
|
|
464
|
-
}
|
|
465
|
-
prjctDb.setDoc(projectId, INDEX_KEY, storable)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Load a BM25 index from SQLite.
|
|
470
|
-
* Returns null if no index exists.
|
|
471
|
-
*/
|
|
472
|
-
export function loadIndex(projectId: string): BM25Index | null {
|
|
473
|
-
const stored = prjctDb.getDoc<{
|
|
474
|
-
invertedIndex: BM25Index['invertedIndex']
|
|
475
|
-
avgDocLength: number
|
|
476
|
-
totalDocs: number
|
|
477
|
-
builtAt: string
|
|
478
|
-
docLengths: Record<string, number>
|
|
479
|
-
}>(projectId, INDEX_KEY)
|
|
480
|
-
|
|
481
|
-
if (!stored) return null
|
|
482
|
-
|
|
483
|
-
// Reconstruct documents map with lengths only (tokens not needed for scoring)
|
|
484
|
-
const documents: BM25Index['documents'] = {}
|
|
485
|
-
for (const [p, length] of Object.entries(stored.docLengths)) {
|
|
486
|
-
documents[p] = { tokens: [], length }
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
documents,
|
|
491
|
-
invertedIndex: stored.invertedIndex,
|
|
492
|
-
avgDocLength: stored.avgDocLength,
|
|
493
|
-
totalDocs: stored.totalDocs,
|
|
494
|
-
builtAt: stored.builtAt,
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// =============================================================================
|
|
499
|
-
// High-level API
|
|
500
|
-
// =============================================================================
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Build and persist a BM25 index for a project.
|
|
504
|
-
*/
|
|
505
|
-
export async function indexProject(projectPath: string, projectId: string): Promise<BM25Index> {
|
|
506
|
-
const index = await buildIndex(projectPath)
|
|
507
|
-
saveIndex(projectId, index)
|
|
508
|
-
return index
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Query files by relevance to a task description.
|
|
513
|
-
* Loads index from SQLite, scores against query, returns top N.
|
|
514
|
-
*
|
|
515
|
-
* @param projectId - Project ID for SQLite lookup
|
|
516
|
-
* @param query - Task description or search query
|
|
517
|
-
* @param topN - Maximum number of results (default: 15)
|
|
518
|
-
* @returns Sorted array of (path, score) — empty if no index exists
|
|
519
|
-
*/
|
|
520
|
-
export function queryFiles(projectId: string, query: string, topN = 15): BM25Score[] {
|
|
521
|
-
const index = loadIndex(projectId)
|
|
522
|
-
if (!index) return []
|
|
523
|
-
|
|
524
|
-
return score(query, index).slice(0, topN)
|
|
525
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Change Propagator — Import-Based Change Detection
|
|
3
|
-
*
|
|
4
|
-
* When a file changes, files that import it may also need re-analysis.
|
|
5
|
-
* Uses the import graph (PRJ-304) to propagate changes 1 level deep
|
|
6
|
-
* through the reverse dependency chain.
|
|
7
|
-
*
|
|
8
|
-
* Example: If `auth.ts` changes, and `user-service.ts` imports `auth.ts`,
|
|
9
|
-
* then `user-service.ts` is also marked as "affected" even though its
|
|
10
|
-
* content hash didn't change.
|
|
11
|
-
*
|
|
12
|
-
* @module domain/change-propagator
|
|
13
|
-
* @version 1.0.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { FileDiff } from './file-hasher'
|
|
17
|
-
import { loadGraph } from './import-graph'
|
|
18
|
-
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Types
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
export interface PropagatedChanges {
|
|
24
|
-
/** Files that changed directly (added + modified from hash diff) */
|
|
25
|
-
directlyChanged: string[]
|
|
26
|
-
/** Files that import a directly changed file (1 level deep) */
|
|
27
|
-
affectedByImports: string[]
|
|
28
|
-
/** Union of directlyChanged + affectedByImports (deduplicated) */
|
|
29
|
-
allAffected: string[]
|
|
30
|
-
/** Files that were deleted */
|
|
31
|
-
deleted: string[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// =============================================================================
|
|
35
|
-
// Propagation
|
|
36
|
-
// =============================================================================
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Given a file diff, propagate changes through the import graph.
|
|
40
|
-
*
|
|
41
|
-
* For each changed file, find all files that import it (reverse edges)
|
|
42
|
-
* at depth 1. These "affected" files should be re-analyzed because
|
|
43
|
-
* their imports have changed behavior.
|
|
44
|
-
*
|
|
45
|
-
* @param diff - The raw file diff from hash comparison
|
|
46
|
-
* @param projectId - Project ID for loading the import graph
|
|
47
|
-
* @returns Propagated changes including affected importers
|
|
48
|
-
*/
|
|
49
|
-
export function propagateChanges(diff: FileDiff, projectId: string): PropagatedChanges {
|
|
50
|
-
const directlyChanged = [...diff.added, ...diff.modified]
|
|
51
|
-
const directSet = new Set(directlyChanged)
|
|
52
|
-
const affected = new Set<string>()
|
|
53
|
-
|
|
54
|
-
// Try to load import graph for reverse-edge lookup
|
|
55
|
-
const graph = loadGraph(projectId)
|
|
56
|
-
|
|
57
|
-
if (graph) {
|
|
58
|
-
// For each directly changed file, find its reverse edges (files that import it)
|
|
59
|
-
for (const changedFile of directlyChanged) {
|
|
60
|
-
const importers = graph.reverse[changedFile]
|
|
61
|
-
if (importers) {
|
|
62
|
-
for (const importer of importers) {
|
|
63
|
-
// Only add if not already directly changed
|
|
64
|
-
if (!directSet.has(importer)) {
|
|
65
|
-
affected.add(importer)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const affectedByImports = Array.from(affected)
|
|
73
|
-
const allAffected = [...directlyChanged, ...affectedByImports]
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
directlyChanged,
|
|
77
|
-
affectedByImports,
|
|
78
|
-
deleted: diff.deleted,
|
|
79
|
-
allAffected,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Determine which domain agents need regeneration based on changed files.
|
|
85
|
-
*
|
|
86
|
-
* Maps file extensions and paths to domains:
|
|
87
|
-
* - .tsx/.jsx/.css/.scss/.html/.vue/.svelte → frontend
|
|
88
|
-
* - .ts/.js (non-test, non-config) → backend
|
|
89
|
-
* - .test.ts/.spec.ts → testing
|
|
90
|
-
* - Dockerfile/.dockerignore → devops
|
|
91
|
-
* - .sql/prisma/drizzle → database
|
|
92
|
-
*
|
|
93
|
-
* Returns the set of domain names that have affected files.
|
|
94
|
-
*/
|
|
95
|
-
export function affectedDomains(changedFiles: string[]): Set<string> {
|
|
96
|
-
const domains = new Set<string>()
|
|
97
|
-
|
|
98
|
-
for (const file of changedFiles) {
|
|
99
|
-
const lower = file.toLowerCase()
|
|
100
|
-
|
|
101
|
-
// Frontend indicators
|
|
102
|
-
if (
|
|
103
|
-
lower.endsWith('.tsx') ||
|
|
104
|
-
lower.endsWith('.jsx') ||
|
|
105
|
-
lower.endsWith('.css') ||
|
|
106
|
-
lower.endsWith('.scss') ||
|
|
107
|
-
lower.endsWith('.vue') ||
|
|
108
|
-
lower.endsWith('.svelte') ||
|
|
109
|
-
lower.includes('/components/') ||
|
|
110
|
-
lower.includes('/pages/') ||
|
|
111
|
-
lower.includes('/app/')
|
|
112
|
-
) {
|
|
113
|
-
domains.add('frontend')
|
|
114
|
-
domains.add('uxui')
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Testing indicators
|
|
118
|
-
if (
|
|
119
|
-
lower.includes('.test.') ||
|
|
120
|
-
lower.includes('.spec.') ||
|
|
121
|
-
lower.includes('__tests__') ||
|
|
122
|
-
lower.includes('/test/')
|
|
123
|
-
) {
|
|
124
|
-
domains.add('testing')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// DevOps indicators
|
|
128
|
-
if (
|
|
129
|
-
lower.includes('dockerfile') ||
|
|
130
|
-
lower.includes('docker-compose') ||
|
|
131
|
-
lower.includes('.dockerignore') ||
|
|
132
|
-
lower.includes('.github/') ||
|
|
133
|
-
lower.includes('ci/') ||
|
|
134
|
-
lower.includes('cd/')
|
|
135
|
-
) {
|
|
136
|
-
domains.add('devops')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Database indicators
|
|
140
|
-
if (
|
|
141
|
-
lower.endsWith('.sql') ||
|
|
142
|
-
lower.includes('prisma') ||
|
|
143
|
-
lower.includes('drizzle') ||
|
|
144
|
-
lower.includes('migration') ||
|
|
145
|
-
lower.includes('/db/')
|
|
146
|
-
) {
|
|
147
|
-
domains.add('database')
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Backend indicators (TypeScript/JavaScript that isn't clearly frontend/test)
|
|
151
|
-
if (
|
|
152
|
-
(lower.endsWith('.ts') || lower.endsWith('.js')) &&
|
|
153
|
-
!lower.includes('.test.') &&
|
|
154
|
-
!lower.includes('.spec.') &&
|
|
155
|
-
!lower.endsWith('.d.ts')
|
|
156
|
-
) {
|
|
157
|
-
domains.add('backend')
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return domains
|
|
162
|
-
}
|