prjct-cli 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { affectedDomains, propagateChanges } from '../../domain/change-propagator'
|
|
3
|
-
import type { FileDiff } from '../../domain/file-hasher'
|
|
4
|
-
|
|
5
|
-
describe('change-propagator', () => {
|
|
6
|
-
// =========================================================================
|
|
7
|
-
// propagateChanges
|
|
8
|
-
// =========================================================================
|
|
9
|
-
|
|
10
|
-
describe('propagateChanges', () => {
|
|
11
|
-
test('returns direct changes when no import graph exists', () => {
|
|
12
|
-
const diff: FileDiff = {
|
|
13
|
-
added: ['src/new.ts'],
|
|
14
|
-
modified: ['src/changed.ts'],
|
|
15
|
-
deleted: ['src/removed.ts'],
|
|
16
|
-
unchanged: ['src/same.ts'],
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Use a fake projectId that won't have a graph
|
|
20
|
-
const result = propagateChanges(diff, 'nonexistent-project')
|
|
21
|
-
|
|
22
|
-
expect(result.directlyChanged).toEqual(['src/new.ts', 'src/changed.ts'])
|
|
23
|
-
expect(result.affectedByImports).toEqual([])
|
|
24
|
-
expect(result.allAffected).toEqual(['src/new.ts', 'src/changed.ts'])
|
|
25
|
-
expect(result.deleted).toEqual(['src/removed.ts'])
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
test('empty diff returns empty propagation', () => {
|
|
29
|
-
const diff: FileDiff = {
|
|
30
|
-
added: [],
|
|
31
|
-
modified: [],
|
|
32
|
-
deleted: [],
|
|
33
|
-
unchanged: ['src/a.ts', 'src/b.ts'],
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const result = propagateChanges(diff, 'nonexistent-project')
|
|
37
|
-
|
|
38
|
-
expect(result.directlyChanged).toEqual([])
|
|
39
|
-
expect(result.affectedByImports).toEqual([])
|
|
40
|
-
expect(result.allAffected).toEqual([])
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// =========================================================================
|
|
45
|
-
// affectedDomains
|
|
46
|
-
// =========================================================================
|
|
47
|
-
|
|
48
|
-
describe('affectedDomains', () => {
|
|
49
|
-
test('detects frontend files', () => {
|
|
50
|
-
const domains = affectedDomains([
|
|
51
|
-
'src/components/Button.tsx',
|
|
52
|
-
'src/pages/Home.jsx',
|
|
53
|
-
'styles/main.css',
|
|
54
|
-
])
|
|
55
|
-
expect(domains.has('frontend')).toBe(true)
|
|
56
|
-
expect(domains.has('uxui')).toBe(true)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('detects backend files', () => {
|
|
60
|
-
const domains = affectedDomains(['core/services/auth.ts', 'core/domain/user.ts'])
|
|
61
|
-
expect(domains.has('backend')).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('detects testing files', () => {
|
|
65
|
-
const domains = affectedDomains([
|
|
66
|
-
'core/__tests__/auth.test.ts',
|
|
67
|
-
'src/components/Button.spec.tsx',
|
|
68
|
-
])
|
|
69
|
-
expect(domains.has('testing')).toBe(true)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test('detects devops files', () => {
|
|
73
|
-
const domains = affectedDomains(['Dockerfile', '.github/workflows/ci.yml'])
|
|
74
|
-
expect(domains.has('devops')).toBe(true)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test('detects database files', () => {
|
|
78
|
-
const domains = affectedDomains(['prisma/schema.prisma', 'db/migrations/001.sql'])
|
|
79
|
-
expect(domains.has('database')).toBe(true)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('handles mixed domain files', () => {
|
|
83
|
-
const domains = affectedDomains([
|
|
84
|
-
'src/components/Form.tsx', // frontend + uxui
|
|
85
|
-
'core/services/api.ts', // backend
|
|
86
|
-
'Dockerfile', // devops
|
|
87
|
-
'core/__tests__/api.test.ts', // testing + backend
|
|
88
|
-
])
|
|
89
|
-
expect(domains.has('frontend')).toBe(true)
|
|
90
|
-
expect(domains.has('backend')).toBe(true)
|
|
91
|
-
expect(domains.has('devops')).toBe(true)
|
|
92
|
-
expect(domains.has('testing')).toBe(true)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test('empty file list returns empty domains', () => {
|
|
96
|
-
const domains = affectedDomains([])
|
|
97
|
-
expect(domains.size).toBe(0)
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
})
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fibonacci Estimation Module Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, expect, it } from 'bun:test'
|
|
6
|
-
import {
|
|
7
|
-
FIBONACCI_POINTS,
|
|
8
|
-
findClosestPoint,
|
|
9
|
-
formatMinutes,
|
|
10
|
-
isValidPoint,
|
|
11
|
-
pointsToMinutes,
|
|
12
|
-
pointsToTimeRange,
|
|
13
|
-
} from '../../domain/fibonacci'
|
|
14
|
-
|
|
15
|
-
describe('Fibonacci Estimation', () => {
|
|
16
|
-
describe('FIBONACCI_POINTS', () => {
|
|
17
|
-
it('should contain the standard Fibonacci sequence', () => {
|
|
18
|
-
expect(FIBONACCI_POINTS).toEqual([1, 2, 3, 5, 8, 13, 21])
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
describe('isValidPoint', () => {
|
|
23
|
-
it('should accept valid Fibonacci points', () => {
|
|
24
|
-
for (const p of FIBONACCI_POINTS) {
|
|
25
|
-
expect(isValidPoint(p)).toBe(true)
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('should reject non-Fibonacci numbers', () => {
|
|
30
|
-
expect(isValidPoint(0)).toBe(false)
|
|
31
|
-
expect(isValidPoint(4)).toBe(false)
|
|
32
|
-
expect(isValidPoint(6)).toBe(false)
|
|
33
|
-
expect(isValidPoint(10)).toBe(false)
|
|
34
|
-
expect(isValidPoint(22)).toBe(false)
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('pointsToMinutes', () => {
|
|
39
|
-
it('should return min/max/typical for each point', () => {
|
|
40
|
-
const result = pointsToMinutes(1)
|
|
41
|
-
expect(result).toEqual({ min: 5, max: 15, typical: 10 })
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should scale up with larger points', () => {
|
|
45
|
-
const small = pointsToMinutes(1)
|
|
46
|
-
const large = pointsToMinutes(21)
|
|
47
|
-
expect(large.typical).toBeGreaterThan(small.typical)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('should have increasing typical times across the scale', () => {
|
|
51
|
-
let prev = 0
|
|
52
|
-
for (const p of FIBONACCI_POINTS) {
|
|
53
|
-
const { typical } = pointsToMinutes(p)
|
|
54
|
-
expect(typical).toBeGreaterThan(prev)
|
|
55
|
-
prev = typical
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('formatMinutes', () => {
|
|
61
|
-
it('should format sub-hour durations', () => {
|
|
62
|
-
expect(formatMinutes(30)).toBe('30m')
|
|
63
|
-
expect(formatMinutes(5)).toBe('5m')
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('should format exact hours', () => {
|
|
67
|
-
expect(formatMinutes(60)).toBe('1h')
|
|
68
|
-
expect(formatMinutes(120)).toBe('2h')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should format hours and minutes', () => {
|
|
72
|
-
expect(formatMinutes(90)).toBe('1h 30m')
|
|
73
|
-
expect(formatMinutes(150)).toBe('2h 30m')
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('pointsToTimeRange', () => {
|
|
78
|
-
it('should return formatted range string', () => {
|
|
79
|
-
expect(pointsToTimeRange(1)).toBe('5m–15m')
|
|
80
|
-
expect(pointsToTimeRange(5)).toBe('1h–2h')
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('findClosestPoint', () => {
|
|
85
|
-
it('should find exact matches for typical times', () => {
|
|
86
|
-
expect(findClosestPoint(10)).toBe(1)
|
|
87
|
-
expect(findClosestPoint(20)).toBe(2)
|
|
88
|
-
expect(findClosestPoint(45)).toBe(3)
|
|
89
|
-
expect(findClosestPoint(90)).toBe(5)
|
|
90
|
-
expect(findClosestPoint(180)).toBe(8)
|
|
91
|
-
expect(findClosestPoint(360)).toBe(13)
|
|
92
|
-
expect(findClosestPoint(720)).toBe(21)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('should find closest point for in-between values', () => {
|
|
96
|
-
// 15 minutes is equidistant between 1 (10m) and 2 (20m) — picks first match
|
|
97
|
-
expect(findClosestPoint(15)).toBe(1)
|
|
98
|
-
// 16 minutes is closer to 2 (20m) than 1 (10m)
|
|
99
|
-
expect(findClosestPoint(16)).toBe(2)
|
|
100
|
-
// 35 minutes is closer to 3 (45m) than 2 (20m)
|
|
101
|
-
expect(findClosestPoint(35)).toBe(3)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('should return 1 for very small durations', () => {
|
|
105
|
-
expect(findClosestPoint(1)).toBe(1)
|
|
106
|
-
expect(findClosestPoint(0)).toBe(1)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should return 21 for very large durations', () => {
|
|
110
|
-
expect(findClosestPoint(1000)).toBe(21)
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
})
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { computeHashes, diffHashes, type FileHash } from '../../domain/file-hasher'
|
|
4
|
-
|
|
5
|
-
describe('file-hasher', () => {
|
|
6
|
-
// =========================================================================
|
|
7
|
-
// diffHashes
|
|
8
|
-
// =========================================================================
|
|
9
|
-
|
|
10
|
-
describe('diffHashes', () => {
|
|
11
|
-
const makeHash = (filePath: string, hash: string): FileHash => ({
|
|
12
|
-
path: filePath,
|
|
13
|
-
hash,
|
|
14
|
-
size: 100,
|
|
15
|
-
mtime: '2026-01-01T00:00:00.000Z',
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('detects added files', () => {
|
|
19
|
-
const current = new Map<string, FileHash>([
|
|
20
|
-
['src/new-file.ts', makeHash('src/new-file.ts', 'xxh64:abc')],
|
|
21
|
-
['src/existing.ts', makeHash('src/existing.ts', 'xxh64:def')],
|
|
22
|
-
])
|
|
23
|
-
const stored = new Map<string, FileHash>([
|
|
24
|
-
['src/existing.ts', makeHash('src/existing.ts', 'xxh64:def')],
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
const diff = diffHashes(current, stored)
|
|
28
|
-
expect(diff.added).toEqual(['src/new-file.ts'])
|
|
29
|
-
expect(diff.modified).toEqual([])
|
|
30
|
-
expect(diff.unchanged).toEqual(['src/existing.ts'])
|
|
31
|
-
expect(diff.deleted).toEqual([])
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('detects modified files', () => {
|
|
35
|
-
const current = new Map<string, FileHash>([
|
|
36
|
-
['src/changed.ts', makeHash('src/changed.ts', 'xxh64:new-hash')],
|
|
37
|
-
])
|
|
38
|
-
const stored = new Map<string, FileHash>([
|
|
39
|
-
['src/changed.ts', makeHash('src/changed.ts', 'xxh64:old-hash')],
|
|
40
|
-
])
|
|
41
|
-
|
|
42
|
-
const diff = diffHashes(current, stored)
|
|
43
|
-
expect(diff.added).toEqual([])
|
|
44
|
-
expect(diff.modified).toEqual(['src/changed.ts'])
|
|
45
|
-
expect(diff.unchanged).toEqual([])
|
|
46
|
-
expect(diff.deleted).toEqual([])
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('detects deleted files', () => {
|
|
50
|
-
const current = new Map<string, FileHash>()
|
|
51
|
-
const stored = new Map<string, FileHash>([
|
|
52
|
-
['src/removed.ts', makeHash('src/removed.ts', 'xxh64:abc')],
|
|
53
|
-
])
|
|
54
|
-
|
|
55
|
-
const diff = diffHashes(current, stored)
|
|
56
|
-
expect(diff.added).toEqual([])
|
|
57
|
-
expect(diff.modified).toEqual([])
|
|
58
|
-
expect(diff.unchanged).toEqual([])
|
|
59
|
-
expect(diff.deleted).toEqual(['src/removed.ts'])
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('handles empty maps', () => {
|
|
63
|
-
const diff = diffHashes(new Map(), new Map())
|
|
64
|
-
expect(diff.added).toEqual([])
|
|
65
|
-
expect(diff.modified).toEqual([])
|
|
66
|
-
expect(diff.unchanged).toEqual([])
|
|
67
|
-
expect(diff.deleted).toEqual([])
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('handles first sync (no stored hashes)', () => {
|
|
71
|
-
const current = new Map<string, FileHash>([
|
|
72
|
-
['src/a.ts', makeHash('src/a.ts', 'xxh64:1')],
|
|
73
|
-
['src/b.ts', makeHash('src/b.ts', 'xxh64:2')],
|
|
74
|
-
['src/c.ts', makeHash('src/c.ts', 'xxh64:3')],
|
|
75
|
-
])
|
|
76
|
-
const stored = new Map<string, FileHash>()
|
|
77
|
-
|
|
78
|
-
const diff = diffHashes(current, stored)
|
|
79
|
-
expect(diff.added).toHaveLength(3)
|
|
80
|
-
expect(diff.modified).toEqual([])
|
|
81
|
-
expect(diff.unchanged).toEqual([])
|
|
82
|
-
expect(diff.deleted).toEqual([])
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('mixed changes: added + modified + deleted + unchanged', () => {
|
|
86
|
-
const current = new Map<string, FileHash>([
|
|
87
|
-
['src/new.ts', makeHash('src/new.ts', 'xxh64:new')],
|
|
88
|
-
['src/changed.ts', makeHash('src/changed.ts', 'xxh64:v2')],
|
|
89
|
-
['src/same.ts', makeHash('src/same.ts', 'xxh64:same')],
|
|
90
|
-
])
|
|
91
|
-
const stored = new Map<string, FileHash>([
|
|
92
|
-
['src/changed.ts', makeHash('src/changed.ts', 'xxh64:v1')],
|
|
93
|
-
['src/same.ts', makeHash('src/same.ts', 'xxh64:same')],
|
|
94
|
-
['src/gone.ts', makeHash('src/gone.ts', 'xxh64:gone')],
|
|
95
|
-
])
|
|
96
|
-
|
|
97
|
-
const diff = diffHashes(current, stored)
|
|
98
|
-
expect(diff.added).toEqual(['src/new.ts'])
|
|
99
|
-
expect(diff.modified).toEqual(['src/changed.ts'])
|
|
100
|
-
expect(diff.unchanged).toEqual(['src/same.ts'])
|
|
101
|
-
expect(diff.deleted).toEqual(['src/gone.ts'])
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
// =========================================================================
|
|
106
|
-
// computeHashes (integration — reads actual files)
|
|
107
|
-
// =========================================================================
|
|
108
|
-
|
|
109
|
-
describe('computeHashes', () => {
|
|
110
|
-
test('computes hashes for project files', async () => {
|
|
111
|
-
// Hash the prjct-cli project itself (small subset)
|
|
112
|
-
const projectPath = path.resolve(__dirname, '..', '..', '..')
|
|
113
|
-
const hashes = await computeHashes(projectPath)
|
|
114
|
-
|
|
115
|
-
// Should find many files
|
|
116
|
-
expect(hashes.size).toBeGreaterThan(50)
|
|
117
|
-
|
|
118
|
-
// Check a known file exists
|
|
119
|
-
const packageJson = hashes.get('package.json')
|
|
120
|
-
expect(packageJson).toBeDefined()
|
|
121
|
-
expect(packageJson!.hash).toMatch(/^(xxh64|fnv1a):/)
|
|
122
|
-
expect(packageJson!.size).toBeGreaterThan(0)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
test('excludes node_modules and .git', async () => {
|
|
126
|
-
const projectPath = path.resolve(__dirname, '..', '..', '..')
|
|
127
|
-
const hashes = await computeHashes(projectPath)
|
|
128
|
-
|
|
129
|
-
for (const [filePath] of hashes) {
|
|
130
|
-
expect(filePath).not.toContain('node_modules')
|
|
131
|
-
expect(filePath).not.toContain('.git/')
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
test('hash is deterministic', async () => {
|
|
136
|
-
const projectPath = path.resolve(__dirname, '..', '..', '..')
|
|
137
|
-
const hashes1 = await computeHashes(projectPath)
|
|
138
|
-
const hashes2 = await computeHashes(projectPath)
|
|
139
|
-
|
|
140
|
-
// Same file should produce same hash
|
|
141
|
-
const pkg1 = hashes1.get('package.json')
|
|
142
|
-
const pkg2 = hashes2.get('package.json')
|
|
143
|
-
expect(pkg1?.hash).toBe(pkg2?.hash)
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
})
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Combined File Ranker
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
6
|
-
import { exec as execCallback } from 'node:child_process'
|
|
7
|
-
import fs from 'node:fs/promises'
|
|
8
|
-
import os from 'node:os'
|
|
9
|
-
import path from 'node:path'
|
|
10
|
-
import { promisify } from 'node:util'
|
|
11
|
-
import { indexProject } from '../../domain/bm25'
|
|
12
|
-
import { hasIndexes, rankFiles } from '../../domain/file-ranker'
|
|
13
|
-
import { indexCoChanges } from '../../domain/git-cochange'
|
|
14
|
-
import { indexImports } from '../../domain/import-graph'
|
|
15
|
-
import pathManager from '../../infrastructure/path-manager'
|
|
16
|
-
import prjctDb from '../../storage/database'
|
|
17
|
-
|
|
18
|
-
const exec = promisify(execCallback)
|
|
19
|
-
|
|
20
|
-
describe('FileRanker', () => {
|
|
21
|
-
let testDir: string
|
|
22
|
-
let testProjectId: string
|
|
23
|
-
const originalGetGlobalProjectPath = pathManager.getGlobalProjectPath.bind(pathManager)
|
|
24
|
-
|
|
25
|
-
beforeEach(async () => {
|
|
26
|
-
testDir = path.join(os.tmpdir(), `prjct-ranker-test-${Date.now()}`)
|
|
27
|
-
testProjectId = `test-ranker-${Date.now()}`
|
|
28
|
-
await fs.mkdir(testDir, { recursive: true })
|
|
29
|
-
|
|
30
|
-
// Mock path manager to use temp dir
|
|
31
|
-
pathManager.getGlobalProjectPath = () => testDir
|
|
32
|
-
|
|
33
|
-
// Initialize git repo
|
|
34
|
-
await exec('git init', { cwd: testDir })
|
|
35
|
-
await exec('git config user.email "test@test.com"', { cwd: testDir })
|
|
36
|
-
await exec('git config user.name "Test"', { cwd: testDir })
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
afterEach(async () => {
|
|
40
|
-
pathManager.getGlobalProjectPath = originalGetGlobalProjectPath
|
|
41
|
-
prjctDb.close()
|
|
42
|
-
try {
|
|
43
|
-
await fs.rm(testDir, { recursive: true, force: true })
|
|
44
|
-
} catch {
|
|
45
|
-
// Ignore
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
describe('hasIndexes', () => {
|
|
50
|
-
it('should return false when no indexes exist', () => {
|
|
51
|
-
const result = hasIndexes(testProjectId)
|
|
52
|
-
expect(result.bm25).toBe(false)
|
|
53
|
-
expect(result.imports).toBe(false)
|
|
54
|
-
expect(result.cochange).toBe(false)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should return true after building indexes', async () => {
|
|
58
|
-
// Create a test file
|
|
59
|
-
await fs.writeFile(path.join(testDir, 'app.ts'), 'export function main() {}')
|
|
60
|
-
await exec('git add -A && git commit -m "init"', { cwd: testDir })
|
|
61
|
-
|
|
62
|
-
await indexProject(testDir, testProjectId)
|
|
63
|
-
await indexImports(testDir, testProjectId)
|
|
64
|
-
await indexCoChanges(testDir, testProjectId)
|
|
65
|
-
|
|
66
|
-
const result = hasIndexes(testProjectId)
|
|
67
|
-
expect(result.bm25).toBe(true)
|
|
68
|
-
expect(result.imports).toBe(true)
|
|
69
|
-
expect(result.cochange).toBe(true)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('rankFiles', () => {
|
|
74
|
-
it('should return empty array when no indexes exist', () => {
|
|
75
|
-
const result = rankFiles(testProjectId, 'anything')
|
|
76
|
-
expect(result).toEqual([])
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('should rank relevant files higher', async () => {
|
|
80
|
-
// Create auth-related files
|
|
81
|
-
await fs.writeFile(
|
|
82
|
-
path.join(testDir, 'auth.ts'),
|
|
83
|
-
`// Authentication service for JWT handling\nexport class AuthService {\n validateJwt(token: string) { return true }\n}`
|
|
84
|
-
)
|
|
85
|
-
await fs.writeFile(
|
|
86
|
-
path.join(testDir, 'middleware.ts'),
|
|
87
|
-
`import { AuthService } from './auth'\n// Auth middleware\nexport function authMiddleware() {}`
|
|
88
|
-
)
|
|
89
|
-
await fs.writeFile(
|
|
90
|
-
path.join(testDir, 'session.ts'),
|
|
91
|
-
`import { AuthService } from './auth'\n// Session management\nexport function refreshSession() {}`
|
|
92
|
-
)
|
|
93
|
-
// Create unrelated file
|
|
94
|
-
await fs.writeFile(
|
|
95
|
-
path.join(testDir, 'button.tsx'),
|
|
96
|
-
`// UI button component\nexport function Button() { return null }`
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
// Create git history with co-changes
|
|
100
|
-
await exec('git add -A && git commit -m "init"', { cwd: testDir })
|
|
101
|
-
await fs.writeFile(path.join(testDir, 'auth.ts'), `export class AuthService { v2() {} }`)
|
|
102
|
-
await fs.writeFile(
|
|
103
|
-
path.join(testDir, 'middleware.ts'),
|
|
104
|
-
`export function authMiddleware() { v2 }`
|
|
105
|
-
)
|
|
106
|
-
await exec('git add -A && git commit -m "update auth"', { cwd: testDir })
|
|
107
|
-
|
|
108
|
-
// Build all indexes
|
|
109
|
-
await indexProject(testDir, testProjectId)
|
|
110
|
-
await indexImports(testDir, testProjectId)
|
|
111
|
-
await indexCoChanges(testDir, testProjectId)
|
|
112
|
-
|
|
113
|
-
const results = rankFiles(testProjectId, 'Fix auth middleware for JWT validation')
|
|
114
|
-
|
|
115
|
-
expect(results.length).toBeGreaterThan(0)
|
|
116
|
-
|
|
117
|
-
// Auth and middleware should be in results
|
|
118
|
-
const authResult = results.find((r) => r.path === 'auth.ts')
|
|
119
|
-
const middlewareResult = results.find((r) => r.path === 'middleware.ts')
|
|
120
|
-
|
|
121
|
-
expect(authResult).toBeDefined()
|
|
122
|
-
expect(middlewareResult).toBeDefined()
|
|
123
|
-
|
|
124
|
-
// Auth should rank higher than button
|
|
125
|
-
const buttonResult = results.find((r) => r.path === 'button.tsx')
|
|
126
|
-
if (authResult && buttonResult) {
|
|
127
|
-
expect(authResult.finalScore).toBeGreaterThan(buttonResult.finalScore)
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('should include signal breakdown', async () => {
|
|
132
|
-
await fs.writeFile(
|
|
133
|
-
path.join(testDir, 'auth.ts'),
|
|
134
|
-
`// Authentication\nexport class AuthService {}`
|
|
135
|
-
)
|
|
136
|
-
await exec('git add -A && git commit -m "init"', { cwd: testDir })
|
|
137
|
-
|
|
138
|
-
await indexProject(testDir, testProjectId)
|
|
139
|
-
await indexImports(testDir, testProjectId)
|
|
140
|
-
await indexCoChanges(testDir, testProjectId)
|
|
141
|
-
|
|
142
|
-
const results = rankFiles(testProjectId, 'authentication')
|
|
143
|
-
|
|
144
|
-
if (results.length > 0) {
|
|
145
|
-
const first = results[0]
|
|
146
|
-
expect(first.signals).toBeDefined()
|
|
147
|
-
expect(typeof first.signals.bm25).toBe('number')
|
|
148
|
-
expect(typeof first.signals.imports).toBe('number')
|
|
149
|
-
expect(typeof first.signals.cochange).toBe('number')
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should respect topN config', async () => {
|
|
154
|
-
// Create many files
|
|
155
|
-
for (let i = 0; i < 20; i++) {
|
|
156
|
-
await fs.writeFile(
|
|
157
|
-
path.join(testDir, `service-${i}.ts`),
|
|
158
|
-
`// Service module ${i}\nexport function service${i}() {}`
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
await exec('git add -A && git commit -m "init"', { cwd: testDir })
|
|
162
|
-
|
|
163
|
-
await indexProject(testDir, testProjectId)
|
|
164
|
-
|
|
165
|
-
const results = rankFiles(testProjectId, 'service module', { topN: 5 })
|
|
166
|
-
expect(results.length).toBeLessThanOrEqual(5)
|
|
167
|
-
})
|
|
168
|
-
})
|
|
169
|
-
})
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Git Co-Change Analyzer
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
6
|
-
import { exec as execCallback } from 'node:child_process'
|
|
7
|
-
import fs from 'node:fs/promises'
|
|
8
|
-
import os from 'node:os'
|
|
9
|
-
import path from 'node:path'
|
|
10
|
-
import { promisify } from 'node:util'
|
|
11
|
-
import { buildMatrix, scoreFromSeeds } from '../../domain/git-cochange'
|
|
12
|
-
|
|
13
|
-
const exec = promisify(execCallback)
|
|
14
|
-
|
|
15
|
-
describe('GitCoChange', () => {
|
|
16
|
-
let testDir: string
|
|
17
|
-
|
|
18
|
-
beforeEach(async () => {
|
|
19
|
-
testDir = path.join(os.tmpdir(), `prjct-cochange-test-${Date.now()}`)
|
|
20
|
-
await fs.mkdir(testDir, { recursive: true })
|
|
21
|
-
|
|
22
|
-
// Initialize a git repo
|
|
23
|
-
await exec('git init', { cwd: testDir })
|
|
24
|
-
await exec('git config user.email "test@test.com"', { cwd: testDir })
|
|
25
|
-
await exec('git config user.name "Test"', { cwd: testDir })
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
try {
|
|
30
|
-
await fs.rm(testDir, { recursive: true, force: true })
|
|
31
|
-
} catch {
|
|
32
|
-
// Ignore cleanup errors
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
describe('buildMatrix', () => {
|
|
37
|
-
it('should detect co-changed files', async () => {
|
|
38
|
-
// Create files that change together
|
|
39
|
-
for (let i = 0; i < 5; i++) {
|
|
40
|
-
await fs.writeFile(path.join(testDir, 'auth.ts'), `export const v${i} = ${i}`)
|
|
41
|
-
await fs.writeFile(path.join(testDir, 'middleware.ts'), `export const v${i} = ${i}`)
|
|
42
|
-
await exec('git add -A', { cwd: testDir })
|
|
43
|
-
await exec(`git commit -m "commit ${i}"`, { cwd: testDir })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Create a file that changes independently
|
|
47
|
-
await fs.writeFile(path.join(testDir, 'unrelated.ts'), 'export const x = 1')
|
|
48
|
-
await exec('git add -A', { cwd: testDir })
|
|
49
|
-
await exec('git commit -m "unrelated"', { cwd: testDir })
|
|
50
|
-
|
|
51
|
-
const index = await buildMatrix(testDir, 100)
|
|
52
|
-
|
|
53
|
-
expect(index.commitsAnalyzed).toBeGreaterThan(0)
|
|
54
|
-
expect(index.matrix['auth.ts']).toBeDefined()
|
|
55
|
-
expect(index.matrix['auth.ts']['middleware.ts']).toBeGreaterThan(0)
|
|
56
|
-
|
|
57
|
-
// Auth and middleware should have high co-change
|
|
58
|
-
const similarity = index.matrix['auth.ts']['middleware.ts']
|
|
59
|
-
expect(similarity).toBeGreaterThan(0.5)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('should be symmetric', async () => {
|
|
63
|
-
for (let i = 0; i < 3; i++) {
|
|
64
|
-
await fs.writeFile(path.join(testDir, 'a.ts'), `const v${i} = ${i}`)
|
|
65
|
-
await fs.writeFile(path.join(testDir, 'b.ts'), `const v${i} = ${i}`)
|
|
66
|
-
await exec('git add -A', { cwd: testDir })
|
|
67
|
-
await exec(`git commit -m "commit ${i}"`, { cwd: testDir })
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const index = await buildMatrix(testDir, 100)
|
|
71
|
-
|
|
72
|
-
if (index.matrix['a.ts'] && index.matrix['b.ts']) {
|
|
73
|
-
expect(index.matrix['a.ts']['b.ts']).toBe(index.matrix['b.ts']['a.ts'])
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('should handle no git history', async () => {
|
|
78
|
-
const emptyDir = path.join(os.tmpdir(), `prjct-cochange-empty-${Date.now()}`)
|
|
79
|
-
await fs.mkdir(emptyDir, { recursive: true })
|
|
80
|
-
|
|
81
|
-
const index = await buildMatrix(emptyDir, 100)
|
|
82
|
-
expect(index.commitsAnalyzed).toBe(0)
|
|
83
|
-
expect(Object.keys(index.matrix)).toHaveLength(0)
|
|
84
|
-
|
|
85
|
-
await fs.rm(emptyDir, { recursive: true, force: true })
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
describe('scoreFromSeeds', () => {
|
|
90
|
-
it('should score co-changed files', async () => {
|
|
91
|
-
// Create co-change history
|
|
92
|
-
for (let i = 0; i < 5; i++) {
|
|
93
|
-
await fs.writeFile(path.join(testDir, 'auth.ts'), `v${i}`)
|
|
94
|
-
await fs.writeFile(path.join(testDir, 'session.ts'), `v${i}`)
|
|
95
|
-
await exec('git add -A', { cwd: testDir })
|
|
96
|
-
await exec(`git commit -m "commit ${i}"`, { cwd: testDir })
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const index = await buildMatrix(testDir, 100)
|
|
100
|
-
const scores = scoreFromSeeds(['auth.ts'], index)
|
|
101
|
-
|
|
102
|
-
const sessionScore = scores.find((s) => s.path === 'session.ts')
|
|
103
|
-
expect(sessionScore).toBeDefined()
|
|
104
|
-
expect(sessionScore!.score).toBeGreaterThan(0)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('should not include seed files in results', async () => {
|
|
108
|
-
for (let i = 0; i < 3; i++) {
|
|
109
|
-
await fs.writeFile(path.join(testDir, 'a.ts'), `v${i}`)
|
|
110
|
-
await fs.writeFile(path.join(testDir, 'b.ts'), `v${i}`)
|
|
111
|
-
await exec('git add -A', { cwd: testDir })
|
|
112
|
-
await exec(`git commit -m "commit ${i}"`, { cwd: testDir })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const index = await buildMatrix(testDir, 100)
|
|
116
|
-
const scores = scoreFromSeeds(['a.ts'], index)
|
|
117
|
-
|
|
118
|
-
expect(scores.find((s) => s.path === 'a.ts')).toBeUndefined()
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})
|