prjct-cli 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct.mjs +917 -35845
- package/dist/bin/prjct.mjs.map +7 -0
- package/dist/cli/linear.mjs +16 -0
- package/dist/cli/linear.mjs.map +7 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
package/core/domain/velocity.ts
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Velocity Engine (PRJ-296)
|
|
3
|
-
*
|
|
4
|
-
* Sprint-based velocity calculation with:
|
|
5
|
-
* - Sprint aggregation from outcomes data
|
|
6
|
-
* - Trend detection (improving/stable/declining)
|
|
7
|
-
* - Estimation accuracy tracking
|
|
8
|
-
* - Over/under estimation pattern detection
|
|
9
|
-
* - Completion projections
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
type CompletionProjection,
|
|
14
|
-
DEFAULT_VELOCITY_CONFIG,
|
|
15
|
-
type EstimationPattern,
|
|
16
|
-
type SprintVelocity,
|
|
17
|
-
type VelocityConfig,
|
|
18
|
-
type VelocityMetrics,
|
|
19
|
-
type VelocityTrend,
|
|
20
|
-
} from '../schemas/velocity'
|
|
21
|
-
import type { Outcome } from '../types'
|
|
22
|
-
|
|
23
|
-
// =============================================================================
|
|
24
|
-
// Types
|
|
25
|
-
// =============================================================================
|
|
26
|
-
|
|
27
|
-
/** Day-of-week index (0=Sunday, 1=Monday, ..., 6=Saturday) */
|
|
28
|
-
const DAY_INDEX: Record<string, number> = {
|
|
29
|
-
sunday: 0,
|
|
30
|
-
monday: 1,
|
|
31
|
-
tuesday: 2,
|
|
32
|
-
wednesday: 3,
|
|
33
|
-
thursday: 4,
|
|
34
|
-
friday: 5,
|
|
35
|
-
saturday: 6,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// =============================================================================
|
|
39
|
-
// Sprint Boundary Calculation
|
|
40
|
-
// =============================================================================
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get the sprint start date for a given date based on config.
|
|
44
|
-
* Sprints align to calendar boundaries (e.g., every Monday for 7-day sprints).
|
|
45
|
-
*/
|
|
46
|
-
export function getSprintStart(date: Date, config: VelocityConfig): Date {
|
|
47
|
-
const resolved = resolveConfig(config)
|
|
48
|
-
const startDayIdx = DAY_INDEX[resolved.startDay]
|
|
49
|
-
const d = new Date(date)
|
|
50
|
-
d.setHours(0, 0, 0, 0)
|
|
51
|
-
|
|
52
|
-
// Roll back to the most recent start day
|
|
53
|
-
const currentDay = d.getDay()
|
|
54
|
-
const diff = (currentDay - startDayIdx + 7) % 7
|
|
55
|
-
d.setDate(d.getDate() - diff)
|
|
56
|
-
|
|
57
|
-
return d
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get sprint end date from sprint start.
|
|
62
|
-
*/
|
|
63
|
-
export function getSprintEnd(sprintStart: Date, config: VelocityConfig): Date {
|
|
64
|
-
const resolved = resolveConfig(config)
|
|
65
|
-
const end = new Date(sprintStart)
|
|
66
|
-
end.setDate(end.getDate() + resolved.sprintLengthDays - 1)
|
|
67
|
-
end.setHours(23, 59, 59, 999)
|
|
68
|
-
return end
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Assign a sprint number to a date.
|
|
73
|
-
* Sprint 1 is the earliest sprint in the data set.
|
|
74
|
-
*/
|
|
75
|
-
function getSprintNumber(date: Date, earliestDate: Date, config: VelocityConfig): number {
|
|
76
|
-
const resolved = resolveConfig(config)
|
|
77
|
-
const sprintStart = getSprintStart(date, config)
|
|
78
|
-
const firstSprintStart = getSprintStart(earliestDate, config)
|
|
79
|
-
|
|
80
|
-
const diffMs = sprintStart.getTime() - firstSprintStart.getTime()
|
|
81
|
-
const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24))
|
|
82
|
-
|
|
83
|
-
return Math.floor(diffDays / resolved.sprintLengthDays) + 1
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// =============================================================================
|
|
87
|
-
// Core Engine
|
|
88
|
-
// =============================================================================
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Calculate velocity metrics from outcome data.
|
|
92
|
-
*/
|
|
93
|
-
export function calculateVelocity(
|
|
94
|
-
outcomes: Outcome[],
|
|
95
|
-
config: VelocityConfig = DEFAULT_VELOCITY_CONFIG
|
|
96
|
-
): VelocityMetrics {
|
|
97
|
-
const resolved = resolveConfig(config)
|
|
98
|
-
|
|
99
|
-
if (outcomes.length === 0) {
|
|
100
|
-
return {
|
|
101
|
-
sprints: [],
|
|
102
|
-
averageVelocity: 0,
|
|
103
|
-
velocityTrend: 'stable',
|
|
104
|
-
estimationAccuracy: 0,
|
|
105
|
-
overEstimated: [],
|
|
106
|
-
underEstimated: [],
|
|
107
|
-
lastUpdated: new Date().toISOString(),
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Parse outcomes into sprint buckets
|
|
112
|
-
const sprintBuckets = bucketBySprint(outcomes, config)
|
|
113
|
-
const sprints = buildSprintVelocities(sprintBuckets, resolved.accuracyTolerance)
|
|
114
|
-
|
|
115
|
-
// Use last N sprints for rolling metrics
|
|
116
|
-
const windowSprints = sprints.slice(-resolved.windowSize)
|
|
117
|
-
|
|
118
|
-
const averageVelocity = calculateAverageVelocity(windowSprints)
|
|
119
|
-
const velocityTrend = detectTrend(windowSprints)
|
|
120
|
-
const estimationAccuracy = calculateOverallAccuracy(outcomes, resolved.accuracyTolerance)
|
|
121
|
-
|
|
122
|
-
// Detect estimation patterns
|
|
123
|
-
const { overEstimated, underEstimated } = detectEstimationPatterns(outcomes)
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
sprints,
|
|
127
|
-
averageVelocity,
|
|
128
|
-
velocityTrend,
|
|
129
|
-
estimationAccuracy,
|
|
130
|
-
overEstimated,
|
|
131
|
-
underEstimated,
|
|
132
|
-
lastUpdated: new Date().toISOString(),
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Project completion date given remaining points and current velocity.
|
|
138
|
-
*/
|
|
139
|
-
export function projectCompletion(
|
|
140
|
-
totalPoints: number,
|
|
141
|
-
averageVelocity: number,
|
|
142
|
-
config: VelocityConfig = DEFAULT_VELOCITY_CONFIG
|
|
143
|
-
): CompletionProjection {
|
|
144
|
-
const resolved = resolveConfig(config)
|
|
145
|
-
|
|
146
|
-
if (averageVelocity <= 0) {
|
|
147
|
-
return {
|
|
148
|
-
totalPoints,
|
|
149
|
-
sprints: 0,
|
|
150
|
-
estimatedDate: '',
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const sprints = Math.ceil(totalPoints / averageVelocity)
|
|
155
|
-
const daysRemaining = sprints * resolved.sprintLengthDays
|
|
156
|
-
const estimatedDate = new Date()
|
|
157
|
-
estimatedDate.setDate(estimatedDate.getDate() + daysRemaining)
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
totalPoints,
|
|
161
|
-
sprints,
|
|
162
|
-
estimatedDate: estimatedDate.toISOString(),
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// =============================================================================
|
|
167
|
-
// Sprint Bucketing
|
|
168
|
-
// =============================================================================
|
|
169
|
-
|
|
170
|
-
interface SprintBucket {
|
|
171
|
-
sprintNumber: number
|
|
172
|
-
startDate: Date
|
|
173
|
-
endDate: Date
|
|
174
|
-
outcomes: Outcome[]
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function bucketBySprint(outcomes: Outcome[], config: VelocityConfig): Map<number, SprintBucket> {
|
|
178
|
-
const buckets = new Map<number, SprintBucket>()
|
|
179
|
-
|
|
180
|
-
// Find earliest outcome date
|
|
181
|
-
const dates = outcomes.map((o) => new Date(o.completedAt))
|
|
182
|
-
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())))
|
|
183
|
-
|
|
184
|
-
for (const outcome of outcomes) {
|
|
185
|
-
const completedDate = new Date(outcome.completedAt)
|
|
186
|
-
const sprintNum = getSprintNumber(completedDate, earliest, config)
|
|
187
|
-
|
|
188
|
-
if (!buckets.has(sprintNum)) {
|
|
189
|
-
const start = getSprintStart(completedDate, config)
|
|
190
|
-
const end = getSprintEnd(start, config)
|
|
191
|
-
buckets.set(sprintNum, {
|
|
192
|
-
sprintNumber: sprintNum,
|
|
193
|
-
startDate: start,
|
|
194
|
-
endDate: end,
|
|
195
|
-
outcomes: [],
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
buckets.get(sprintNum)!.outcomes.push(outcome)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return buckets
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function buildSprintVelocities(
|
|
206
|
-
buckets: Map<number, SprintBucket>,
|
|
207
|
-
accuracyTolerance: number
|
|
208
|
-
): SprintVelocity[] {
|
|
209
|
-
const sprints: SprintVelocity[] = []
|
|
210
|
-
|
|
211
|
-
for (const [, bucket] of buckets) {
|
|
212
|
-
const points = bucket.outcomes.reduce((sum, o) => {
|
|
213
|
-
return sum + derivePoints(o)
|
|
214
|
-
}, 0)
|
|
215
|
-
|
|
216
|
-
const variances = bucket.outcomes.filter((o) => o.variance).map((o) => parseVariancePercent(o))
|
|
217
|
-
|
|
218
|
-
const avgVariance =
|
|
219
|
-
variances.length > 0 ? Math.round(variances.reduce((a, b) => a + b, 0) / variances.length) : 0
|
|
220
|
-
|
|
221
|
-
const accurateCount = variances.filter((v) => Math.abs(v) <= accuracyTolerance).length
|
|
222
|
-
const estimationAccuracy =
|
|
223
|
-
variances.length > 0 ? Math.round((accurateCount / variances.length) * 100) : 0
|
|
224
|
-
|
|
225
|
-
sprints.push({
|
|
226
|
-
sprintNumber: bucket.sprintNumber,
|
|
227
|
-
startDate: bucket.startDate.toISOString(),
|
|
228
|
-
endDate: bucket.endDate.toISOString(),
|
|
229
|
-
pointsCompleted: points,
|
|
230
|
-
tasksCompleted: bucket.outcomes.length,
|
|
231
|
-
avgVariance,
|
|
232
|
-
estimationAccuracy,
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Sort by sprint number
|
|
237
|
-
return sprints.sort((a, b) => a.sprintNumber - b.sprintNumber)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// =============================================================================
|
|
241
|
-
// Trend Detection
|
|
242
|
-
// =============================================================================
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Detect velocity trend using simple linear regression on points per sprint.
|
|
246
|
-
* Requires at least 3 sprints for meaningful trend detection.
|
|
247
|
-
*/
|
|
248
|
-
export function detectTrend(sprints: SprintVelocity[]): VelocityTrend {
|
|
249
|
-
if (sprints.length < 3) return 'stable'
|
|
250
|
-
|
|
251
|
-
const points = sprints.map((s) => s.pointsCompleted)
|
|
252
|
-
const n = points.length
|
|
253
|
-
|
|
254
|
-
// Simple linear regression slope
|
|
255
|
-
let sumX = 0
|
|
256
|
-
let sumY = 0
|
|
257
|
-
let sumXY = 0
|
|
258
|
-
let sumX2 = 0
|
|
259
|
-
for (let i = 0; i < n; i++) {
|
|
260
|
-
sumX += i
|
|
261
|
-
sumY += points[i]
|
|
262
|
-
sumXY += i * points[i]
|
|
263
|
-
sumX2 += i * i
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
|
|
267
|
-
const avgVelocity = sumY / n
|
|
268
|
-
|
|
269
|
-
// Normalize slope as percentage of average velocity
|
|
270
|
-
if (avgVelocity === 0) return 'stable'
|
|
271
|
-
|
|
272
|
-
const normalizedSlope = slope / avgVelocity
|
|
273
|
-
|
|
274
|
-
// Thresholds: >10% per sprint = improving, <-10% = declining
|
|
275
|
-
if (normalizedSlope > 0.1) return 'improving'
|
|
276
|
-
if (normalizedSlope < -0.1) return 'declining'
|
|
277
|
-
return 'stable'
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// =============================================================================
|
|
281
|
-
// Estimation Accuracy
|
|
282
|
-
// =============================================================================
|
|
283
|
-
|
|
284
|
-
function calculateOverallAccuracy(outcomes: Outcome[], tolerance: number): number {
|
|
285
|
-
const withEstimates = outcomes.filter((o) => o.variance)
|
|
286
|
-
if (withEstimates.length === 0) return 0
|
|
287
|
-
|
|
288
|
-
const accurate = withEstimates.filter((o) => {
|
|
289
|
-
const variancePct = parseVariancePercent(o)
|
|
290
|
-
return Math.abs(variancePct) <= tolerance
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
return Math.round((accurate.length / withEstimates.length) * 100)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function calculateAverageVelocity(sprints: SprintVelocity[]): number {
|
|
297
|
-
if (sprints.length === 0) return 0
|
|
298
|
-
const total = sprints.reduce((sum, s) => sum + s.pointsCompleted, 0)
|
|
299
|
-
return Math.round((total / sprints.length) * 10) / 10
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// =============================================================================
|
|
303
|
-
// Estimation Pattern Detection
|
|
304
|
-
// =============================================================================
|
|
305
|
-
|
|
306
|
-
function detectEstimationPatterns(outcomes: Outcome[]): {
|
|
307
|
-
overEstimated: EstimationPattern[]
|
|
308
|
-
underEstimated: EstimationPattern[]
|
|
309
|
-
} {
|
|
310
|
-
// Group by tags/categories
|
|
311
|
-
const byCategory = new Map<string, { variances: number[]; count: number }>()
|
|
312
|
-
|
|
313
|
-
for (const outcome of outcomes) {
|
|
314
|
-
if (!outcome.variance) continue
|
|
315
|
-
const variancePct = parseVariancePercent(outcome)
|
|
316
|
-
|
|
317
|
-
// Use tags as categories, fall back to 'uncategorized'
|
|
318
|
-
const categories = outcome.tags && outcome.tags.length > 0 ? outcome.tags : ['uncategorized']
|
|
319
|
-
|
|
320
|
-
for (const category of categories) {
|
|
321
|
-
if (!byCategory.has(category)) {
|
|
322
|
-
byCategory.set(category, { variances: [], count: 0 })
|
|
323
|
-
}
|
|
324
|
-
const entry = byCategory.get(category)!
|
|
325
|
-
entry.variances.push(variancePct)
|
|
326
|
-
entry.count++
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const overEstimated: EstimationPattern[] = []
|
|
331
|
-
const underEstimated: EstimationPattern[] = []
|
|
332
|
-
|
|
333
|
-
for (const [category, data] of byCategory) {
|
|
334
|
-
if (data.count < 2) continue // Need at least 2 data points
|
|
335
|
-
|
|
336
|
-
const avg = Math.round(data.variances.reduce((a, b) => a + b, 0) / data.variances.length)
|
|
337
|
-
|
|
338
|
-
if (avg > 10) {
|
|
339
|
-
// Actual took longer than estimated → under-estimated
|
|
340
|
-
underEstimated.push({ category, avgVariance: avg, taskCount: data.count })
|
|
341
|
-
} else if (avg < -10) {
|
|
342
|
-
// Actual was faster than estimated → over-estimated
|
|
343
|
-
overEstimated.push({ category, avgVariance: Math.abs(avg), taskCount: data.count })
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Sort by severity
|
|
348
|
-
overEstimated.sort((a, b) => b.avgVariance - a.avgVariance)
|
|
349
|
-
underEstimated.sort((a, b) => b.avgVariance - a.avgVariance)
|
|
350
|
-
|
|
351
|
-
return { overEstimated, underEstimated }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// =============================================================================
|
|
355
|
-
// Helpers
|
|
356
|
-
// =============================================================================
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Parse variance from an outcome into a percentage.
|
|
360
|
-
* Handles both string format ("+30m") and the presence of estimatedDuration.
|
|
361
|
-
*/
|
|
362
|
-
function parseVariancePercent(outcome: Outcome): number {
|
|
363
|
-
if (!outcome.variance) return 0
|
|
364
|
-
|
|
365
|
-
const estimated = parseDurationMinutes(outcome.estimatedDuration)
|
|
366
|
-
const actual = parseDurationMinutes(outcome.actualDuration)
|
|
367
|
-
|
|
368
|
-
if (estimated <= 0) return 0
|
|
369
|
-
|
|
370
|
-
return Math.round(((actual - estimated) / estimated) * 100)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Parse duration string to minutes.
|
|
375
|
-
* Supports: "2h", "30m", "1h 30m", "2h30m", "90m", "45s" (→ 1m)
|
|
376
|
-
*/
|
|
377
|
-
export function parseDurationMinutes(duration: string): number {
|
|
378
|
-
let minutes = 0
|
|
379
|
-
|
|
380
|
-
const hourMatch = duration.match(/(\d+)h/)
|
|
381
|
-
if (hourMatch) {
|
|
382
|
-
minutes += Number.parseInt(hourMatch[1], 10) * 60
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const minMatch = duration.match(/(\d+)m/)
|
|
386
|
-
if (minMatch) {
|
|
387
|
-
minutes += Number.parseInt(minMatch[1], 10)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const secMatch = duration.match(/(\d+)s/)
|
|
391
|
-
if (secMatch && minutes === 0) {
|
|
392
|
-
// Only count seconds if no hours/minutes (round up to 1 min)
|
|
393
|
-
minutes = 1
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return minutes
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Format velocity for LLM context injection.
|
|
401
|
-
*/
|
|
402
|
-
export function formatVelocityContext(metrics: VelocityMetrics): string {
|
|
403
|
-
if (metrics.sprints.length === 0) {
|
|
404
|
-
return 'No velocity data available yet.'
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const lines: string[] = []
|
|
408
|
-
lines.push(
|
|
409
|
-
`Project velocity: ${metrics.averageVelocity} pts/sprint (trend: ${metrics.velocityTrend})`
|
|
410
|
-
)
|
|
411
|
-
lines.push(`Estimation accuracy: ${metrics.estimationAccuracy}%`)
|
|
412
|
-
|
|
413
|
-
for (const pattern of metrics.underEstimated) {
|
|
414
|
-
lines.push(
|
|
415
|
-
`⚠ "${pattern.category}" tasks historically take ${pattern.avgVariance}% longer than estimated`
|
|
416
|
-
)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
for (const pattern of metrics.overEstimated) {
|
|
420
|
-
lines.push(
|
|
421
|
-
`"${pattern.category}" tasks typically finish ${pattern.avgVariance}% faster than estimated`
|
|
422
|
-
)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return lines.join('\n')
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/** Fibonacci points with typical minutes for points derivation */
|
|
429
|
-
const FIBONACCI_MINUTES: Array<{ points: number; typical: number }> = [
|
|
430
|
-
{ points: 1, typical: 10 },
|
|
431
|
-
{ points: 2, typical: 20 },
|
|
432
|
-
{ points: 3, typical: 45 },
|
|
433
|
-
{ points: 5, typical: 90 },
|
|
434
|
-
{ points: 8, typical: 180 },
|
|
435
|
-
{ points: 13, typical: 360 },
|
|
436
|
-
{ points: 21, typical: 720 },
|
|
437
|
-
]
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Derive story points from an outcome's estimated duration.
|
|
441
|
-
* Maps to nearest Fibonacci point using the standard points-to-minutes table.
|
|
442
|
-
*/
|
|
443
|
-
function derivePoints(outcome: Outcome): number {
|
|
444
|
-
if (!outcome.estimatedDuration) return 0
|
|
445
|
-
|
|
446
|
-
const minutes = parseDurationMinutes(outcome.estimatedDuration)
|
|
447
|
-
if (minutes <= 0) return 0
|
|
448
|
-
|
|
449
|
-
let closest = FIBONACCI_MINUTES[0]
|
|
450
|
-
let smallestDiff = Number.POSITIVE_INFINITY
|
|
451
|
-
|
|
452
|
-
for (const entry of FIBONACCI_MINUTES) {
|
|
453
|
-
const diff = Math.abs(entry.typical - minutes)
|
|
454
|
-
if (diff < smallestDiff) {
|
|
455
|
-
smallestDiff = diff
|
|
456
|
-
closest = entry
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return closest.points
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function resolveConfig(config: VelocityConfig): Required<VelocityConfig> {
|
|
464
|
-
return {
|
|
465
|
-
sprintLengthDays: config.sprintLengthDays ?? 7,
|
|
466
|
-
startDay: config.startDay ?? 'monday',
|
|
467
|
-
windowSize: config.windowSize ?? 6,
|
|
468
|
-
accuracyTolerance: config.accuracyTolerance ?? 20,
|
|
469
|
-
}
|
|
470
|
-
}
|