prjct-cli 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Ranker — Combined Scoring
|
|
3
|
-
*
|
|
4
|
-
* Combines three signals to rank files by relevance to a task:
|
|
5
|
-
* - BM25 text search (0.5 weight)
|
|
6
|
-
* - Import graph proximity (0.3 weight)
|
|
7
|
-
* - Git co-change correlation (0.2 weight)
|
|
8
|
-
*
|
|
9
|
-
* Zero API calls. Pure math on pre-built indexes.
|
|
10
|
-
*
|
|
11
|
-
* @module domain/file-ranker
|
|
12
|
-
* @version 1.0.0
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { loadIndex, queryFiles } from './bm25'
|
|
16
|
-
import { scoreFromSeeds as cochangeScore, loadMatrix } from './git-cochange'
|
|
17
|
-
import { scoreFromSeeds as importScore, loadGraph } from './import-graph'
|
|
18
|
-
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Types
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
export interface RankedFile {
|
|
24
|
-
path: string
|
|
25
|
-
finalScore: number
|
|
26
|
-
signals: {
|
|
27
|
-
bm25: number
|
|
28
|
-
imports: number
|
|
29
|
-
cochange: number
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface RankingConfig {
|
|
34
|
-
/** Weight for BM25 text relevance (default: 0.5) */
|
|
35
|
-
bm25Weight?: number
|
|
36
|
-
/** Weight for import graph proximity (default: 0.3) */
|
|
37
|
-
importWeight?: number
|
|
38
|
-
/** Weight for git co-change correlation (default: 0.2) */
|
|
39
|
-
cochangeWeight?: number
|
|
40
|
-
/** Maximum number of results (default: 15) */
|
|
41
|
-
topN?: number
|
|
42
|
-
/** Maximum depth for import graph traversal (default: 2) */
|
|
43
|
-
importDepth?: number
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const DEFAULT_CONFIG: Required<RankingConfig> = {
|
|
47
|
-
bm25Weight: 0.5,
|
|
48
|
-
importWeight: 0.3,
|
|
49
|
-
cochangeWeight: 0.2,
|
|
50
|
-
topN: 15,
|
|
51
|
-
importDepth: 2,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// =============================================================================
|
|
55
|
-
// Combined Ranking
|
|
56
|
-
// =============================================================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Rank files by combined relevance to a task description.
|
|
60
|
-
*
|
|
61
|
-
* Algorithm:
|
|
62
|
-
* 1. BM25: Score all files against the query
|
|
63
|
-
* 2. Import graph: From top BM25 hits, follow imports 2 levels deep
|
|
64
|
-
* 3. Co-change: From top BM25 hits, find co-changed files
|
|
65
|
-
* 4. Normalize each signal to [0, 1]
|
|
66
|
-
* 5. Combine: finalScore = bm25 * 0.5 + imports * 0.3 + cochange * 0.2
|
|
67
|
-
*
|
|
68
|
-
* Performance target: <50ms per query (all indexes pre-loaded from SQLite).
|
|
69
|
-
*/
|
|
70
|
-
export function rankFiles(
|
|
71
|
-
projectId: string,
|
|
72
|
-
query: string,
|
|
73
|
-
config: RankingConfig = {}
|
|
74
|
-
): RankedFile[] {
|
|
75
|
-
const cfg = { ...DEFAULT_CONFIG, ...config }
|
|
76
|
-
|
|
77
|
-
// 1. BM25 scoring — get broad candidate set
|
|
78
|
-
const bm25Results = queryFiles(projectId, query, cfg.topN * 3) // Get more candidates
|
|
79
|
-
if (bm25Results.length === 0) return []
|
|
80
|
-
|
|
81
|
-
// Normalize BM25 scores to [0, 1]
|
|
82
|
-
const maxBm25 = bm25Results[0]?.score || 1
|
|
83
|
-
const bm25Map = new Map<string, number>()
|
|
84
|
-
for (const result of bm25Results) {
|
|
85
|
-
bm25Map.set(result.path, result.score / maxBm25)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Seed files: top BM25 hits for graph traversal
|
|
89
|
-
const seedFiles = bm25Results.slice(0, 10).map((r) => r.path)
|
|
90
|
-
|
|
91
|
-
// 2. Import graph scoring
|
|
92
|
-
const importMap = new Map<string, number>()
|
|
93
|
-
const graph = loadGraph(projectId)
|
|
94
|
-
if (graph) {
|
|
95
|
-
const importResults = importScore(seedFiles, graph, cfg.importDepth)
|
|
96
|
-
const maxImport = importResults[0]?.score || 1
|
|
97
|
-
for (const result of importResults) {
|
|
98
|
-
importMap.set(result.path, result.score / maxImport)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 3. Co-change scoring
|
|
103
|
-
const cochangeMap = new Map<string, number>()
|
|
104
|
-
const cochangeIndex = loadMatrix(projectId)
|
|
105
|
-
if (cochangeIndex) {
|
|
106
|
-
const cochangeResults = cochangeScore(seedFiles, cochangeIndex)
|
|
107
|
-
const maxCochange = cochangeResults[0]?.score || 1
|
|
108
|
-
for (const result of cochangeResults) {
|
|
109
|
-
cochangeMap.set(result.path, result.score / maxCochange)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 4. Collect all candidate files
|
|
114
|
-
const allFiles = new Set([...bm25Map.keys(), ...importMap.keys(), ...cochangeMap.keys()])
|
|
115
|
-
|
|
116
|
-
// 5. Combined scoring
|
|
117
|
-
const ranked: RankedFile[] = []
|
|
118
|
-
for (const filePath of allFiles) {
|
|
119
|
-
const bm25 = bm25Map.get(filePath) || 0
|
|
120
|
-
const imports = importMap.get(filePath) || 0
|
|
121
|
-
const cochange = cochangeMap.get(filePath) || 0
|
|
122
|
-
|
|
123
|
-
const finalScore =
|
|
124
|
-
bm25 * cfg.bm25Weight + imports * cfg.importWeight + cochange * cfg.cochangeWeight
|
|
125
|
-
|
|
126
|
-
ranked.push({
|
|
127
|
-
path: filePath,
|
|
128
|
-
finalScore,
|
|
129
|
-
signals: { bm25, imports, cochange },
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Sort by finalScore descending, return top N
|
|
134
|
-
ranked.sort((a, b) => b.finalScore - a.finalScore)
|
|
135
|
-
return ranked.slice(0, cfg.topN)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check if all three indexes exist for a project.
|
|
140
|
-
*/
|
|
141
|
-
export function hasIndexes(projectId: string): {
|
|
142
|
-
bm25: boolean
|
|
143
|
-
imports: boolean
|
|
144
|
-
cochange: boolean
|
|
145
|
-
} {
|
|
146
|
-
return {
|
|
147
|
-
bm25: loadIndex(projectId) !== null,
|
|
148
|
-
imports: loadGraph(projectId) !== null,
|
|
149
|
-
cochange: loadMatrix(projectId) !== null,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git Co-Change Analyzer
|
|
3
|
-
*
|
|
4
|
-
* Analyzes git history to find files that frequently change together.
|
|
5
|
-
* If middleware.ts appears in 8 of 10 commits that touch auth.ts,
|
|
6
|
-
* they're a cluster — when one is relevant, include the other.
|
|
7
|
-
*
|
|
8
|
-
* Uses Jaccard similarity: |A ∩ B| / |A ∪ B| for each file pair.
|
|
9
|
-
*
|
|
10
|
-
* Zero API calls — pure math on git log data.
|
|
11
|
-
*
|
|
12
|
-
* @module domain/git-cochange
|
|
13
|
-
* @version 1.0.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { exec as execCallback } from 'node:child_process'
|
|
17
|
-
import { promisify } from 'node:util'
|
|
18
|
-
import prjctDb from '../storage/database'
|
|
19
|
-
|
|
20
|
-
const exec = promisify(execCallback)
|
|
21
|
-
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Types
|
|
24
|
-
// =============================================================================
|
|
25
|
-
|
|
26
|
-
/** Co-change matrix: file → { related_file: similarity_score } */
|
|
27
|
-
export type CoChangeMatrix = Record<string, Record<string, number>>
|
|
28
|
-
|
|
29
|
-
export interface CoChangeIndex {
|
|
30
|
-
/** The co-change similarity matrix */
|
|
31
|
-
matrix: CoChangeMatrix
|
|
32
|
-
/** Number of commits analyzed */
|
|
33
|
-
commitsAnalyzed: number
|
|
34
|
-
/** Total unique files seen */
|
|
35
|
-
filesAnalyzed: number
|
|
36
|
-
/** Build timestamp */
|
|
37
|
-
builtAt: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface CoChangeScore {
|
|
41
|
-
path: string
|
|
42
|
-
score: number
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// =============================================================================
|
|
46
|
-
// Constants
|
|
47
|
-
// =============================================================================
|
|
48
|
-
|
|
49
|
-
/** Minimum Jaccard similarity to include in the matrix */
|
|
50
|
-
const MIN_SIMILARITY = 0.1
|
|
51
|
-
|
|
52
|
-
/** Minimum times a file must appear in commits to be included */
|
|
53
|
-
const MIN_FILE_OCCURRENCES = 2
|
|
54
|
-
|
|
55
|
-
/** Maximum number of files in a single commit to consider (skip merges/bulk) */
|
|
56
|
-
const MAX_FILES_PER_COMMIT = 30
|
|
57
|
-
|
|
58
|
-
// =============================================================================
|
|
59
|
-
// Git Log Parsing
|
|
60
|
-
// =============================================================================
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse git log to extract commit → files mapping.
|
|
64
|
-
*
|
|
65
|
-
* @param projectPath - Project root path
|
|
66
|
-
* @param maxCommits - Maximum number of commits to analyze (default: 100)
|
|
67
|
-
* @returns Array of file sets, one per commit
|
|
68
|
-
*/
|
|
69
|
-
async function parseGitLog(projectPath: string, maxCommits = 100): Promise<Set<string>[]> {
|
|
70
|
-
try {
|
|
71
|
-
const { stdout } = await exec(
|
|
72
|
-
`git log --name-only --pretty=format:'---COMMIT---' -${maxCommits}`,
|
|
73
|
-
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
const commits: Set<string>[] = []
|
|
77
|
-
let currentFiles: Set<string> | null = null
|
|
78
|
-
|
|
79
|
-
for (const line of stdout.split('\n')) {
|
|
80
|
-
const trimmed = line.trim()
|
|
81
|
-
if (trimmed === '---COMMIT---') {
|
|
82
|
-
if (currentFiles && currentFiles.size > 0 && currentFiles.size <= MAX_FILES_PER_COMMIT) {
|
|
83
|
-
commits.push(currentFiles)
|
|
84
|
-
}
|
|
85
|
-
currentFiles = new Set()
|
|
86
|
-
} else if (trimmed && currentFiles) {
|
|
87
|
-
// Only include source files (skip binaries, lockfiles, etc.)
|
|
88
|
-
if (isSourceFile(trimmed)) {
|
|
89
|
-
currentFiles.add(trimmed)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Don't forget the last commit
|
|
95
|
-
if (currentFiles && currentFiles.size > 0 && currentFiles.size <= MAX_FILES_PER_COMMIT) {
|
|
96
|
-
commits.push(currentFiles)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return commits
|
|
100
|
-
} catch {
|
|
101
|
-
return []
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Check if a path looks like a source file worth tracking.
|
|
107
|
-
*/
|
|
108
|
-
function isSourceFile(filePath: string): boolean {
|
|
109
|
-
const sourceExtensions = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|cs|rb|php|vue|svelte)$/i
|
|
110
|
-
return sourceExtensions.test(filePath) && !filePath.includes('node_modules/')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// =============================================================================
|
|
114
|
-
// Co-Change Matrix
|
|
115
|
-
// =============================================================================
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Build a co-change matrix from git history.
|
|
119
|
-
*
|
|
120
|
-
* For each pair of files that appear in the same commit,
|
|
121
|
-
* calculate Jaccard similarity = |commits_both| / |commits_either|.
|
|
122
|
-
*
|
|
123
|
-
* Performance target: <2 seconds for 100 commits.
|
|
124
|
-
*/
|
|
125
|
-
export async function buildMatrix(projectPath: string, maxCommits = 100): Promise<CoChangeIndex> {
|
|
126
|
-
const commitSets = await parseGitLog(projectPath, maxCommits)
|
|
127
|
-
|
|
128
|
-
// Count how many commits each file appears in
|
|
129
|
-
const fileCommitCount = new Map<string, number>()
|
|
130
|
-
// Count how many commits each pair appears in together
|
|
131
|
-
const pairCount = new Map<string, number>()
|
|
132
|
-
|
|
133
|
-
for (const files of commitSets) {
|
|
134
|
-
const fileArray = Array.from(files)
|
|
135
|
-
|
|
136
|
-
for (const file of fileArray) {
|
|
137
|
-
fileCommitCount.set(file, (fileCommitCount.get(file) || 0) + 1)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Count co-occurrences for each pair
|
|
141
|
-
for (let i = 0; i < fileArray.length; i++) {
|
|
142
|
-
for (let j = i + 1; j < fileArray.length; j++) {
|
|
143
|
-
const key = pairKey(fileArray[i], fileArray[j])
|
|
144
|
-
pairCount.set(key, (pairCount.get(key) || 0) + 1)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Build Jaccard similarity matrix
|
|
150
|
-
const matrix: CoChangeMatrix = {}
|
|
151
|
-
|
|
152
|
-
for (const [key, count] of pairCount) {
|
|
153
|
-
const [fileA, fileB] = key.split('\0')
|
|
154
|
-
const countA = fileCommitCount.get(fileA) || 0
|
|
155
|
-
const countB = fileCommitCount.get(fileB) || 0
|
|
156
|
-
|
|
157
|
-
// Skip rare files
|
|
158
|
-
if (countA < MIN_FILE_OCCURRENCES || countB < MIN_FILE_OCCURRENCES) continue
|
|
159
|
-
|
|
160
|
-
// Jaccard similarity
|
|
161
|
-
const unionCount = countA + countB - count
|
|
162
|
-
const similarity = unionCount > 0 ? count / unionCount : 0
|
|
163
|
-
|
|
164
|
-
if (similarity < MIN_SIMILARITY) continue
|
|
165
|
-
|
|
166
|
-
// Store bidirectionally
|
|
167
|
-
if (!matrix[fileA]) matrix[fileA] = {}
|
|
168
|
-
if (!matrix[fileB]) matrix[fileB] = {}
|
|
169
|
-
matrix[fileA][fileB] = similarity
|
|
170
|
-
matrix[fileB][fileA] = similarity
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
matrix,
|
|
175
|
-
commitsAnalyzed: commitSets.length,
|
|
176
|
-
filesAnalyzed: fileCommitCount.size,
|
|
177
|
-
builtAt: new Date().toISOString(),
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Create a canonical pair key (sorted to avoid duplicates) */
|
|
182
|
-
function pairKey(a: string, b: string): string {
|
|
183
|
-
return a < b ? `${a}\0${b}` : `${b}\0${a}`
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// =============================================================================
|
|
187
|
-
// Scoring
|
|
188
|
-
// =============================================================================
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Given a set of seed files, find co-changed files and their scores.
|
|
192
|
-
*
|
|
193
|
-
* @param seedFiles - Files already identified as relevant
|
|
194
|
-
* @param index - The co-change index
|
|
195
|
-
* @returns Scored files NOT in the seed set
|
|
196
|
-
*/
|
|
197
|
-
export function scoreFromSeeds(seedFiles: string[], index: CoChangeIndex): CoChangeScore[] {
|
|
198
|
-
const seedSet = new Set(seedFiles)
|
|
199
|
-
const scores = new Map<string, number>()
|
|
200
|
-
|
|
201
|
-
for (const seed of seedFiles) {
|
|
202
|
-
const related = index.matrix[seed]
|
|
203
|
-
if (!related) continue
|
|
204
|
-
|
|
205
|
-
for (const [file, similarity] of Object.entries(related)) {
|
|
206
|
-
if (seedSet.has(file)) continue
|
|
207
|
-
|
|
208
|
-
// Take the max similarity across all seed connections
|
|
209
|
-
const existing = scores.get(file) || 0
|
|
210
|
-
if (similarity > existing) {
|
|
211
|
-
scores.set(file, similarity)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return Array.from(scores.entries())
|
|
217
|
-
.map(([p, s]) => ({ path: p, score: s }))
|
|
218
|
-
.sort((a, b) => b.score - a.score)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// =============================================================================
|
|
222
|
-
// SQLite Persistence
|
|
223
|
-
// =============================================================================
|
|
224
|
-
|
|
225
|
-
const INDEX_KEY = 'cochange-index'
|
|
226
|
-
|
|
227
|
-
export function saveMatrix(projectId: string, index: CoChangeIndex): void {
|
|
228
|
-
prjctDb.setDoc(projectId, INDEX_KEY, index)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function loadMatrix(projectId: string): CoChangeIndex | null {
|
|
232
|
-
return prjctDb.getDoc<CoChangeIndex>(projectId, INDEX_KEY)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// =============================================================================
|
|
236
|
-
// High-level API
|
|
237
|
-
// =============================================================================
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Build and persist the co-change matrix for a project.
|
|
241
|
-
*/
|
|
242
|
-
export async function indexCoChanges(
|
|
243
|
-
projectPath: string,
|
|
244
|
-
projectId: string,
|
|
245
|
-
maxCommits = 100
|
|
246
|
-
): Promise<CoChangeIndex> {
|
|
247
|
-
const index = await buildMatrix(projectPath, maxCommits)
|
|
248
|
-
saveMatrix(projectId, index)
|
|
249
|
-
return index
|
|
250
|
-
}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Import Graph Builder
|
|
3
|
-
*
|
|
4
|
-
* Builds a directed dependency graph from TypeScript/JavaScript imports.
|
|
5
|
-
* Uses the existing imports-tool parser to extract import relationships.
|
|
6
|
-
*
|
|
7
|
-
* When BM25 identifies a file as relevant, this follows the import chain
|
|
8
|
-
* N levels deep (default: 2) to include closely related files.
|
|
9
|
-
*
|
|
10
|
-
* Score = 1 / (depth + 1) for each reachable file.
|
|
11
|
-
* Direct imports get 0.5, 2nd-level imports get 0.33.
|
|
12
|
-
*
|
|
13
|
-
* @module domain/import-graph
|
|
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
|
-
/** Adjacency list: file → list of files it imports (resolved paths) */
|
|
26
|
-
export type ImportAdjacency = Record<string, string[]>
|
|
27
|
-
|
|
28
|
-
/** Reverse adjacency: file → list of files that import it */
|
|
29
|
-
export type ReverseAdjacency = Record<string, string[]>
|
|
30
|
-
|
|
31
|
-
export interface ImportGraph {
|
|
32
|
-
/** Forward edges: file imports these files */
|
|
33
|
-
forward: ImportAdjacency
|
|
34
|
-
/** Reverse edges: file is imported by these files */
|
|
35
|
-
reverse: ReverseAdjacency
|
|
36
|
-
/** Total number of files in the graph */
|
|
37
|
-
fileCount: number
|
|
38
|
-
/** Total number of edges */
|
|
39
|
-
edgeCount: number
|
|
40
|
-
/** Build timestamp */
|
|
41
|
-
builtAt: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ImportScore {
|
|
45
|
-
path: string
|
|
46
|
-
score: number
|
|
47
|
-
depth: number
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// =============================================================================
|
|
51
|
-
// Constants
|
|
52
|
-
// =============================================================================
|
|
53
|
-
|
|
54
|
-
const INDEXABLE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'])
|
|
55
|
-
|
|
56
|
-
const SKIP_DIRS = new Set([
|
|
57
|
-
'node_modules',
|
|
58
|
-
'.git',
|
|
59
|
-
'dist',
|
|
60
|
-
'build',
|
|
61
|
-
'out',
|
|
62
|
-
'.next',
|
|
63
|
-
'coverage',
|
|
64
|
-
'.cache',
|
|
65
|
-
'.turbo',
|
|
66
|
-
'.vercel',
|
|
67
|
-
])
|
|
68
|
-
|
|
69
|
-
/** Extensions to try when resolving imports */
|
|
70
|
-
const RESOLVE_EXTENSIONS = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.js']
|
|
71
|
-
|
|
72
|
-
// =============================================================================
|
|
73
|
-
// Import Extraction (lightweight — no dep on imports-tool for build speed)
|
|
74
|
-
// =============================================================================
|
|
75
|
-
|
|
76
|
-
const IMPORT_REGEX = /(?:import|from)\s+['"]([^'"]+)['"]/g
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Extract internal import paths from file content.
|
|
80
|
-
* Only resolves relative imports (starting with . or @/).
|
|
81
|
-
*/
|
|
82
|
-
function extractImportSources(content: string): string[] {
|
|
83
|
-
const sources: string[] = []
|
|
84
|
-
let match: RegExpExecArray | null
|
|
85
|
-
const regex = new RegExp(IMPORT_REGEX.source, 'g')
|
|
86
|
-
while ((match = regex.exec(content)) !== null) {
|
|
87
|
-
const source = match[1]
|
|
88
|
-
if (source.startsWith('.') || source.startsWith('@/')) {
|
|
89
|
-
sources.push(source)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return sources
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Try to resolve an import source to an actual file path.
|
|
97
|
-
*/
|
|
98
|
-
async function resolveImport(
|
|
99
|
-
source: string,
|
|
100
|
-
fromFile: string,
|
|
101
|
-
projectPath: string
|
|
102
|
-
): Promise<string | null> {
|
|
103
|
-
let basePath: string
|
|
104
|
-
|
|
105
|
-
if (source.startsWith('@/')) {
|
|
106
|
-
basePath = path.join(projectPath, 'src', source.slice(2))
|
|
107
|
-
} else {
|
|
108
|
-
const fromDir = path.dirname(path.join(projectPath, fromFile))
|
|
109
|
-
basePath = path.resolve(fromDir, source)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const ext of RESOLVE_EXTENSIONS) {
|
|
113
|
-
const fullPath = basePath + ext
|
|
114
|
-
try {
|
|
115
|
-
const stat = await fs.stat(fullPath)
|
|
116
|
-
if (stat.isFile()) {
|
|
117
|
-
return path.relative(projectPath, fullPath)
|
|
118
|
-
}
|
|
119
|
-
} catch {
|
|
120
|
-
// Extension not found, try next
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return null
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// =============================================================================
|
|
127
|
-
// Graph Building
|
|
128
|
-
// =============================================================================
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Recursively list all indexable files.
|
|
132
|
-
*/
|
|
133
|
-
async function listFiles(dir: string, projectPath: string): Promise<string[]> {
|
|
134
|
-
const files: string[] = []
|
|
135
|
-
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
136
|
-
|
|
137
|
-
for (const entry of entries) {
|
|
138
|
-
if (SKIP_DIRS.has(entry.name)) continue
|
|
139
|
-
|
|
140
|
-
const fullPath = path.join(dir, entry.name)
|
|
141
|
-
if (entry.isDirectory()) {
|
|
142
|
-
files.push(...(await listFiles(fullPath, projectPath)))
|
|
143
|
-
} else if (entry.isFile()) {
|
|
144
|
-
const ext = path.extname(entry.name).toLowerCase()
|
|
145
|
-
if (INDEXABLE_EXTENSIONS.has(ext)) {
|
|
146
|
-
files.push(path.relative(projectPath, fullPath))
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return files
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Build the import graph for a project.
|
|
155
|
-
*
|
|
156
|
-
* Performance target: <3 seconds for 500-file project.
|
|
157
|
-
*/
|
|
158
|
-
export async function buildGraph(projectPath: string): Promise<ImportGraph> {
|
|
159
|
-
const files = await listFiles(projectPath, projectPath)
|
|
160
|
-
const forward: ImportAdjacency = {}
|
|
161
|
-
const reverse: ReverseAdjacency = {}
|
|
162
|
-
let edgeCount = 0
|
|
163
|
-
|
|
164
|
-
// Process files in parallel batches
|
|
165
|
-
const BATCH_SIZE = 50
|
|
166
|
-
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
167
|
-
const batch = files.slice(i, i + BATCH_SIZE)
|
|
168
|
-
const results = await Promise.all(
|
|
169
|
-
batch.map(async (filePath) => {
|
|
170
|
-
try {
|
|
171
|
-
const content = await fs.readFile(path.join(projectPath, filePath), 'utf-8')
|
|
172
|
-
const sources = extractImportSources(content)
|
|
173
|
-
const resolved: string[] = []
|
|
174
|
-
|
|
175
|
-
for (const source of sources) {
|
|
176
|
-
const target = await resolveImport(source, filePath, projectPath)
|
|
177
|
-
if (target && target !== filePath) {
|
|
178
|
-
resolved.push(target)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { filePath, imports: resolved }
|
|
183
|
-
} catch {
|
|
184
|
-
return { filePath, imports: [] as string[] }
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
for (const { filePath, imports } of results) {
|
|
190
|
-
if (imports.length === 0) continue
|
|
191
|
-
|
|
192
|
-
forward[filePath] = imports
|
|
193
|
-
edgeCount += imports.length
|
|
194
|
-
|
|
195
|
-
for (const target of imports) {
|
|
196
|
-
if (!reverse[target]) reverse[target] = []
|
|
197
|
-
reverse[target].push(filePath)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
forward,
|
|
204
|
-
reverse,
|
|
205
|
-
fileCount: files.length,
|
|
206
|
-
edgeCount,
|
|
207
|
-
builtAt: new Date().toISOString(),
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// =============================================================================
|
|
212
|
-
// Graph Scoring
|
|
213
|
-
// =============================================================================
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Given a set of seed files (e.g., from BM25), follow import chains
|
|
217
|
-
* and score connected files by proximity.
|
|
218
|
-
*
|
|
219
|
-
* Score = 1 / (depth + 1):
|
|
220
|
-
* - Seed file itself: not scored (already scored by BM25)
|
|
221
|
-
* - Direct import/importer: 0.5 (depth=1)
|
|
222
|
-
* - 2nd-level: 0.33 (depth=2)
|
|
223
|
-
*
|
|
224
|
-
* Follows both forward (imports) and reverse (imported-by) edges.
|
|
225
|
-
*
|
|
226
|
-
* @param seedFiles - Files already identified as relevant
|
|
227
|
-
* @param graph - The import graph
|
|
228
|
-
* @param maxDepth - Maximum depth to follow (default: 2)
|
|
229
|
-
* @returns Scored files NOT in the seed set
|
|
230
|
-
*/
|
|
231
|
-
export function scoreFromSeeds(
|
|
232
|
-
seedFiles: string[],
|
|
233
|
-
graph: ImportGraph,
|
|
234
|
-
maxDepth = 2
|
|
235
|
-
): ImportScore[] {
|
|
236
|
-
const seedSet = new Set(seedFiles)
|
|
237
|
-
const visited = new Map<string, { score: number; depth: number }>()
|
|
238
|
-
|
|
239
|
-
// BFS from each seed
|
|
240
|
-
const queue: Array<{ file: string; depth: number }> = []
|
|
241
|
-
|
|
242
|
-
for (const seed of seedFiles) {
|
|
243
|
-
// Add direct neighbors at depth 1
|
|
244
|
-
const forwardEdges = graph.forward[seed] || []
|
|
245
|
-
const reverseEdges = graph.reverse[seed] || []
|
|
246
|
-
|
|
247
|
-
for (const neighbor of [...forwardEdges, ...reverseEdges]) {
|
|
248
|
-
if (!seedSet.has(neighbor)) {
|
|
249
|
-
queue.push({ file: neighbor, depth: 1 })
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Process queue
|
|
255
|
-
while (queue.length > 0) {
|
|
256
|
-
const { file, depth } = queue.shift()!
|
|
257
|
-
if (depth > maxDepth) continue
|
|
258
|
-
|
|
259
|
-
const score = 1 / (depth + 1)
|
|
260
|
-
const existing = visited.get(file)
|
|
261
|
-
|
|
262
|
-
if (existing) {
|
|
263
|
-
// Keep the better (higher) score
|
|
264
|
-
if (score > existing.score) {
|
|
265
|
-
visited.set(file, { score, depth })
|
|
266
|
-
}
|
|
267
|
-
continue
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
visited.set(file, { score, depth })
|
|
271
|
-
|
|
272
|
-
// Continue BFS for next level
|
|
273
|
-
if (depth < maxDepth) {
|
|
274
|
-
const forwardEdges = graph.forward[file] || []
|
|
275
|
-
const reverseEdges = graph.reverse[file] || []
|
|
276
|
-
|
|
277
|
-
for (const neighbor of [...forwardEdges, ...reverseEdges]) {
|
|
278
|
-
if (!seedSet.has(neighbor) && !visited.has(neighbor)) {
|
|
279
|
-
queue.push({ file: neighbor, depth: depth + 1 })
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return Array.from(visited.entries())
|
|
286
|
-
.map(([p, { score, depth }]) => ({ path: p, score, depth }))
|
|
287
|
-
.sort((a, b) => b.score - a.score)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// =============================================================================
|
|
291
|
-
// SQLite Persistence
|
|
292
|
-
// =============================================================================
|
|
293
|
-
|
|
294
|
-
const INDEX_KEY = 'import-graph'
|
|
295
|
-
|
|
296
|
-
export function saveGraph(projectId: string, graph: ImportGraph): void {
|
|
297
|
-
prjctDb.setDoc(projectId, INDEX_KEY, graph)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function loadGraph(projectId: string): ImportGraph | null {
|
|
301
|
-
return prjctDb.getDoc<ImportGraph>(projectId, INDEX_KEY)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// =============================================================================
|
|
305
|
-
// High-level API
|
|
306
|
-
// =============================================================================
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Build and persist the import graph for a project.
|
|
310
|
-
*/
|
|
311
|
-
export async function indexImports(projectPath: string, projectId: string): Promise<ImportGraph> {
|
|
312
|
-
const graph = await buildGraph(projectPath)
|
|
313
|
-
saveGraph(projectId, graph)
|
|
314
|
-
return graph
|
|
315
|
-
}
|