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,126 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import type { DetectedProjectCommands } from '../types'
|
|
3
|
-
import * as fileHelper from './file-helper'
|
|
4
|
-
|
|
5
|
-
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
|
|
6
|
-
|
|
7
|
-
interface PackageJson {
|
|
8
|
-
scripts?: Record<string, string>
|
|
9
|
-
packageManager?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Detect the most likely JS package manager for a project.
|
|
14
|
-
*
|
|
15
|
-
* Reason: installed users may not have Bun, and many projects use pnpm/yarn.
|
|
16
|
-
*/
|
|
17
|
-
async function detectPackageManager(
|
|
18
|
-
projectPath: string,
|
|
19
|
-
pkg: PackageJson | null
|
|
20
|
-
): Promise<PackageManager> {
|
|
21
|
-
const declared = pkg?.packageManager?.trim().toLowerCase()
|
|
22
|
-
if (declared?.startsWith('pnpm@')) return 'pnpm'
|
|
23
|
-
if (declared?.startsWith('yarn@')) return 'yarn'
|
|
24
|
-
if (declared?.startsWith('bun@')) return 'bun'
|
|
25
|
-
if (declared?.startsWith('npm@')) return 'npm'
|
|
26
|
-
|
|
27
|
-
// Lockfile heuristics
|
|
28
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
29
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'yarn.lock'))) return 'yarn'
|
|
30
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'bun.lockb'))) return 'bun'
|
|
31
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'bun.lock'))) return 'bun'
|
|
32
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'package-lock.json'))) return 'npm'
|
|
33
|
-
|
|
34
|
-
return 'npm'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function pmRun(pm: PackageManager, scriptName: string): string {
|
|
38
|
-
if (pm === 'yarn') return `yarn ${scriptName}`
|
|
39
|
-
if (pm === 'pnpm') return `pnpm run ${scriptName}`
|
|
40
|
-
if (pm === 'bun') return `bun run ${scriptName}`
|
|
41
|
-
return `npm run ${scriptName}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function pmTest(pm: PackageManager): string {
|
|
45
|
-
if (pm === 'yarn') return 'yarn test'
|
|
46
|
-
if (pm === 'pnpm') return 'pnpm test'
|
|
47
|
-
if (pm === 'bun') return 'bun test'
|
|
48
|
-
return 'npm test'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Detect the appropriate lint/typecheck/test commands for a given project.
|
|
53
|
-
*
|
|
54
|
-
* - JS/TS projects: uses the project package manager + scripts when present
|
|
55
|
-
* - Python: prefers pytest when config is present
|
|
56
|
-
* - Go/Rust/.NET/Java: uses conventional defaults
|
|
57
|
-
*
|
|
58
|
-
* @param {string} projectPath - Repository root.
|
|
59
|
-
* @returns {Promise<DetectedProjectCommands>} detected commands (missing when not applicable).
|
|
60
|
-
*/
|
|
61
|
-
export async function detectProjectCommands(projectPath: string): Promise<DetectedProjectCommands> {
|
|
62
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
63
|
-
const pkg = await fileHelper.readJson<PackageJson | null>(pkgPath, null)
|
|
64
|
-
|
|
65
|
-
// JS/TS (prefer explicit scripts)
|
|
66
|
-
if (pkg) {
|
|
67
|
-
const pm = await detectPackageManager(projectPath, pkg)
|
|
68
|
-
const scripts = pkg.scripts || {}
|
|
69
|
-
|
|
70
|
-
const result: DetectedProjectCommands = { stack: 'js', packageManager: pm }
|
|
71
|
-
|
|
72
|
-
if (scripts.lint) {
|
|
73
|
-
result.lint = { tool: pm, command: pmRun(pm, 'lint') }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (scripts.typecheck) {
|
|
77
|
-
result.typecheck = { tool: pm, command: pmRun(pm, 'typecheck') }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (scripts.test) {
|
|
81
|
-
result.test = { tool: pm, command: pmTest(pm) }
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return result
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Python
|
|
88
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'pytest.ini'))) {
|
|
89
|
-
return { stack: 'python', test: { tool: 'pytest', command: 'pytest' } }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const pyproject = await fileHelper.readFile(path.join(projectPath, 'pyproject.toml'), '')
|
|
93
|
-
if (pyproject.includes('[tool.pytest') || pyproject.includes('pytest')) {
|
|
94
|
-
return { stack: 'python', test: { tool: 'pytest', command: 'pytest' } }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Rust
|
|
98
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'Cargo.toml'))) {
|
|
99
|
-
return { stack: 'rust', test: { tool: 'cargo', command: 'cargo test' } }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Go
|
|
103
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'go.mod'))) {
|
|
104
|
-
return { stack: 'go', test: { tool: 'go', command: 'go test ./...' } }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// .NET
|
|
108
|
-
const files = await fileHelper.listFiles(projectPath)
|
|
109
|
-
if (files.some((f) => f.endsWith('.sln') || f.endsWith('.csproj') || f.endsWith('.fsproj'))) {
|
|
110
|
-
return { stack: 'dotnet', test: { tool: 'dotnet', command: 'dotnet test' } }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Java
|
|
114
|
-
if (await fileHelper.fileExists(path.join(projectPath, 'pom.xml'))) {
|
|
115
|
-
return { stack: 'java', test: { tool: 'maven', command: 'mvn test' } }
|
|
116
|
-
}
|
|
117
|
-
if (
|
|
118
|
-
(await fileHelper.fileExists(path.join(projectPath, 'gradlew'))) &&
|
|
119
|
-
((await fileHelper.fileExists(path.join(projectPath, 'build.gradle'))) ||
|
|
120
|
-
(await fileHelper.fileExists(path.join(projectPath, 'build.gradle.kts'))))
|
|
121
|
-
) {
|
|
122
|
-
return { stack: 'java', test: { tool: 'gradle', command: './gradlew test' } }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return { stack: 'unknown' }
|
|
126
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-project credential storage
|
|
3
|
-
*
|
|
4
|
-
* Stores credentials in: ~/.prjct-cli/projects/{projectId}/config/credentials.json
|
|
5
|
-
*
|
|
6
|
-
* Fallback chain for Linear API key:
|
|
7
|
-
* 1. Project credentials (per-project)
|
|
8
|
-
* 2. Global keychain (macOS)
|
|
9
|
-
* 3. Environment variables
|
|
10
|
-
*
|
|
11
|
-
* This allows different projects to use different Linear workspaces.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from 'node:fs/promises'
|
|
15
|
-
import os from 'node:os'
|
|
16
|
-
import path from 'node:path'
|
|
17
|
-
import { getErrorMessage } from '../types/fs'
|
|
18
|
-
import { fileExists } from './fs-helpers'
|
|
19
|
-
import { type CredentialKey, getCredential } from './keychain'
|
|
20
|
-
|
|
21
|
-
interface LinearCredentials {
|
|
22
|
-
apiKey: string
|
|
23
|
-
teamId?: string
|
|
24
|
-
teamKey?: string
|
|
25
|
-
setupAt: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ProjectCredentials {
|
|
29
|
-
linear?: LinearCredentials
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get path to project credentials file
|
|
34
|
-
*/
|
|
35
|
-
function getCredentialsPath(projectId: string): string {
|
|
36
|
-
return path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'config', 'credentials.json')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get all credentials for a project
|
|
41
|
-
*/
|
|
42
|
-
export async function getProjectCredentials(projectId: string): Promise<ProjectCredentials> {
|
|
43
|
-
const credPath = getCredentialsPath(projectId)
|
|
44
|
-
if (!(await fileExists(credPath))) {
|
|
45
|
-
return {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
return JSON.parse(await fs.readFile(credPath, 'utf-8'))
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error('[project-credentials] Failed to read credentials:', getErrorMessage(error))
|
|
52
|
-
return {}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Set Linear credentials for a project
|
|
58
|
-
*/
|
|
59
|
-
export async function setLinearCredentials(
|
|
60
|
-
projectId: string,
|
|
61
|
-
credentials: LinearCredentials
|
|
62
|
-
): Promise<void> {
|
|
63
|
-
const credPath = getCredentialsPath(projectId)
|
|
64
|
-
const dir = path.dirname(credPath)
|
|
65
|
-
|
|
66
|
-
// Ensure directory exists
|
|
67
|
-
if (!(await fileExists(dir))) {
|
|
68
|
-
await fs.mkdir(dir, { recursive: true })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Read existing, merge, write
|
|
72
|
-
const current = await getProjectCredentials(projectId)
|
|
73
|
-
current.linear = credentials
|
|
74
|
-
await fs.writeFile(credPath, JSON.stringify(current, null, 2))
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Delete Linear credentials for a project
|
|
79
|
-
*/
|
|
80
|
-
export async function deleteLinearCredentials(projectId: string): Promise<void> {
|
|
81
|
-
const current = await getProjectCredentials(projectId)
|
|
82
|
-
|
|
83
|
-
if (current.linear) {
|
|
84
|
-
delete current.linear
|
|
85
|
-
const credPath = getCredentialsPath(projectId)
|
|
86
|
-
await fs.writeFile(credPath, JSON.stringify(current, null, 2))
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get Linear API key with fallback chain:
|
|
92
|
-
* 1. Project credentials
|
|
93
|
-
* 2. Global keychain
|
|
94
|
-
* 3. Environment variable
|
|
95
|
-
*/
|
|
96
|
-
export async function getLinearApiKey(projectId: string): Promise<string | null> {
|
|
97
|
-
// 1. Project credentials
|
|
98
|
-
const projectCreds = await getProjectCredentials(projectId)
|
|
99
|
-
if (projectCreds.linear?.apiKey) {
|
|
100
|
-
return projectCreds.linear.apiKey
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 2. Global keychain (falls back to env var internally)
|
|
104
|
-
return getCredential('linear-api-key' as CredentialKey)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get Linear team ID from project credentials
|
|
109
|
-
*/
|
|
110
|
-
export async function getLinearTeamId(projectId: string): Promise<string | undefined> {
|
|
111
|
-
const projectCreds = await getProjectCredentials(projectId)
|
|
112
|
-
return projectCreds.linear?.teamId
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Check if Linear is configured for a project
|
|
117
|
-
*/
|
|
118
|
-
export async function isLinearConfigured(projectId: string): Promise<boolean> {
|
|
119
|
-
const apiKey = await getLinearApiKey(projectId)
|
|
120
|
-
return apiKey !== null && apiKey.length > 0
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get credential source for debugging
|
|
125
|
-
*/
|
|
126
|
-
export async function getCredentialSource(
|
|
127
|
-
projectId: string
|
|
128
|
-
): Promise<'project' | 'keychain' | 'env' | 'none'> {
|
|
129
|
-
// Check project credentials
|
|
130
|
-
const projectCreds = await getProjectCredentials(projectId)
|
|
131
|
-
if (projectCreds.linear?.apiKey) {
|
|
132
|
-
return 'project'
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check keychain/env
|
|
136
|
-
const { getCredentialWithSource } = await import('./keychain')
|
|
137
|
-
const result = await getCredentialWithSource('linear-api-key' as CredentialKey)
|
|
138
|
-
if (result.value) {
|
|
139
|
-
return result.source === 'keychain' ? 'keychain' : 'env'
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return 'none'
|
|
143
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import type { ProviderDetectionResult } from '../types/provider'
|
|
5
|
-
|
|
6
|
-
const CACHE_DIR = path.join(os.homedir(), '.prjct-cli', 'cache')
|
|
7
|
-
const CACHE_FILE = path.join(CACHE_DIR, 'providers.json')
|
|
8
|
-
const TTL_MS = 10 * 60 * 1000 // 10 minutes
|
|
9
|
-
|
|
10
|
-
interface ProviderCache {
|
|
11
|
-
timestamp: string
|
|
12
|
-
detection: {
|
|
13
|
-
claude: ProviderDetectionResult
|
|
14
|
-
gemini: ProviderDetectionResult
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function readProviderCache(): Promise<ProviderCache['detection'] | null> {
|
|
19
|
-
try {
|
|
20
|
-
const raw = await fs.readFile(CACHE_FILE, 'utf-8')
|
|
21
|
-
const cache: ProviderCache = JSON.parse(raw)
|
|
22
|
-
|
|
23
|
-
if (!cache.timestamp || !cache.detection) return null
|
|
24
|
-
|
|
25
|
-
const age = Date.now() - new Date(cache.timestamp).getTime()
|
|
26
|
-
if (age > TTL_MS) return null
|
|
27
|
-
|
|
28
|
-
return cache.detection
|
|
29
|
-
} catch {
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function writeProviderCache(detection: ProviderCache['detection']): Promise<void> {
|
|
35
|
-
const cache: ProviderCache = {
|
|
36
|
-
timestamp: new Date().toISOString(),
|
|
37
|
-
detection,
|
|
38
|
-
}
|
|
39
|
-
await fs.mkdir(CACHE_DIR, { recursive: true })
|
|
40
|
-
await fs.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function invalidateProviderCache(): Promise<void> {
|
|
44
|
-
try {
|
|
45
|
-
await fs.unlink(CACHE_FILE)
|
|
46
|
-
} catch {
|
|
47
|
-
// File doesn't exist — fine
|
|
48
|
-
}
|
|
49
|
-
}
|
package/core/utils/retry.ts
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retry Policy Utility
|
|
3
|
-
*
|
|
4
|
-
* Provides exponential backoff retry logic with error classification and circuit breaker.
|
|
5
|
-
* Used to make agent and tool operations resilient against transient failures.
|
|
6
|
-
*
|
|
7
|
-
* @module utils/retry
|
|
8
|
-
* @version 1.0.0
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// =============================================================================
|
|
12
|
-
// Types
|
|
13
|
-
// =============================================================================
|
|
14
|
-
|
|
15
|
-
export interface RetryOptions {
|
|
16
|
-
/** Maximum number of retry attempts (default: 3) */
|
|
17
|
-
maxAttempts: number
|
|
18
|
-
|
|
19
|
-
/** Base delay in milliseconds for exponential backoff (default: 1000) */
|
|
20
|
-
baseDelayMs: number
|
|
21
|
-
|
|
22
|
-
/** Maximum delay in milliseconds (default: 8000) */
|
|
23
|
-
maxDelayMs: number
|
|
24
|
-
|
|
25
|
-
/** Number of consecutive failures before opening circuit (default: 5) */
|
|
26
|
-
circuitBreakerThreshold?: number
|
|
27
|
-
|
|
28
|
-
/** Time in milliseconds to keep circuit open (default: 60000) */
|
|
29
|
-
circuitBreakerTimeoutMs?: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CircuitState {
|
|
33
|
-
consecutiveFailures: number
|
|
34
|
-
openedAt: number | null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// =============================================================================
|
|
38
|
-
// Error Classification
|
|
39
|
-
// =============================================================================
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Node.js error codes that indicate transient failures worth retrying
|
|
43
|
-
*/
|
|
44
|
-
const TRANSIENT_ERROR_CODES = new Set([
|
|
45
|
-
'EBUSY', // Resource busy
|
|
46
|
-
'EAGAIN', // Resource temporarily unavailable
|
|
47
|
-
'ETIMEDOUT', // Operation timed out
|
|
48
|
-
'ECONNRESET', // Connection reset by peer
|
|
49
|
-
'ECONNREFUSED', // Connection refused (may be temporary)
|
|
50
|
-
'ENOTFOUND', // DNS lookup failed (may be temporary)
|
|
51
|
-
'EAI_AGAIN', // DNS temporary failure
|
|
52
|
-
])
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Node.js error codes that indicate permanent failures (fail fast)
|
|
56
|
-
*/
|
|
57
|
-
const PERMANENT_ERROR_CODES = new Set([
|
|
58
|
-
'ENOENT', // No such file or directory
|
|
59
|
-
'EACCES', // Permission denied
|
|
60
|
-
'EPERM', // Operation not permitted
|
|
61
|
-
'EISDIR', // Is a directory
|
|
62
|
-
'ENOTDIR', // Not a directory
|
|
63
|
-
'EINVAL', // Invalid argument
|
|
64
|
-
])
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Check if an error is transient (worth retrying)
|
|
68
|
-
*/
|
|
69
|
-
export function isTransientError(error: unknown): boolean {
|
|
70
|
-
if (!error || typeof error !== 'object') {
|
|
71
|
-
return false
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const err = error as { code?: string; errno?: number; message?: string }
|
|
75
|
-
|
|
76
|
-
// Check error code
|
|
77
|
-
if (err.code && TRANSIENT_ERROR_CODES.has(err.code)) {
|
|
78
|
-
return true
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Permanent errors should never be retried
|
|
82
|
-
if (err.code && PERMANENT_ERROR_CODES.has(err.code)) {
|
|
83
|
-
return false
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check message for timeout indicators
|
|
87
|
-
if (err.message) {
|
|
88
|
-
const msg = err.message.toLowerCase()
|
|
89
|
-
if (msg.includes('timeout') || msg.includes('timed out')) {
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Unknown errors are not retried by default (fail fast)
|
|
95
|
-
return false
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Check if an error is permanent (should not retry)
|
|
100
|
-
*/
|
|
101
|
-
export function isPermanentError(error: unknown): boolean {
|
|
102
|
-
if (!error || typeof error !== 'object') {
|
|
103
|
-
return false
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const err = error as { code?: string }
|
|
107
|
-
return !!(err.code && PERMANENT_ERROR_CODES.has(err.code))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// =============================================================================
|
|
111
|
-
// Circuit Breaker
|
|
112
|
-
// =============================================================================
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Circuit breaker state registry (per operation ID)
|
|
116
|
-
*/
|
|
117
|
-
const circuitStates = new Map<string, CircuitState>()
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Check if circuit is open for a given operation
|
|
121
|
-
*/
|
|
122
|
-
function isCircuitOpen(operationId: string, threshold: number, timeoutMs: number): boolean {
|
|
123
|
-
const state = circuitStates.get(operationId)
|
|
124
|
-
if (!state) {
|
|
125
|
-
return false
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Circuit is open if threshold exceeded
|
|
129
|
-
if (state.consecutiveFailures >= threshold && state.openedAt) {
|
|
130
|
-
const elapsed = Date.now() - state.openedAt
|
|
131
|
-
// Circuit closes after timeout
|
|
132
|
-
if (elapsed >= timeoutMs) {
|
|
133
|
-
// Reset circuit
|
|
134
|
-
circuitStates.delete(operationId)
|
|
135
|
-
return false
|
|
136
|
-
}
|
|
137
|
-
return true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return false
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Record a failure for circuit breaker
|
|
145
|
-
*/
|
|
146
|
-
function recordFailure(operationId: string, threshold: number): void {
|
|
147
|
-
const state = circuitStates.get(operationId) || {
|
|
148
|
-
consecutiveFailures: 0,
|
|
149
|
-
openedAt: null,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
state.consecutiveFailures++
|
|
153
|
-
|
|
154
|
-
// Open circuit if threshold reached
|
|
155
|
-
if (state.consecutiveFailures >= threshold && !state.openedAt) {
|
|
156
|
-
state.openedAt = Date.now()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
circuitStates.set(operationId, state)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Record a success (reset circuit breaker)
|
|
164
|
-
*/
|
|
165
|
-
function recordSuccess(operationId: string): void {
|
|
166
|
-
circuitStates.delete(operationId)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// =============================================================================
|
|
170
|
-
// Retry Policy
|
|
171
|
-
// =============================================================================
|
|
172
|
-
|
|
173
|
-
export class RetryPolicy {
|
|
174
|
-
private options: Required<RetryOptions>
|
|
175
|
-
|
|
176
|
-
constructor(options: Partial<RetryOptions> = {}) {
|
|
177
|
-
this.options = {
|
|
178
|
-
maxAttempts: options.maxAttempts ?? 3,
|
|
179
|
-
baseDelayMs: options.baseDelayMs ?? 1000,
|
|
180
|
-
maxDelayMs: options.maxDelayMs ?? 8000,
|
|
181
|
-
circuitBreakerThreshold: options.circuitBreakerThreshold ?? 5,
|
|
182
|
-
circuitBreakerTimeoutMs: options.circuitBreakerTimeoutMs ?? 60000,
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Execute an operation with retry logic
|
|
188
|
-
*
|
|
189
|
-
* @param operation - Async function to execute
|
|
190
|
-
* @param operationId - Optional ID for circuit breaker tracking
|
|
191
|
-
* @returns Result of the operation
|
|
192
|
-
* @throws Error if all attempts fail or circuit is open
|
|
193
|
-
*/
|
|
194
|
-
async execute<T>(operation: () => Promise<T>, operationId: string = 'default'): Promise<T> {
|
|
195
|
-
// Check circuit breaker
|
|
196
|
-
if (
|
|
197
|
-
isCircuitOpen(
|
|
198
|
-
operationId,
|
|
199
|
-
this.options.circuitBreakerThreshold,
|
|
200
|
-
this.options.circuitBreakerTimeoutMs
|
|
201
|
-
)
|
|
202
|
-
) {
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Circuit breaker is open for operation: ${operationId}. Too many consecutive failures.`
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
let lastError: unknown
|
|
209
|
-
let attempt = 0
|
|
210
|
-
|
|
211
|
-
while (attempt < this.options.maxAttempts) {
|
|
212
|
-
try {
|
|
213
|
-
const result = await operation()
|
|
214
|
-
// Success - reset circuit breaker
|
|
215
|
-
recordSuccess(operationId)
|
|
216
|
-
return result
|
|
217
|
-
} catch (error) {
|
|
218
|
-
lastError = error
|
|
219
|
-
attempt++
|
|
220
|
-
|
|
221
|
-
// Check if error is permanent (fail fast)
|
|
222
|
-
if (isPermanentError(error)) {
|
|
223
|
-
recordFailure(operationId, this.options.circuitBreakerThreshold)
|
|
224
|
-
throw error
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check if error is transient and we have attempts left
|
|
228
|
-
const shouldRetry = isTransientError(error) && attempt < this.options.maxAttempts
|
|
229
|
-
|
|
230
|
-
if (!shouldRetry) {
|
|
231
|
-
// Not transient or out of attempts
|
|
232
|
-
recordFailure(operationId, this.options.circuitBreakerThreshold)
|
|
233
|
-
throw error
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Calculate delay with exponential backoff
|
|
237
|
-
const delay = Math.min(
|
|
238
|
-
this.options.baseDelayMs * 2 ** (attempt - 1),
|
|
239
|
-
this.options.maxDelayMs
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
// Wait before retry
|
|
243
|
-
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// All attempts failed
|
|
248
|
-
recordFailure(operationId, this.options.circuitBreakerThreshold)
|
|
249
|
-
throw lastError
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Check if an error is transient (exposed for testing)
|
|
254
|
-
*/
|
|
255
|
-
isTransientError(error: unknown): boolean {
|
|
256
|
-
return isTransientError(error)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Check if circuit is open for an operation (exposed for testing)
|
|
261
|
-
*/
|
|
262
|
-
isCircuitOpen(operationId: string): boolean {
|
|
263
|
-
return isCircuitOpen(
|
|
264
|
-
operationId,
|
|
265
|
-
this.options.circuitBreakerThreshold,
|
|
266
|
-
this.options.circuitBreakerTimeoutMs
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Get current circuit state for an operation (exposed for testing)
|
|
272
|
-
*/
|
|
273
|
-
getCircuitState(operationId: string): CircuitState | undefined {
|
|
274
|
-
return circuitStates.get(operationId)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Reset circuit breaker for an operation (exposed for testing)
|
|
279
|
-
*/
|
|
280
|
-
resetCircuit(operationId: string): void {
|
|
281
|
-
circuitStates.delete(operationId)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Reset all circuit breakers (exposed for testing)
|
|
286
|
-
*/
|
|
287
|
-
resetAllCircuits(): void {
|
|
288
|
-
circuitStates.clear()
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// =============================================================================
|
|
293
|
-
// Exports
|
|
294
|
-
// =============================================================================
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Default retry policy for agent operations
|
|
298
|
-
* - 3 attempts
|
|
299
|
-
* - 1s base delay
|
|
300
|
-
* - Up to 8s max delay
|
|
301
|
-
*/
|
|
302
|
-
export const defaultAgentRetryPolicy = new RetryPolicy({
|
|
303
|
-
maxAttempts: 3,
|
|
304
|
-
baseDelayMs: 1000,
|
|
305
|
-
maxDelayMs: 8000,
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Retry policy for tool operations (less aggressive)
|
|
310
|
-
* - 2 attempts
|
|
311
|
-
* - 500ms base delay
|
|
312
|
-
* - Up to 2s max delay
|
|
313
|
-
*/
|
|
314
|
-
export const defaultToolRetryPolicy = new RetryPolicy({
|
|
315
|
-
maxAttempts: 2,
|
|
316
|
-
baseDelayMs: 500,
|
|
317
|
-
maxDelayMs: 2000,
|
|
318
|
-
})
|