prjct-cli 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retry Policy Tests
|
|
3
|
-
* Tests for exponential backoff, error classification, and circuit breaker
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
7
|
-
import {
|
|
8
|
-
defaultAgentRetryPolicy,
|
|
9
|
-
defaultToolRetryPolicy,
|
|
10
|
-
isPermanentError,
|
|
11
|
-
isTransientError,
|
|
12
|
-
RetryPolicy,
|
|
13
|
-
} from '../../utils/retry'
|
|
14
|
-
|
|
15
|
-
describe('RetryPolicy', () => {
|
|
16
|
-
let policy: RetryPolicy
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
policy = new RetryPolicy({
|
|
20
|
-
maxAttempts: 3,
|
|
21
|
-
baseDelayMs: 100, // Shorter delays for tests
|
|
22
|
-
maxDelayMs: 400,
|
|
23
|
-
circuitBreakerThreshold: 5,
|
|
24
|
-
circuitBreakerTimeoutMs: 1000,
|
|
25
|
-
})
|
|
26
|
-
// Reset all circuits before each test
|
|
27
|
-
policy.resetAllCircuits()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
policy.resetAllCircuits()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
describe('Error Classification', () => {
|
|
35
|
-
it('should identify transient errors correctly', () => {
|
|
36
|
-
const transientErrors = [
|
|
37
|
-
{ code: 'EBUSY' },
|
|
38
|
-
{ code: 'EAGAIN' },
|
|
39
|
-
{ code: 'ETIMEDOUT' },
|
|
40
|
-
{ code: 'ECONNRESET' },
|
|
41
|
-
{ code: 'ECONNREFUSED' },
|
|
42
|
-
{ message: 'Operation timed out' },
|
|
43
|
-
{ message: 'Request timeout' },
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
for (const error of transientErrors) {
|
|
47
|
-
expect(isTransientError(error)).toBe(true)
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should identify permanent errors correctly', () => {
|
|
52
|
-
const permanentErrors = [
|
|
53
|
-
{ code: 'ENOENT' },
|
|
54
|
-
{ code: 'EACCES' },
|
|
55
|
-
{ code: 'EPERM' },
|
|
56
|
-
{ code: 'EISDIR' },
|
|
57
|
-
{ code: 'ENOTDIR' },
|
|
58
|
-
{ code: 'EINVAL' },
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
for (const error of permanentErrors) {
|
|
62
|
-
expect(isPermanentError(error)).toBe(true)
|
|
63
|
-
expect(isTransientError(error)).toBe(false)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('should not classify unknown errors as transient', () => {
|
|
68
|
-
const unknownErrors = [
|
|
69
|
-
{ code: 'UNKNOWN' },
|
|
70
|
-
{ message: 'Unknown error' },
|
|
71
|
-
new Error('Generic error'),
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
for (const error of unknownErrors) {
|
|
75
|
-
expect(isTransientError(error)).toBe(false)
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('Successful Operations', () => {
|
|
81
|
-
it('should execute successful operation without retry', async () => {
|
|
82
|
-
let attempts = 0
|
|
83
|
-
const operation = async () => {
|
|
84
|
-
attempts++
|
|
85
|
-
return 'success'
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const result = await policy.execute(operation, 'test-op')
|
|
89
|
-
|
|
90
|
-
expect(result).toBe('success')
|
|
91
|
-
expect(attempts).toBe(1)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should reset circuit breaker after success', async () => {
|
|
95
|
-
// Force some failures to increment circuit state
|
|
96
|
-
let failCount = 0
|
|
97
|
-
const failOperation = async () => {
|
|
98
|
-
failCount++
|
|
99
|
-
throw { code: 'EBUSY' }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
await policy.execute(failOperation, 'test-op')
|
|
104
|
-
} catch {
|
|
105
|
-
// Expected to fail
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
expect(failCount).toBe(3) // maxAttempts
|
|
109
|
-
|
|
110
|
-
// Now succeed - should reset circuit
|
|
111
|
-
const successOperation = async () => 'success'
|
|
112
|
-
await policy.execute(successOperation, 'test-op')
|
|
113
|
-
|
|
114
|
-
const circuitState = policy.getCircuitState('test-op')
|
|
115
|
-
expect(circuitState).toBeUndefined()
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
describe('Transient Error Retry', () => {
|
|
120
|
-
it('should retry transient errors and succeed', async () => {
|
|
121
|
-
let attempts = 0
|
|
122
|
-
const operation = async () => {
|
|
123
|
-
attempts++
|
|
124
|
-
if (attempts < 3) {
|
|
125
|
-
throw { code: 'EBUSY' } // Transient error
|
|
126
|
-
}
|
|
127
|
-
return 'success'
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const result = await policy.execute(operation, 'test-op')
|
|
131
|
-
|
|
132
|
-
expect(result).toBe('success')
|
|
133
|
-
expect(attempts).toBe(3)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('should apply exponential backoff between retries', async () => {
|
|
137
|
-
const timestamps: number[] = []
|
|
138
|
-
let attempts = 0
|
|
139
|
-
|
|
140
|
-
const operation = async () => {
|
|
141
|
-
timestamps.push(Date.now())
|
|
142
|
-
attempts++
|
|
143
|
-
if (attempts < 3) {
|
|
144
|
-
throw { code: 'ETIMEDOUT' }
|
|
145
|
-
}
|
|
146
|
-
return 'success'
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
await policy.execute(operation, 'test-op')
|
|
150
|
-
|
|
151
|
-
// Check delays: should be ~100ms, ~200ms (with some tolerance)
|
|
152
|
-
const delay1 = timestamps[1] - timestamps[0]
|
|
153
|
-
const delay2 = timestamps[2] - timestamps[1]
|
|
154
|
-
|
|
155
|
-
expect(delay1).toBeGreaterThanOrEqual(90) // 100ms with tolerance
|
|
156
|
-
expect(delay1).toBeLessThan(150)
|
|
157
|
-
|
|
158
|
-
expect(delay2).toBeGreaterThanOrEqual(180) // 200ms with tolerance
|
|
159
|
-
expect(delay2).toBeLessThan(250)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should respect maxDelayMs cap', async () => {
|
|
163
|
-
const policy = new RetryPolicy({
|
|
164
|
-
maxAttempts: 5,
|
|
165
|
-
baseDelayMs: 100,
|
|
166
|
-
maxDelayMs: 200,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
const timestamps: number[] = []
|
|
170
|
-
let attempts = 0
|
|
171
|
-
|
|
172
|
-
const operation = async () => {
|
|
173
|
-
timestamps.push(Date.now())
|
|
174
|
-
attempts++
|
|
175
|
-
if (attempts < 5) {
|
|
176
|
-
throw { code: 'EBUSY' }
|
|
177
|
-
}
|
|
178
|
-
return 'success'
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
await policy.execute(operation, 'test-op')
|
|
182
|
-
|
|
183
|
-
// Last delay should not exceed maxDelayMs (check delay between attempt 3 and 4)
|
|
184
|
-
// Attempt 1: no delay
|
|
185
|
-
// Attempt 2: 100ms delay (baseDelayMs * 2^0)
|
|
186
|
-
// Attempt 3: 200ms delay (baseDelayMs * 2^1, capped at maxDelayMs)
|
|
187
|
-
// Attempt 4: 200ms delay (baseDelayMs * 2^2 = 400ms, capped at 200ms)
|
|
188
|
-
const delay3 = timestamps[3] - timestamps[2]
|
|
189
|
-
expect(delay3).toBeLessThanOrEqual(250) // 200ms + tolerance
|
|
190
|
-
expect(delay3).toBeGreaterThanOrEqual(180)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('should throw if all retry attempts fail with transient error', async () => {
|
|
194
|
-
let attempts = 0
|
|
195
|
-
const operation = async () => {
|
|
196
|
-
attempts++
|
|
197
|
-
throw { code: 'EAGAIN' }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
await expect(policy.execute(operation, 'test-op')).rejects.toMatchObject({
|
|
201
|
-
code: 'EAGAIN',
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
expect(attempts).toBe(3) // maxAttempts
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
describe('Permanent Error Handling', () => {
|
|
209
|
-
it('should fail fast on permanent errors without retry', async () => {
|
|
210
|
-
let attempts = 0
|
|
211
|
-
const operation = async () => {
|
|
212
|
-
attempts++
|
|
213
|
-
throw { code: 'ENOENT' } // Permanent error
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
await expect(policy.execute(operation, 'test-op')).rejects.toMatchObject({
|
|
217
|
-
code: 'ENOENT',
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
expect(attempts).toBe(1) // No retry
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should fail fast on permission denied', async () => {
|
|
224
|
-
let attempts = 0
|
|
225
|
-
const operation = async () => {
|
|
226
|
-
attempts++
|
|
227
|
-
throw { code: 'EPERM' }
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
await expect(policy.execute(operation, 'test-op')).rejects.toMatchObject({
|
|
231
|
-
code: 'EPERM',
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
expect(attempts).toBe(1)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('should record failure for permanent errors', async () => {
|
|
238
|
-
const operation = async () => {
|
|
239
|
-
throw { code: 'ENOENT' }
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
await policy.execute(operation, 'perm-op')
|
|
244
|
-
} catch {
|
|
245
|
-
// Expected
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const state = policy.getCircuitState('perm-op')
|
|
249
|
-
expect(state?.consecutiveFailures).toBe(1)
|
|
250
|
-
})
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
describe('Circuit Breaker', () => {
|
|
254
|
-
it('should open circuit after threshold failures', async () => {
|
|
255
|
-
const operation = async () => {
|
|
256
|
-
throw { code: 'EBUSY' }
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Execute 5 times to reach threshold (each attempt counts as 1 failure)
|
|
260
|
-
for (let i = 0; i < 5; i++) {
|
|
261
|
-
try {
|
|
262
|
-
await policy.execute(operation, 'circuit-op')
|
|
263
|
-
} catch {
|
|
264
|
-
// Expected
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Circuit should now be open
|
|
269
|
-
expect(policy.isCircuitOpen('circuit-op')).toBe(true)
|
|
270
|
-
|
|
271
|
-
// Next call should fail immediately with circuit breaker error
|
|
272
|
-
await expect(policy.execute(operation, 'circuit-op')).rejects.toThrow(
|
|
273
|
-
/Circuit breaker is open/
|
|
274
|
-
)
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
it('should close circuit after timeout', async () => {
|
|
278
|
-
const policy = new RetryPolicy({
|
|
279
|
-
maxAttempts: 3,
|
|
280
|
-
baseDelayMs: 10,
|
|
281
|
-
maxDelayMs: 50,
|
|
282
|
-
circuitBreakerThreshold: 3,
|
|
283
|
-
circuitBreakerTimeoutMs: 100, // Short timeout for test
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
const operation = async () => {
|
|
287
|
-
throw { code: 'ETIMEDOUT' }
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Trigger circuit breaker
|
|
291
|
-
for (let i = 0; i < 3; i++) {
|
|
292
|
-
try {
|
|
293
|
-
await policy.execute(operation, 'timeout-op')
|
|
294
|
-
} catch {
|
|
295
|
-
// Expected
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
expect(policy.isCircuitOpen('timeout-op')).toBe(true)
|
|
300
|
-
|
|
301
|
-
// Wait for timeout
|
|
302
|
-
await new Promise((resolve) => setTimeout(resolve, 150))
|
|
303
|
-
|
|
304
|
-
// Circuit should be closed now
|
|
305
|
-
expect(policy.isCircuitOpen('timeout-op')).toBe(false)
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
it('should track failures per operation independently', async () => {
|
|
309
|
-
const operation = async () => {
|
|
310
|
-
throw { code: 'EAGAIN' }
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Fail operation A multiple times
|
|
314
|
-
for (let i = 0; i < 3; i++) {
|
|
315
|
-
try {
|
|
316
|
-
await policy.execute(operation, 'op-a')
|
|
317
|
-
} catch {
|
|
318
|
-
// Expected
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const stateA = policy.getCircuitState('op-a')
|
|
323
|
-
const stateB = policy.getCircuitState('op-b')
|
|
324
|
-
|
|
325
|
-
expect(stateA?.consecutiveFailures).toBe(3)
|
|
326
|
-
expect(stateB).toBeUndefined()
|
|
327
|
-
})
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
describe('Default Policies', () => {
|
|
331
|
-
it('should have agent retry policy with correct defaults', () => {
|
|
332
|
-
// Test that default agent policy is configured correctly
|
|
333
|
-
expect(defaultAgentRetryPolicy).toBeInstanceOf(RetryPolicy)
|
|
334
|
-
// We can't directly inspect options, but we can test behavior
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
it('should have tool retry policy with correct defaults', () => {
|
|
338
|
-
expect(defaultToolRetryPolicy).toBeInstanceOf(RetryPolicy)
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
it('should retry agent operations 3 times', async () => {
|
|
342
|
-
let attempts = 0
|
|
343
|
-
const operation = async () => {
|
|
344
|
-
attempts++
|
|
345
|
-
if (attempts < 3) {
|
|
346
|
-
throw { code: 'EBUSY' }
|
|
347
|
-
}
|
|
348
|
-
return 'success'
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
await defaultAgentRetryPolicy.execute(operation, 'agent-test')
|
|
352
|
-
expect(attempts).toBe(3)
|
|
353
|
-
})
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
describe('Edge Cases', () => {
|
|
357
|
-
it('should handle non-Error objects', async () => {
|
|
358
|
-
const operation = async () => {
|
|
359
|
-
throw 'string error'
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
await expect(policy.execute(operation, 'edge-op')).rejects.toBe('string error')
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
it('should handle null/undefined errors', async () => {
|
|
366
|
-
const operation = async () => {
|
|
367
|
-
throw null
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
await expect(policy.execute(operation, 'null-op')).rejects.toBeNull()
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
it('should handle errors without code property', async () => {
|
|
374
|
-
const operation = async () => {
|
|
375
|
-
throw new Error('Generic error')
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
await expect(policy.execute(operation, 'generic-op')).rejects.toThrow('Generic error')
|
|
379
|
-
})
|
|
380
|
-
})
|
|
381
|
-
})
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Machine Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for workflow state machine transitions:
|
|
5
|
-
* - All valid transitions work
|
|
6
|
-
* - Invalid transitions are rejected
|
|
7
|
-
* - New transitions: completed→paused, paused→shipped, completed→reopen
|
|
8
|
-
* - getCurrentState detects paused tasks from pausedTasks array
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, expect, it } from 'bun:test'
|
|
12
|
-
import type { WorkflowCommand, WorkflowState } from '../../workflow/state-machine'
|
|
13
|
-
import { WorkflowStateMachine } from '../../workflow/state-machine'
|
|
14
|
-
|
|
15
|
-
const sm = new WorkflowStateMachine()
|
|
16
|
-
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// getCurrentState
|
|
19
|
-
// =============================================================================
|
|
20
|
-
|
|
21
|
-
describe('getCurrentState', () => {
|
|
22
|
-
it('returns idle when no task and no paused tasks', () => {
|
|
23
|
-
expect(sm.getCurrentState({ currentTask: null })).toBe('idle')
|
|
24
|
-
expect(sm.getCurrentState({})).toBe('idle')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('returns working for in_progress status', () => {
|
|
28
|
-
expect(sm.getCurrentState({ currentTask: { status: 'in_progress' } })).toBe('working')
|
|
29
|
-
expect(sm.getCurrentState({ currentTask: { status: 'working' } })).toBe('working')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('returns completed for completed/done status', () => {
|
|
33
|
-
expect(sm.getCurrentState({ currentTask: { status: 'completed' } })).toBe('completed')
|
|
34
|
-
expect(sm.getCurrentState({ currentTask: { status: 'done' } })).toBe('completed')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('returns shipped for shipped status', () => {
|
|
38
|
-
expect(sm.getCurrentState({ currentTask: { status: 'shipped' } })).toBe('shipped')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('returns paused when currentTask has paused status', () => {
|
|
42
|
-
expect(sm.getCurrentState({ currentTask: { status: 'paused' } })).toBe('paused')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('returns paused when no currentTask but pausedTasks array has entries', () => {
|
|
46
|
-
expect(sm.getCurrentState({ currentTask: null, pausedTasks: [{ id: '1' }] })).toBe('paused')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('returns paused when no currentTask but legacy previousTask is paused', () => {
|
|
50
|
-
expect(sm.getCurrentState({ currentTask: null, previousTask: { status: 'paused' } })).toBe(
|
|
51
|
-
'paused'
|
|
52
|
-
)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('returns idle when no currentTask and empty pausedTasks', () => {
|
|
56
|
-
expect(sm.getCurrentState({ currentTask: null, pausedTasks: [] })).toBe('idle')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('returns working for unknown status when task exists', () => {
|
|
60
|
-
expect(sm.getCurrentState({ currentTask: { status: 'active' } })).toBe('working')
|
|
61
|
-
expect(sm.getCurrentState({ currentTask: {} })).toBe('working')
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// =============================================================================
|
|
66
|
-
// canTransition - valid transitions
|
|
67
|
-
// =============================================================================
|
|
68
|
-
|
|
69
|
-
describe('canTransition - valid', () => {
|
|
70
|
-
const validTransitions: [WorkflowState, WorkflowCommand][] = [
|
|
71
|
-
// idle
|
|
72
|
-
['idle', 'task'],
|
|
73
|
-
['idle', 'next'],
|
|
74
|
-
// working
|
|
75
|
-
['working', 'done'],
|
|
76
|
-
['working', 'pause'],
|
|
77
|
-
// paused
|
|
78
|
-
['paused', 'resume'],
|
|
79
|
-
['paused', 'task'],
|
|
80
|
-
['paused', 'ship'], // NEW: fast-track ship
|
|
81
|
-
// completed
|
|
82
|
-
['completed', 'ship'],
|
|
83
|
-
['completed', 'task'],
|
|
84
|
-
['completed', 'next'],
|
|
85
|
-
['completed', 'pause'], // NEW: reopen for review
|
|
86
|
-
['completed', 'reopen'], // NEW: reopen for rework
|
|
87
|
-
// shipped
|
|
88
|
-
['shipped', 'task'],
|
|
89
|
-
['shipped', 'next'],
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
for (const [state, command] of validTransitions) {
|
|
93
|
-
it(`${state} → ${command} is valid`, () => {
|
|
94
|
-
const result = sm.canTransition(state, command)
|
|
95
|
-
expect(result.valid).toBe(true)
|
|
96
|
-
expect(result.error).toBeUndefined()
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// =============================================================================
|
|
102
|
-
// canTransition - invalid transitions
|
|
103
|
-
// =============================================================================
|
|
104
|
-
|
|
105
|
-
describe('canTransition - invalid', () => {
|
|
106
|
-
const invalidTransitions: [WorkflowState, WorkflowCommand][] = [
|
|
107
|
-
['idle', 'done'],
|
|
108
|
-
['idle', 'pause'],
|
|
109
|
-
['idle', 'resume'],
|
|
110
|
-
['idle', 'ship'],
|
|
111
|
-
['idle', 'reopen'],
|
|
112
|
-
['working', 'task'],
|
|
113
|
-
['working', 'ship'],
|
|
114
|
-
['working', 'resume'],
|
|
115
|
-
['working', 'next'],
|
|
116
|
-
['working', 'reopen'],
|
|
117
|
-
['paused', 'done'],
|
|
118
|
-
['paused', 'pause'],
|
|
119
|
-
['paused', 'reopen'],
|
|
120
|
-
['shipped', 'done'],
|
|
121
|
-
['shipped', 'pause'],
|
|
122
|
-
['shipped', 'resume'],
|
|
123
|
-
['shipped', 'ship'],
|
|
124
|
-
['shipped', 'reopen'],
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
for (const [state, command] of invalidTransitions) {
|
|
128
|
-
it(`${state} → ${command} is invalid`, () => {
|
|
129
|
-
const result = sm.canTransition(state, command)
|
|
130
|
-
expect(result.valid).toBe(false)
|
|
131
|
-
expect(result.error).toBeDefined()
|
|
132
|
-
expect(result.suggestion).toBeDefined()
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
// =============================================================================
|
|
138
|
-
// getNextState
|
|
139
|
-
// =============================================================================
|
|
140
|
-
|
|
141
|
-
describe('getNextState', () => {
|
|
142
|
-
it('task → working', () => {
|
|
143
|
-
expect(sm.getNextState('idle', 'task')).toBe('working')
|
|
144
|
-
expect(sm.getNextState('paused', 'task')).toBe('working')
|
|
145
|
-
expect(sm.getNextState('completed', 'task')).toBe('working')
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('done → completed', () => {
|
|
149
|
-
expect(sm.getNextState('working', 'done')).toBe('completed')
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('pause → paused', () => {
|
|
153
|
-
expect(sm.getNextState('working', 'pause')).toBe('paused')
|
|
154
|
-
expect(sm.getNextState('completed', 'pause')).toBe('paused')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('resume → working', () => {
|
|
158
|
-
expect(sm.getNextState('paused', 'resume')).toBe('working')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('ship → shipped', () => {
|
|
162
|
-
expect(sm.getNextState('completed', 'ship')).toBe('shipped')
|
|
163
|
-
expect(sm.getNextState('paused', 'ship')).toBe('shipped')
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('reopen → working', () => {
|
|
167
|
-
expect(sm.getNextState('completed', 'reopen')).toBe('working')
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('next preserves current state', () => {
|
|
171
|
-
expect(sm.getNextState('idle', 'next')).toBe('idle')
|
|
172
|
-
expect(sm.getNextState('completed', 'next')).toBe('completed')
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// =============================================================================
|
|
177
|
-
// getValidCommands
|
|
178
|
-
// =============================================================================
|
|
179
|
-
|
|
180
|
-
describe('getValidCommands', () => {
|
|
181
|
-
it('idle allows task, next', () => {
|
|
182
|
-
expect(sm.getValidCommands('idle')).toEqual(['task', 'next'])
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('working allows done, pause', () => {
|
|
186
|
-
expect(sm.getValidCommands('working')).toEqual(['done', 'pause'])
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('paused allows resume, task, ship', () => {
|
|
190
|
-
expect(sm.getValidCommands('paused')).toEqual(['resume', 'task', 'ship'])
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('completed allows ship, task, next, pause, reopen', () => {
|
|
194
|
-
expect(sm.getValidCommands('completed')).toEqual(['ship', 'task', 'next', 'pause', 'reopen'])
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('shipped allows task, next', () => {
|
|
198
|
-
expect(sm.getValidCommands('shipped')).toEqual(['task', 'next'])
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// =============================================================================
|
|
203
|
-
// formatNextSteps
|
|
204
|
-
// =============================================================================
|
|
205
|
-
|
|
206
|
-
describe('formatNextSteps', () => {
|
|
207
|
-
it('includes reopen in completed state steps', () => {
|
|
208
|
-
const steps = sm.formatNextSteps('completed')
|
|
209
|
-
expect(steps.some((s) => s.includes('reopen'))).toBe(true)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('includes ship in paused state steps', () => {
|
|
213
|
-
const steps = sm.formatNextSteps('paused')
|
|
214
|
-
expect(steps.some((s) => s.includes('ship'))).toBe(true)
|
|
215
|
-
})
|
|
216
|
-
})
|