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,659 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command Executor Tests
|
|
3
|
-
* PRJ-82: Unit tests for command execution pipeline
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock } from 'bun:test'
|
|
7
|
-
import fs from 'node:fs'
|
|
8
|
-
import fsPromises from 'node:fs/promises'
|
|
9
|
-
import os from 'node:os'
|
|
10
|
-
import path from 'node:path'
|
|
11
|
-
// Import dependencies to mock
|
|
12
|
-
import chainOfThought from '../../agentic/chain-of-thought'
|
|
13
|
-
// Import the module under test
|
|
14
|
-
import commandExecutor, {
|
|
15
|
-
CommandExecutor,
|
|
16
|
-
signalEnd,
|
|
17
|
-
signalStart,
|
|
18
|
-
} from '../../agentic/command-executor'
|
|
19
|
-
import contextBuilder from '../../agentic/context-builder'
|
|
20
|
-
import groundTruth from '../../agentic/ground-truth'
|
|
21
|
-
import loopDetector from '../../agentic/loop-detector'
|
|
22
|
-
import memorySystem from '../../agentic/memory-system'
|
|
23
|
-
import planMode from '../../agentic/plan-mode'
|
|
24
|
-
import promptBuilder from '../../agentic/prompt-builder'
|
|
25
|
-
import templateExecutor from '../../agentic/template-executor'
|
|
26
|
-
import templateLoader from '../../agentic/template-loader'
|
|
27
|
-
import toolRegistry from '../../agentic/tool-registry'
|
|
28
|
-
|
|
29
|
-
// =============================================================================
|
|
30
|
-
// Test Setup
|
|
31
|
-
// =============================================================================
|
|
32
|
-
|
|
33
|
-
const TEST_BASE_DIR = path.join(process.cwd(), '.tmp', 'prjct-cli-tests', 'command-executor')
|
|
34
|
-
const RUNNING_FILE = path.join(os.homedir(), '.prjct-cli', '.running')
|
|
35
|
-
|
|
36
|
-
let testCounter = 0
|
|
37
|
-
const getTestProjectId = () => `test-cmd-exec-${Date.now()}-${++testCounter}`
|
|
38
|
-
|
|
39
|
-
// Store original implementations for restoration
|
|
40
|
-
const originalFunctions: Record<string, unknown> = {}
|
|
41
|
-
|
|
42
|
-
// =============================================================================
|
|
43
|
-
// Mock Factories
|
|
44
|
-
// =============================================================================
|
|
45
|
-
|
|
46
|
-
function createMockTemplate(overrides = {}) {
|
|
47
|
-
return {
|
|
48
|
-
name: 'test-command',
|
|
49
|
-
content: '# Test Command\n\nTest content',
|
|
50
|
-
frontmatter: {
|
|
51
|
-
'allowed-tools': ['Read', 'Write', 'Bash'],
|
|
52
|
-
...overrides,
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function createMockContext(projectId: string, overrides = {}) {
|
|
58
|
-
return {
|
|
59
|
-
projectId,
|
|
60
|
-
projectPath: TEST_BASE_DIR,
|
|
61
|
-
globalPath: TEST_BASE_DIR,
|
|
62
|
-
projectName: 'test-project',
|
|
63
|
-
ecosystem: 'node',
|
|
64
|
-
paths: {},
|
|
65
|
-
params: {},
|
|
66
|
-
timestamp: new Date().toISOString(),
|
|
67
|
-
date: new Date().toISOString().split('T')[0],
|
|
68
|
-
...overrides,
|
|
69
|
-
} as unknown as ReturnType<typeof contextBuilder.build> extends Promise<infer T> ? T : never
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function createMockState(overrides = {}) {
|
|
73
|
-
return {
|
|
74
|
-
currentTask: null,
|
|
75
|
-
pausedTasks: [],
|
|
76
|
-
...overrides,
|
|
77
|
-
} as Record<string, unknown>
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// =============================================================================
|
|
81
|
-
// Tests: signalStart / signalEnd
|
|
82
|
-
// =============================================================================
|
|
83
|
-
|
|
84
|
-
describe('CommandExecutor', () => {
|
|
85
|
-
beforeAll(async () => {
|
|
86
|
-
await fsPromises.mkdir(TEST_BASE_DIR, { recursive: true })
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
afterAll(async () => {
|
|
90
|
-
try {
|
|
91
|
-
await fsPromises.rm(TEST_BASE_DIR, { recursive: true, force: true })
|
|
92
|
-
} catch (_error) {
|
|
93
|
-
// Ignore cleanup errors
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('signalStart', () => {
|
|
98
|
-
afterEach(() => {
|
|
99
|
-
// Clean up the running file after each test
|
|
100
|
-
try {
|
|
101
|
-
if (fs.existsSync(RUNNING_FILE)) {
|
|
102
|
-
fs.unlinkSync(RUNNING_FILE)
|
|
103
|
-
}
|
|
104
|
-
} catch (_error) {
|
|
105
|
-
// Ignore
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('should create status file with command name', async () => {
|
|
110
|
-
await signalStart('test-command')
|
|
111
|
-
|
|
112
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(true)
|
|
113
|
-
const content = fs.readFileSync(RUNNING_FILE, 'utf-8')
|
|
114
|
-
expect(content).toBe('/p:test-command')
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('should overwrite existing status file', async () => {
|
|
118
|
-
await signalStart('first-command')
|
|
119
|
-
await signalStart('second-command')
|
|
120
|
-
|
|
121
|
-
const content = fs.readFileSync(RUNNING_FILE, 'utf-8')
|
|
122
|
-
expect(content).toBe('/p:second-command')
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('should handle filesystem errors gracefully', async () => {
|
|
126
|
-
// This test verifies that errors are silently ignored
|
|
127
|
-
// We can't easily simulate fs errors, but we can verify the function doesn't throw
|
|
128
|
-
await expect(signalStart('test-command')).resolves.toBeUndefined()
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
describe('signalEnd', () => {
|
|
133
|
-
it('should remove status file if it exists', async () => {
|
|
134
|
-
// Create the file first
|
|
135
|
-
await signalStart('test-command')
|
|
136
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(true)
|
|
137
|
-
|
|
138
|
-
await signalEnd()
|
|
139
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('should not throw if file does not exist', async () => {
|
|
143
|
-
// Ensure file doesn't exist
|
|
144
|
-
try {
|
|
145
|
-
fs.unlinkSync(RUNNING_FILE)
|
|
146
|
-
} catch (_error) {
|
|
147
|
-
// Ignore
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
await expect(signalEnd()).resolves.toBeUndefined()
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
describe('CommandExecutor class', () => {
|
|
155
|
-
let executor: CommandExecutor
|
|
156
|
-
|
|
157
|
-
beforeEach(() => {
|
|
158
|
-
executor = new CommandExecutor()
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('should have signalStart method that calls module function', async () => {
|
|
162
|
-
await executor.signalStart('class-test')
|
|
163
|
-
|
|
164
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(true)
|
|
165
|
-
const content = fs.readFileSync(RUNNING_FILE, 'utf-8')
|
|
166
|
-
expect(content).toBe('/p:class-test')
|
|
167
|
-
|
|
168
|
-
// Cleanup
|
|
169
|
-
await executor.signalEnd()
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('should have signalEnd method that calls module function', async () => {
|
|
173
|
-
await executor.signalStart('class-test')
|
|
174
|
-
await executor.signalEnd()
|
|
175
|
-
|
|
176
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// =============================================================================
|
|
182
|
-
// Tests: executeTool
|
|
183
|
-
// =============================================================================
|
|
184
|
-
|
|
185
|
-
describe('executeTool', () => {
|
|
186
|
-
let executor: CommandExecutor
|
|
187
|
-
|
|
188
|
-
beforeAll(() => {
|
|
189
|
-
// Store original toolRegistry methods
|
|
190
|
-
originalFunctions['toolRegistry.isAllowed'] = toolRegistry.isAllowed
|
|
191
|
-
originalFunctions['toolRegistry.get'] = toolRegistry.get
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
beforeEach(() => {
|
|
195
|
-
executor = new CommandExecutor()
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
afterEach(() => {
|
|
199
|
-
// Restore original methods
|
|
200
|
-
toolRegistry.isAllowed = originalFunctions[
|
|
201
|
-
'toolRegistry.isAllowed'
|
|
202
|
-
] as typeof toolRegistry.isAllowed
|
|
203
|
-
toolRegistry.get = originalFunctions['toolRegistry.get'] as typeof toolRegistry.get
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('should reject tool not in allowed list', async () => {
|
|
207
|
-
toolRegistry.isAllowed = mock(() => false)
|
|
208
|
-
|
|
209
|
-
await expect(executor.executeTool('DangerousTool', [], ['Read', 'Write'])).rejects.toThrow(
|
|
210
|
-
'Tool DangerousTool not allowed for this command'
|
|
211
|
-
)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should throw if tool not found in registry', async () => {
|
|
215
|
-
toolRegistry.isAllowed = mock(() => true)
|
|
216
|
-
toolRegistry.get = mock(() => undefined)
|
|
217
|
-
|
|
218
|
-
await expect(executor.executeTool('NonExistent', [], ['NonExistent'])).rejects.toThrow(
|
|
219
|
-
'Tool NonExistent not found'
|
|
220
|
-
)
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
it('should execute allowed tool successfully', async () => {
|
|
224
|
-
const mockToolFn = mock(() => Promise.resolve('tool-result'))
|
|
225
|
-
|
|
226
|
-
toolRegistry.isAllowed = mock(() => true)
|
|
227
|
-
toolRegistry.get = mock(() => mockToolFn)
|
|
228
|
-
|
|
229
|
-
const result = await executor.executeTool('Read', ['/path/to/file'], ['Read'])
|
|
230
|
-
|
|
231
|
-
expect(result).toBe('tool-result')
|
|
232
|
-
expect(mockToolFn).toHaveBeenCalledWith('/path/to/file')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('should pass multiple arguments to tool', async () => {
|
|
236
|
-
const mockToolFn = mock(() => Promise.resolve('written'))
|
|
237
|
-
|
|
238
|
-
toolRegistry.isAllowed = mock(() => true)
|
|
239
|
-
toolRegistry.get = mock(() => mockToolFn)
|
|
240
|
-
|
|
241
|
-
const result = await executor.executeTool('Write', ['/path', 'content'], ['Write'])
|
|
242
|
-
|
|
243
|
-
expect(result).toBe('written')
|
|
244
|
-
expect(mockToolFn).toHaveBeenCalledWith('/path', 'content')
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// =============================================================================
|
|
249
|
-
// Tests: execute (main flow)
|
|
250
|
-
// =============================================================================
|
|
251
|
-
|
|
252
|
-
describe('execute', () => {
|
|
253
|
-
let executor: CommandExecutor
|
|
254
|
-
let TEST_PROJECT_ID: string
|
|
255
|
-
|
|
256
|
-
beforeAll(() => {
|
|
257
|
-
// Store original implementations
|
|
258
|
-
originalFunctions['loopDetector.shouldEscalate'] = loopDetector.shouldEscalate
|
|
259
|
-
originalFunctions['loopDetector.getEscalationInfo'] = loopDetector.getEscalationInfo
|
|
260
|
-
originalFunctions['loopDetector.recordSuccess'] = loopDetector.recordSuccess
|
|
261
|
-
originalFunctions['loopDetector.recordAttempt'] = loopDetector.recordAttempt
|
|
262
|
-
originalFunctions['templateLoader.load'] = templateLoader.load
|
|
263
|
-
originalFunctions['contextBuilder.build'] = contextBuilder.build
|
|
264
|
-
originalFunctions['contextBuilder.loadState'] = contextBuilder.loadState
|
|
265
|
-
originalFunctions['contextBuilder.loadStateForCommand'] = contextBuilder.loadStateForCommand
|
|
266
|
-
originalFunctions['planMode.requiresPlanning'] = planMode.requiresPlanning
|
|
267
|
-
originalFunctions['planMode.isDestructive'] = planMode.isDestructive
|
|
268
|
-
originalFunctions['planMode.isInPlanningMode'] = planMode.isInPlanningMode
|
|
269
|
-
originalFunctions['planMode.getAllowedTools'] = planMode.getAllowedTools
|
|
270
|
-
originalFunctions['groundTruth.requiresVerification'] = groundTruth.requiresVerification
|
|
271
|
-
originalFunctions['chainOfThought.requiresReasoning'] = chainOfThought.requiresReasoning
|
|
272
|
-
originalFunctions['templateExecutor.buildContext'] = templateExecutor.buildContext
|
|
273
|
-
originalFunctions['templateExecutor.buildAgenticPrompt'] = templateExecutor.buildAgenticPrompt
|
|
274
|
-
originalFunctions['templateExecutor.requiresOrchestration'] =
|
|
275
|
-
templateExecutor.requiresOrchestration
|
|
276
|
-
originalFunctions['memorySystem.getSmartDecision'] = memorySystem.getSmartDecision
|
|
277
|
-
originalFunctions['memorySystem.getRelevantMemories'] = memorySystem.getRelevantMemories
|
|
278
|
-
originalFunctions['promptBuilder.build'] = promptBuilder.build
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
beforeEach(() => {
|
|
282
|
-
executor = new CommandExecutor()
|
|
283
|
-
TEST_PROJECT_ID = getTestProjectId()
|
|
284
|
-
|
|
285
|
-
// Reset all mocks to default behavior
|
|
286
|
-
loopDetector.shouldEscalate = mock(() => false)
|
|
287
|
-
loopDetector.getEscalationInfo = mock(() => null)
|
|
288
|
-
loopDetector.recordSuccess = mock(() => {})
|
|
289
|
-
loopDetector.recordAttempt = mock(() => ({
|
|
290
|
-
attemptNumber: 1,
|
|
291
|
-
isLooping: false,
|
|
292
|
-
shouldEscalate: false,
|
|
293
|
-
}))
|
|
294
|
-
|
|
295
|
-
// Mock planMode
|
|
296
|
-
planMode.requiresPlanning = mock(() => false)
|
|
297
|
-
planMode.isDestructive = mock(() => false)
|
|
298
|
-
planMode.isInPlanningMode = mock(() => false)
|
|
299
|
-
planMode.getAllowedTools = mock(() => ['Read', 'Write', 'Bash'])
|
|
300
|
-
|
|
301
|
-
// Mock groundTruth and chainOfThought
|
|
302
|
-
groundTruth.requiresVerification = mock(() => false)
|
|
303
|
-
chainOfThought.requiresReasoning = mock(() => false)
|
|
304
|
-
|
|
305
|
-
// Mock templateExecutor
|
|
306
|
-
templateExecutor.buildContext = mock(() =>
|
|
307
|
-
Promise.resolve({
|
|
308
|
-
projectPath: TEST_BASE_DIR,
|
|
309
|
-
projectId: TEST_PROJECT_ID,
|
|
310
|
-
globalPath: TEST_BASE_DIR,
|
|
311
|
-
command: 'test-cmd',
|
|
312
|
-
args: '',
|
|
313
|
-
agentName: 'test-agent',
|
|
314
|
-
agentSettingsPath: '',
|
|
315
|
-
paths: {
|
|
316
|
-
orchestrator: '',
|
|
317
|
-
agentRouting: '',
|
|
318
|
-
taskFragmentation: '',
|
|
319
|
-
commandTemplate: '',
|
|
320
|
-
repoAnalysis: '',
|
|
321
|
-
agentsDir: '',
|
|
322
|
-
skillsDir: '',
|
|
323
|
-
stateJson: '',
|
|
324
|
-
},
|
|
325
|
-
})
|
|
326
|
-
)
|
|
327
|
-
templateExecutor.buildAgenticPrompt = mock(() => ({
|
|
328
|
-
prompt: 'test prompt',
|
|
329
|
-
context: {} as never,
|
|
330
|
-
requiresOrchestration: false,
|
|
331
|
-
}))
|
|
332
|
-
templateExecutor.requiresOrchestration = mock(() => false)
|
|
333
|
-
|
|
334
|
-
// Mock memorySystem
|
|
335
|
-
memorySystem.getSmartDecision = mock(() => Promise.resolve(null))
|
|
336
|
-
memorySystem.getRelevantMemories = mock(() => Promise.resolve([]))
|
|
337
|
-
|
|
338
|
-
// Mock promptBuilder
|
|
339
|
-
promptBuilder.build = mock(() => Promise.resolve('built prompt'))
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
afterEach(() => {
|
|
343
|
-
// Restore original implementations
|
|
344
|
-
loopDetector.shouldEscalate = originalFunctions[
|
|
345
|
-
'loopDetector.shouldEscalate'
|
|
346
|
-
] as typeof loopDetector.shouldEscalate
|
|
347
|
-
loopDetector.getEscalationInfo = originalFunctions[
|
|
348
|
-
'loopDetector.getEscalationInfo'
|
|
349
|
-
] as typeof loopDetector.getEscalationInfo
|
|
350
|
-
loopDetector.recordSuccess = originalFunctions[
|
|
351
|
-
'loopDetector.recordSuccess'
|
|
352
|
-
] as typeof loopDetector.recordSuccess
|
|
353
|
-
loopDetector.recordAttempt = originalFunctions[
|
|
354
|
-
'loopDetector.recordAttempt'
|
|
355
|
-
] as typeof loopDetector.recordAttempt
|
|
356
|
-
templateLoader.load = originalFunctions['templateLoader.load'] as typeof templateLoader.load
|
|
357
|
-
contextBuilder.build = originalFunctions['contextBuilder.build'] as typeof contextBuilder.build
|
|
358
|
-
contextBuilder.loadState = originalFunctions[
|
|
359
|
-
'contextBuilder.loadState'
|
|
360
|
-
] as typeof contextBuilder.loadState
|
|
361
|
-
contextBuilder.loadStateForCommand = originalFunctions[
|
|
362
|
-
'contextBuilder.loadStateForCommand'
|
|
363
|
-
] as typeof contextBuilder.loadStateForCommand
|
|
364
|
-
planMode.requiresPlanning = originalFunctions[
|
|
365
|
-
'planMode.requiresPlanning'
|
|
366
|
-
] as typeof planMode.requiresPlanning
|
|
367
|
-
planMode.isDestructive = originalFunctions[
|
|
368
|
-
'planMode.isDestructive'
|
|
369
|
-
] as typeof planMode.isDestructive
|
|
370
|
-
planMode.isInPlanningMode = originalFunctions[
|
|
371
|
-
'planMode.isInPlanningMode'
|
|
372
|
-
] as typeof planMode.isInPlanningMode
|
|
373
|
-
planMode.getAllowedTools = originalFunctions[
|
|
374
|
-
'planMode.getAllowedTools'
|
|
375
|
-
] as typeof planMode.getAllowedTools
|
|
376
|
-
groundTruth.requiresVerification = originalFunctions[
|
|
377
|
-
'groundTruth.requiresVerification'
|
|
378
|
-
] as typeof groundTruth.requiresVerification
|
|
379
|
-
chainOfThought.requiresReasoning = originalFunctions[
|
|
380
|
-
'chainOfThought.requiresReasoning'
|
|
381
|
-
] as typeof chainOfThought.requiresReasoning
|
|
382
|
-
templateExecutor.buildContext = originalFunctions[
|
|
383
|
-
'templateExecutor.buildContext'
|
|
384
|
-
] as typeof templateExecutor.buildContext
|
|
385
|
-
templateExecutor.buildAgenticPrompt = originalFunctions[
|
|
386
|
-
'templateExecutor.buildAgenticPrompt'
|
|
387
|
-
] as typeof templateExecutor.buildAgenticPrompt
|
|
388
|
-
templateExecutor.requiresOrchestration = originalFunctions[
|
|
389
|
-
'templateExecutor.requiresOrchestration'
|
|
390
|
-
] as typeof templateExecutor.requiresOrchestration
|
|
391
|
-
memorySystem.getSmartDecision = originalFunctions[
|
|
392
|
-
'memorySystem.getSmartDecision'
|
|
393
|
-
] as typeof memorySystem.getSmartDecision
|
|
394
|
-
memorySystem.getRelevantMemories = originalFunctions[
|
|
395
|
-
'memorySystem.getRelevantMemories'
|
|
396
|
-
] as typeof memorySystem.getRelevantMemories
|
|
397
|
-
promptBuilder.build = originalFunctions['promptBuilder.build'] as typeof promptBuilder.build
|
|
398
|
-
|
|
399
|
-
// Clean up running file
|
|
400
|
-
try {
|
|
401
|
-
if (fs.existsSync(RUNNING_FILE)) {
|
|
402
|
-
fs.unlinkSync(RUNNING_FILE)
|
|
403
|
-
}
|
|
404
|
-
} catch (_error) {
|
|
405
|
-
// Ignore
|
|
406
|
-
}
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
describe('loop detection', () => {
|
|
410
|
-
it('should return escalation when loop detected before execution', async () => {
|
|
411
|
-
loopDetector.shouldEscalate = mock(() => true)
|
|
412
|
-
loopDetector.getEscalationInfo = mock(() => ({
|
|
413
|
-
message: 'Command stuck in loop',
|
|
414
|
-
suggestion: 'Try a different approach',
|
|
415
|
-
attemptCount: 3,
|
|
416
|
-
})) as unknown as typeof loopDetector.getEscalationInfo
|
|
417
|
-
|
|
418
|
-
const result = await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
419
|
-
|
|
420
|
-
expect(result.success).toBe(false)
|
|
421
|
-
expect(result.isLoopDetected).toBe(true)
|
|
422
|
-
expect(result.error).toBe('Command stuck in loop')
|
|
423
|
-
expect(result.suggestion).toBe('Try a different approach')
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
it('should record successful execution', async () => {
|
|
427
|
-
const mockTemplate = createMockTemplate()
|
|
428
|
-
const mockContext = createMockContext(TEST_PROJECT_ID)
|
|
429
|
-
const mockState = createMockState()
|
|
430
|
-
|
|
431
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
432
|
-
contextBuilder.build = mock(() => Promise.resolve(mockContext)) as typeof contextBuilder.build
|
|
433
|
-
contextBuilder.loadState = mock(() =>
|
|
434
|
-
Promise.resolve(mockState)
|
|
435
|
-
) as typeof contextBuilder.loadState
|
|
436
|
-
contextBuilder.loadStateForCommand = mock(() =>
|
|
437
|
-
Promise.resolve(mockState)
|
|
438
|
-
) as typeof contextBuilder.loadStateForCommand
|
|
439
|
-
|
|
440
|
-
await executor.execute('test-cmd', { task: 'test task' }, TEST_BASE_DIR)
|
|
441
|
-
|
|
442
|
-
expect(loopDetector.recordSuccess).toHaveBeenCalled()
|
|
443
|
-
})
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
describe('error handling', () => {
|
|
447
|
-
it('should handle template loading errors', async () => {
|
|
448
|
-
templateLoader.load = mock(() => Promise.reject(new Error('Template not found')))
|
|
449
|
-
|
|
450
|
-
const result = await executor.execute('nonexistent', {}, TEST_BASE_DIR)
|
|
451
|
-
|
|
452
|
-
expect(result.success).toBe(false)
|
|
453
|
-
expect(result.error).toBe('Template not found')
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
it('should handle context building errors', async () => {
|
|
457
|
-
const mockTemplate = createMockTemplate()
|
|
458
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
459
|
-
contextBuilder.build = mock(() => Promise.reject(new Error('Context build failed')))
|
|
460
|
-
|
|
461
|
-
const result = await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
462
|
-
|
|
463
|
-
expect(result.success).toBe(false)
|
|
464
|
-
expect(result.error).toBe('Context build failed')
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
it('should record failed attempts for loop detection', async () => {
|
|
468
|
-
templateLoader.load = mock(() => Promise.reject(new Error('Some error')))
|
|
469
|
-
|
|
470
|
-
await executor.execute('test-cmd', { task: 'test task' }, TEST_BASE_DIR)
|
|
471
|
-
|
|
472
|
-
expect(loopDetector.recordAttempt).toHaveBeenCalled()
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
it('should escalate after repeated failures', async () => {
|
|
476
|
-
templateLoader.load = mock(() => Promise.reject(new Error('Repeated error')))
|
|
477
|
-
loopDetector.recordAttempt = mock(() => ({
|
|
478
|
-
attemptNumber: 3,
|
|
479
|
-
isLooping: true,
|
|
480
|
-
shouldEscalate: true,
|
|
481
|
-
}))
|
|
482
|
-
loopDetector.getEscalationInfo = mock(() => ({
|
|
483
|
-
message: 'Too many failures',
|
|
484
|
-
suggestion: 'Check your setup',
|
|
485
|
-
attemptCount: 3,
|
|
486
|
-
})) as unknown as typeof loopDetector.getEscalationInfo
|
|
487
|
-
|
|
488
|
-
const result = await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
489
|
-
|
|
490
|
-
expect(result.success).toBe(false)
|
|
491
|
-
expect(result.isLoopDetected).toBe(true)
|
|
492
|
-
expect(result.error).toBe('Too many failures')
|
|
493
|
-
})
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
describe('signal lifecycle', () => {
|
|
497
|
-
it('should call signalStart at beginning and signalEnd on success', async () => {
|
|
498
|
-
const mockTemplate = createMockTemplate()
|
|
499
|
-
const mockContext = createMockContext(TEST_PROJECT_ID)
|
|
500
|
-
const mockState = createMockState()
|
|
501
|
-
|
|
502
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
503
|
-
contextBuilder.build = mock(() => Promise.resolve(mockContext)) as typeof contextBuilder.build
|
|
504
|
-
contextBuilder.loadState = mock(() =>
|
|
505
|
-
Promise.resolve(mockState)
|
|
506
|
-
) as typeof contextBuilder.loadState
|
|
507
|
-
contextBuilder.loadStateForCommand = mock(() =>
|
|
508
|
-
Promise.resolve(mockState)
|
|
509
|
-
) as typeof contextBuilder.loadStateForCommand
|
|
510
|
-
|
|
511
|
-
// File shouldn't exist before
|
|
512
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
513
|
-
|
|
514
|
-
await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
515
|
-
|
|
516
|
-
// File should be cleaned up after
|
|
517
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
it('should call signalEnd on error', async () => {
|
|
521
|
-
templateLoader.load = mock(() => Promise.reject(new Error('Test error')))
|
|
522
|
-
|
|
523
|
-
await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
524
|
-
|
|
525
|
-
// File should be cleaned up even after error
|
|
526
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
it('should call signalEnd on loop detection', async () => {
|
|
530
|
-
loopDetector.shouldEscalate = mock(() => true)
|
|
531
|
-
loopDetector.getEscalationInfo = mock(() => ({
|
|
532
|
-
message: 'Loop detected',
|
|
533
|
-
suggestion: 'Stop',
|
|
534
|
-
attemptCount: 3,
|
|
535
|
-
})) as unknown as typeof loopDetector.getEscalationInfo
|
|
536
|
-
|
|
537
|
-
await executor.execute('test-cmd', {}, TEST_BASE_DIR)
|
|
538
|
-
|
|
539
|
-
expect(fs.existsSync(RUNNING_FILE)).toBe(false)
|
|
540
|
-
})
|
|
541
|
-
})
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
// =============================================================================
|
|
545
|
-
// Tests: executeSimple
|
|
546
|
-
// =============================================================================
|
|
547
|
-
|
|
548
|
-
describe('executeSimple', () => {
|
|
549
|
-
let executor: CommandExecutor
|
|
550
|
-
let TEST_PROJECT_ID: string
|
|
551
|
-
|
|
552
|
-
beforeAll(() => {
|
|
553
|
-
originalFunctions['templateLoader.load'] = templateLoader.load
|
|
554
|
-
originalFunctions['contextBuilder.build'] = contextBuilder.build
|
|
555
|
-
originalFunctions['toolRegistry.isAllowed'] = toolRegistry.isAllowed
|
|
556
|
-
originalFunctions['toolRegistry.get'] = toolRegistry.get
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
beforeEach(() => {
|
|
560
|
-
executor = new CommandExecutor()
|
|
561
|
-
TEST_PROJECT_ID = getTestProjectId()
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
afterEach(() => {
|
|
565
|
-
templateLoader.load = originalFunctions['templateLoader.load'] as typeof templateLoader.load
|
|
566
|
-
contextBuilder.build = originalFunctions['contextBuilder.build'] as typeof contextBuilder.build
|
|
567
|
-
toolRegistry.isAllowed = originalFunctions[
|
|
568
|
-
'toolRegistry.isAllowed'
|
|
569
|
-
] as typeof toolRegistry.isAllowed
|
|
570
|
-
toolRegistry.get = originalFunctions['toolRegistry.get'] as typeof toolRegistry.get
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
it('should execute function with tools proxy', async () => {
|
|
574
|
-
const mockTemplate = createMockTemplate({ 'allowed-tools': ['Read'] })
|
|
575
|
-
const mockContext = createMockContext(TEST_PROJECT_ID)
|
|
576
|
-
|
|
577
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
578
|
-
contextBuilder.build = mock(() => Promise.resolve(mockContext)) as typeof contextBuilder.build
|
|
579
|
-
toolRegistry.isAllowed = mock(() => true)
|
|
580
|
-
toolRegistry.get = mock(() => mock(() => Promise.resolve('file content')))
|
|
581
|
-
|
|
582
|
-
const executionFn = mock(async (tools: { read: (path: string) => Promise<unknown> }) => {
|
|
583
|
-
const content = await tools.read('/some/file')
|
|
584
|
-
return { content }
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
const result = await executor.executeSimple('test-cmd', executionFn, TEST_BASE_DIR)
|
|
588
|
-
|
|
589
|
-
expect(result.success).toBe(true)
|
|
590
|
-
expect(result.result).toEqual({ content: 'file content' })
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
it('should check tool permissions in proxy', async () => {
|
|
594
|
-
const mockTemplate = createMockTemplate({ 'allowed-tools': ['Read'] }) // Only Read allowed
|
|
595
|
-
const mockContext = createMockContext(TEST_PROJECT_ID)
|
|
596
|
-
|
|
597
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
598
|
-
contextBuilder.build = mock(() => Promise.resolve(mockContext)) as typeof contextBuilder.build
|
|
599
|
-
toolRegistry.isAllowed = mock((tool: string, allowed: string[]) => allowed.includes(tool))
|
|
600
|
-
|
|
601
|
-
const executionFn = mock(
|
|
602
|
-
async (tools: { write: (path: string, content: string) => Promise<unknown> }) => {
|
|
603
|
-
await tools.write('/some/file', 'content') // Try to use Write (not allowed)
|
|
604
|
-
return {}
|
|
605
|
-
}
|
|
606
|
-
)
|
|
607
|
-
|
|
608
|
-
const result = await executor.executeSimple('test-cmd', executionFn, TEST_BASE_DIR)
|
|
609
|
-
|
|
610
|
-
expect(result.success).toBe(false)
|
|
611
|
-
expect(result.error).toContain('not allowed')
|
|
612
|
-
})
|
|
613
|
-
|
|
614
|
-
it('should handle execution function errors', async () => {
|
|
615
|
-
const mockTemplate = createMockTemplate()
|
|
616
|
-
const mockContext = createMockContext(TEST_PROJECT_ID)
|
|
617
|
-
|
|
618
|
-
templateLoader.load = mock(() => Promise.resolve(mockTemplate))
|
|
619
|
-
contextBuilder.build = mock(() => Promise.resolve(mockContext)) as typeof contextBuilder.build
|
|
620
|
-
|
|
621
|
-
const executionFn = mock(async () => {
|
|
622
|
-
throw new Error('Execution failed')
|
|
623
|
-
})
|
|
624
|
-
|
|
625
|
-
const result = await executor.executeSimple('test-cmd', executionFn, TEST_BASE_DIR)
|
|
626
|
-
|
|
627
|
-
expect(result.success).toBe(false)
|
|
628
|
-
expect(result.error).toBe('Execution failed')
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
it('should handle template loading errors', async () => {
|
|
632
|
-
templateLoader.load = mock(() => Promise.reject(new Error('Template not found')))
|
|
633
|
-
|
|
634
|
-
const executionFn = mock(async () => ({}))
|
|
635
|
-
|
|
636
|
-
const result = await executor.executeSimple('nonexistent', executionFn, TEST_BASE_DIR)
|
|
637
|
-
|
|
638
|
-
expect(result.success).toBe(false)
|
|
639
|
-
expect(result.error).toBe('Template not found')
|
|
640
|
-
})
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
// =============================================================================
|
|
644
|
-
// Tests: Default Export
|
|
645
|
-
// =============================================================================
|
|
646
|
-
|
|
647
|
-
describe('default export', () => {
|
|
648
|
-
it('should export singleton instance', () => {
|
|
649
|
-
expect(commandExecutor).toBeInstanceOf(CommandExecutor)
|
|
650
|
-
})
|
|
651
|
-
|
|
652
|
-
it('should have all expected methods', () => {
|
|
653
|
-
expect(typeof commandExecutor.signalStart).toBe('function')
|
|
654
|
-
expect(typeof commandExecutor.signalEnd).toBe('function')
|
|
655
|
-
expect(typeof commandExecutor.execute).toBe('function')
|
|
656
|
-
expect(typeof commandExecutor.executeTool).toBe('function')
|
|
657
|
-
expect(typeof commandExecutor.executeSimple).toBe('function')
|
|
658
|
-
})
|
|
659
|
-
})
|