opencode-repos 0.3.0 → 0.3.1
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/index.ts +10 -6
- package/package.json +1 -1
- package/src/__tests__/git.test.ts +33 -2
- package/src/git.ts +32 -5
- package/.sisyphus/boulder.json +0 -8
- package/.sisyphus/notepads/opencode-repos/decisions.md +0 -15
- package/.sisyphus/notepads/opencode-repos/learnings.md +0 -384
- package/.sisyphus/plans/opencode-repos.md +0 -987
- package/.tmux-sessionizer +0 -8
- package/TODO.md +0 -3
- package/oh-my-opencode/.github/FUNDING.yml +0 -15
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/bug_report.yml +0 -129
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/feature_request.yml +0 -100
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/general.yml +0 -83
- package/oh-my-opencode/.github/assets/google.jpg +0 -0
- package/oh-my-opencode/.github/assets/hero.jpg +0 -0
- package/oh-my-opencode/.github/assets/indent.jpg +0 -0
- package/oh-my-opencode/.github/assets/microsoft.jpg +0 -0
- package/oh-my-opencode/.github/assets/omo.png +0 -0
- package/oh-my-opencode/.github/assets/orchestrator-atlas.png +0 -0
- package/oh-my-opencode/.github/assets/sisyphus.png +0 -0
- package/oh-my-opencode/.github/assets/sisyphuslabs.png +0 -0
- package/oh-my-opencode/.github/pull_request_template.md +0 -34
- package/oh-my-opencode/.github/workflows/ci.yml +0 -138
- package/oh-my-opencode/.github/workflows/cla.yml +0 -41
- package/oh-my-opencode/.github/workflows/lint-workflows.yml +0 -22
- package/oh-my-opencode/.github/workflows/publish.yml +0 -165
- package/oh-my-opencode/.github/workflows/sisyphus-agent.yml +0 -500
- package/oh-my-opencode/.opencode/background-tasks.json +0 -27
- package/oh-my-opencode/.opencode/command/get-unpublished-changes.md +0 -84
- package/oh-my-opencode/.opencode/command/omomomo.md +0 -37
- package/oh-my-opencode/.opencode/command/publish.md +0 -257
- package/oh-my-opencode/AGENTS.md +0 -179
- package/oh-my-opencode/CLA.md +0 -58
- package/oh-my-opencode/CONTRIBUTING.md +0 -268
- package/oh-my-opencode/LICENSE.md +0 -82
- package/oh-my-opencode/README.ja.md +0 -370
- package/oh-my-opencode/README.md +0 -376
- package/oh-my-opencode/README.zh-cn.md +0 -380
- package/oh-my-opencode/assets/oh-my-opencode.schema.json +0 -2171
- package/oh-my-opencode/bin/oh-my-opencode.js +0 -80
- package/oh-my-opencode/bin/platform.js +0 -38
- package/oh-my-opencode/bin/platform.test.ts +0 -148
- package/oh-my-opencode/bun.lock +0 -314
- package/oh-my-opencode/bunfig.toml +0 -2
- package/oh-my-opencode/docs/category-skill-guide.md +0 -200
- package/oh-my-opencode/docs/cli-guide.md +0 -272
- package/oh-my-opencode/docs/configurations.md +0 -654
- package/oh-my-opencode/docs/features.md +0 -550
- package/oh-my-opencode/docs/guide/installation.md +0 -288
- package/oh-my-opencode/docs/guide/overview.md +0 -97
- package/oh-my-opencode/docs/guide/understanding-orchestration-system.md +0 -445
- package/oh-my-opencode/docs/orchestration-guide.md +0 -152
- package/oh-my-opencode/docs/ultrawork-manifesto.md +0 -197
- package/oh-my-opencode/package.json +0 -89
- package/oh-my-opencode/packages/darwin-arm64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/darwin-arm64/package.json +0 -22
- package/oh-my-opencode/packages/darwin-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/darwin-x64/package.json +0 -22
- package/oh-my-opencode/packages/linux-arm64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-arm64/package.json +0 -25
- package/oh-my-opencode/packages/linux-arm64-musl/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-arm64-musl/package.json +0 -25
- package/oh-my-opencode/packages/linux-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-x64/package.json +0 -25
- package/oh-my-opencode/packages/linux-x64-musl/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-x64-musl/package.json +0 -25
- package/oh-my-opencode/packages/windows-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/windows-x64/package.json +0 -22
- package/oh-my-opencode/postinstall.mjs +0 -43
- package/oh-my-opencode/script/build-binaries.ts +0 -103
- package/oh-my-opencode/script/build-schema.ts +0 -28
- package/oh-my-opencode/script/generate-changelog.ts +0 -92
- package/oh-my-opencode/script/publish.ts +0 -344
- package/oh-my-opencode/signatures/cla.json +0 -676
- package/oh-my-opencode/src/agents/AGENTS.md +0 -67
- package/oh-my-opencode/src/agents/atlas.ts +0 -1383
- package/oh-my-opencode/src/agents/dynamic-agent-prompt-builder.ts +0 -400
- package/oh-my-opencode/src/agents/explore.ts +0 -122
- package/oh-my-opencode/src/agents/index.ts +0 -13
- package/oh-my-opencode/src/agents/librarian.ts +0 -326
- package/oh-my-opencode/src/agents/metis.ts +0 -315
- package/oh-my-opencode/src/agents/momus.test.ts +0 -57
- package/oh-my-opencode/src/agents/momus.ts +0 -444
- package/oh-my-opencode/src/agents/multimodal-looker.ts +0 -56
- package/oh-my-opencode/src/agents/oracle.ts +0 -122
- package/oh-my-opencode/src/agents/prometheus-prompt.test.ts +0 -22
- package/oh-my-opencode/src/agents/prometheus-prompt.ts +0 -1196
- package/oh-my-opencode/src/agents/sisyphus-junior.test.ts +0 -232
- package/oh-my-opencode/src/agents/sisyphus-junior.ts +0 -134
- package/oh-my-opencode/src/agents/sisyphus.ts +0 -633
- package/oh-my-opencode/src/agents/types.ts +0 -80
- package/oh-my-opencode/src/agents/utils.test.ts +0 -311
- package/oh-my-opencode/src/agents/utils.ts +0 -240
- package/oh-my-opencode/src/cli/AGENTS.md +0 -91
- package/oh-my-opencode/src/cli/config-manager.test.ts +0 -364
- package/oh-my-opencode/src/cli/config-manager.ts +0 -641
- package/oh-my-opencode/src/cli/doctor/checks/auth.test.ts +0 -114
- package/oh-my-opencode/src/cli/doctor/checks/auth.ts +0 -115
- package/oh-my-opencode/src/cli/doctor/checks/config.test.ts +0 -103
- package/oh-my-opencode/src/cli/doctor/checks/config.ts +0 -123
- package/oh-my-opencode/src/cli/doctor/checks/dependencies.test.ts +0 -152
- package/oh-my-opencode/src/cli/doctor/checks/dependencies.ts +0 -163
- package/oh-my-opencode/src/cli/doctor/checks/gh.test.ts +0 -151
- package/oh-my-opencode/src/cli/doctor/checks/gh.ts +0 -171
- package/oh-my-opencode/src/cli/doctor/checks/index.ts +0 -34
- package/oh-my-opencode/src/cli/doctor/checks/lsp.test.ts +0 -134
- package/oh-my-opencode/src/cli/doctor/checks/lsp.ts +0 -77
- package/oh-my-opencode/src/cli/doctor/checks/mcp.test.ts +0 -115
- package/oh-my-opencode/src/cli/doctor/checks/mcp.ts +0 -128
- package/oh-my-opencode/src/cli/doctor/checks/opencode.test.ts +0 -227
- package/oh-my-opencode/src/cli/doctor/checks/opencode.ts +0 -178
- package/oh-my-opencode/src/cli/doctor/checks/plugin.test.ts +0 -109
- package/oh-my-opencode/src/cli/doctor/checks/plugin.ts +0 -124
- package/oh-my-opencode/src/cli/doctor/checks/version.test.ts +0 -148
- package/oh-my-opencode/src/cli/doctor/checks/version.ts +0 -135
- package/oh-my-opencode/src/cli/doctor/constants.ts +0 -72
- package/oh-my-opencode/src/cli/doctor/formatter.test.ts +0 -218
- package/oh-my-opencode/src/cli/doctor/formatter.ts +0 -140
- package/oh-my-opencode/src/cli/doctor/index.ts +0 -11
- package/oh-my-opencode/src/cli/doctor/runner.test.ts +0 -153
- package/oh-my-opencode/src/cli/doctor/runner.ts +0 -132
- package/oh-my-opencode/src/cli/doctor/types.ts +0 -113
- package/oh-my-opencode/src/cli/get-local-version/formatter.ts +0 -66
- package/oh-my-opencode/src/cli/get-local-version/index.ts +0 -106
- package/oh-my-opencode/src/cli/get-local-version/types.ts +0 -14
- package/oh-my-opencode/src/cli/index.ts +0 -153
- package/oh-my-opencode/src/cli/install.ts +0 -523
- package/oh-my-opencode/src/cli/model-fallback.ts +0 -246
- package/oh-my-opencode/src/cli/run/completion.test.ts +0 -170
- package/oh-my-opencode/src/cli/run/completion.ts +0 -79
- package/oh-my-opencode/src/cli/run/events.test.ts +0 -155
- package/oh-my-opencode/src/cli/run/events.ts +0 -325
- package/oh-my-opencode/src/cli/run/index.ts +0 -2
- package/oh-my-opencode/src/cli/run/runner.ts +0 -159
- package/oh-my-opencode/src/cli/run/types.ts +0 -76
- package/oh-my-opencode/src/cli/types.ts +0 -40
- package/oh-my-opencode/src/config/index.ts +0 -26
- package/oh-my-opencode/src/config/schema.test.ts +0 -444
- package/oh-my-opencode/src/config/schema.ts +0 -339
- package/oh-my-opencode/src/features/AGENTS.md +0 -77
- package/oh-my-opencode/src/features/background-agent/concurrency.test.ts +0 -418
- package/oh-my-opencode/src/features/background-agent/concurrency.ts +0 -137
- package/oh-my-opencode/src/features/background-agent/index.ts +0 -3
- package/oh-my-opencode/src/features/background-agent/manager.test.ts +0 -1928
- package/oh-my-opencode/src/features/background-agent/manager.ts +0 -1335
- package/oh-my-opencode/src/features/background-agent/types.ts +0 -66
- package/oh-my-opencode/src/features/boulder-state/constants.ts +0 -13
- package/oh-my-opencode/src/features/boulder-state/index.ts +0 -3
- package/oh-my-opencode/src/features/boulder-state/storage.test.ts +0 -250
- package/oh-my-opencode/src/features/boulder-state/storage.ts +0 -150
- package/oh-my-opencode/src/features/boulder-state/types.ts +0 -26
- package/oh-my-opencode/src/features/builtin-commands/commands.ts +0 -89
- package/oh-my-opencode/src/features/builtin-commands/index.ts +0 -2
- package/oh-my-opencode/src/features/builtin-commands/templates/init-deep.ts +0 -300
- package/oh-my-opencode/src/features/builtin-commands/templates/ralph-loop.ts +0 -38
- package/oh-my-opencode/src/features/builtin-commands/templates/refactor.ts +0 -619
- package/oh-my-opencode/src/features/builtin-commands/templates/start-work.ts +0 -72
- package/oh-my-opencode/src/features/builtin-commands/types.ts +0 -9
- package/oh-my-opencode/src/features/builtin-skills/frontend-ui-ux/SKILL.md +0 -78
- package/oh-my-opencode/src/features/builtin-skills/git-master/SKILL.md +0 -1105
- package/oh-my-opencode/src/features/builtin-skills/index.ts +0 -2
- package/oh-my-opencode/src/features/builtin-skills/skills.ts +0 -1203
- package/oh-my-opencode/src/features/builtin-skills/types.ts +0 -16
- package/oh-my-opencode/src/features/claude-code-agent-loader/index.ts +0 -2
- package/oh-my-opencode/src/features/claude-code-agent-loader/loader.ts +0 -90
- package/oh-my-opencode/src/features/claude-code-agent-loader/types.ts +0 -17
- package/oh-my-opencode/src/features/claude-code-command-loader/index.ts +0 -2
- package/oh-my-opencode/src/features/claude-code-command-loader/loader.ts +0 -144
- package/oh-my-opencode/src/features/claude-code-command-loader/types.ts +0 -46
- package/oh-my-opencode/src/features/claude-code-mcp-loader/env-expander.ts +0 -27
- package/oh-my-opencode/src/features/claude-code-mcp-loader/index.ts +0 -11
- package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.test.ts +0 -162
- package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.ts +0 -113
- package/oh-my-opencode/src/features/claude-code-mcp-loader/transformer.ts +0 -53
- package/oh-my-opencode/src/features/claude-code-mcp-loader/types.ts +0 -42
- package/oh-my-opencode/src/features/claude-code-plugin-loader/index.ts +0 -3
- package/oh-my-opencode/src/features/claude-code-plugin-loader/loader.ts +0 -486
- package/oh-my-opencode/src/features/claude-code-plugin-loader/types.ts +0 -210
- package/oh-my-opencode/src/features/claude-code-session-state/index.ts +0 -1
- package/oh-my-opencode/src/features/claude-code-session-state/state.test.ts +0 -126
- package/oh-my-opencode/src/features/claude-code-session-state/state.ts +0 -37
- package/oh-my-opencode/src/features/context-injector/collector.test.ts +0 -330
- package/oh-my-opencode/src/features/context-injector/collector.ts +0 -85
- package/oh-my-opencode/src/features/context-injector/index.ts +0 -14
- package/oh-my-opencode/src/features/context-injector/injector.test.ts +0 -122
- package/oh-my-opencode/src/features/context-injector/injector.ts +0 -167
- package/oh-my-opencode/src/features/context-injector/types.ts +0 -91
- package/oh-my-opencode/src/features/hook-message-injector/constants.ts +0 -6
- package/oh-my-opencode/src/features/hook-message-injector/index.ts +0 -4
- package/oh-my-opencode/src/features/hook-message-injector/injector.ts +0 -195
- package/oh-my-opencode/src/features/hook-message-injector/types.ts +0 -47
- package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.test.ts +0 -448
- package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.ts +0 -180
- package/oh-my-opencode/src/features/opencode-skill-loader/blocking.test.ts +0 -210
- package/oh-my-opencode/src/features/opencode-skill-loader/blocking.ts +0 -62
- package/oh-my-opencode/src/features/opencode-skill-loader/discover-worker.ts +0 -59
- package/oh-my-opencode/src/features/opencode-skill-loader/index.ts +0 -4
- package/oh-my-opencode/src/features/opencode-skill-loader/loader.test.ts +0 -273
- package/oh-my-opencode/src/features/opencode-skill-loader/loader.ts +0 -259
- package/oh-my-opencode/src/features/opencode-skill-loader/merger.ts +0 -267
- package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.test.ts +0 -267
- package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.ts +0 -206
- package/oh-my-opencode/src/features/opencode-skill-loader/types.ts +0 -38
- package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.test.ts +0 -201
- package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.ts +0 -27
- package/oh-my-opencode/src/features/skill-mcp-manager/index.ts +0 -2
- package/oh-my-opencode/src/features/skill-mcp-manager/manager.test.ts +0 -611
- package/oh-my-opencode/src/features/skill-mcp-manager/manager.ts +0 -520
- package/oh-my-opencode/src/features/skill-mcp-manager/types.ts +0 -14
- package/oh-my-opencode/src/features/task-toast-manager/index.ts +0 -2
- package/oh-my-opencode/src/features/task-toast-manager/manager.test.ts +0 -249
- package/oh-my-opencode/src/features/task-toast-manager/manager.ts +0 -215
- package/oh-my-opencode/src/features/task-toast-manager/types.ts +0 -24
- package/oh-my-opencode/src/hooks/AGENTS.md +0 -73
- package/oh-my-opencode/src/hooks/agent-usage-reminder/constants.ts +0 -54
- package/oh-my-opencode/src/hooks/agent-usage-reminder/index.ts +0 -109
- package/oh-my-opencode/src/hooks/agent-usage-reminder/storage.ts +0 -42
- package/oh-my-opencode/src/hooks/agent-usage-reminder/types.ts +0 -6
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +0 -307
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.ts +0 -485
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/index.ts +0 -151
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/parser.ts +0 -201
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts +0 -33
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts +0 -184
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts +0 -44
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +0 -77
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.ts +0 -250
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/types.ts +0 -42
- package/oh-my-opencode/src/hooks/atlas/index.test.ts +0 -953
- package/oh-my-opencode/src/hooks/atlas/index.ts +0 -771
- package/oh-my-opencode/src/hooks/auto-slash-command/constants.ts +0 -12
- package/oh-my-opencode/src/hooks/auto-slash-command/detector.test.ts +0 -296
- package/oh-my-opencode/src/hooks/auto-slash-command/detector.ts +0 -65
- package/oh-my-opencode/src/hooks/auto-slash-command/executor.ts +0 -205
- package/oh-my-opencode/src/hooks/auto-slash-command/index.test.ts +0 -254
- package/oh-my-opencode/src/hooks/auto-slash-command/index.ts +0 -89
- package/oh-my-opencode/src/hooks/auto-slash-command/types.ts +0 -23
- package/oh-my-opencode/src/hooks/auto-update-checker/cache.ts +0 -93
- package/oh-my-opencode/src/hooks/auto-update-checker/checker.test.ts +0 -24
- package/oh-my-opencode/src/hooks/auto-update-checker/checker.ts +0 -284
- package/oh-my-opencode/src/hooks/auto-update-checker/constants.ts +0 -64
- package/oh-my-opencode/src/hooks/auto-update-checker/index.test.ts +0 -254
- package/oh-my-opencode/src/hooks/auto-update-checker/index.ts +0 -260
- package/oh-my-opencode/src/hooks/auto-update-checker/types.ts +0 -29
- package/oh-my-opencode/src/hooks/background-compaction/index.ts +0 -87
- package/oh-my-opencode/src/hooks/background-notification/index.ts +0 -28
- package/oh-my-opencode/src/hooks/background-notification/types.ts +0 -5
- package/oh-my-opencode/src/hooks/claude-code-hooks/AGENTS.md +0 -70
- package/oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts +0 -107
- package/oh-my-opencode/src/hooks/claude-code-hooks/config.ts +0 -103
- package/oh-my-opencode/src/hooks/claude-code-hooks/index.ts +0 -401
- package/oh-my-opencode/src/hooks/claude-code-hooks/plugin-config.ts +0 -12
- package/oh-my-opencode/src/hooks/claude-code-hooks/post-tool-use.ts +0 -199
- package/oh-my-opencode/src/hooks/claude-code-hooks/pre-compact.ts +0 -109
- package/oh-my-opencode/src/hooks/claude-code-hooks/pre-tool-use.ts +0 -172
- package/oh-my-opencode/src/hooks/claude-code-hooks/stop.ts +0 -118
- package/oh-my-opencode/src/hooks/claude-code-hooks/todo.ts +0 -76
- package/oh-my-opencode/src/hooks/claude-code-hooks/tool-input-cache.ts +0 -47
- package/oh-my-opencode/src/hooks/claude-code-hooks/transcript.ts +0 -252
- package/oh-my-opencode/src/hooks/claude-code-hooks/types.ts +0 -204
- package/oh-my-opencode/src/hooks/claude-code-hooks/user-prompt-submit.ts +0 -117
- package/oh-my-opencode/src/hooks/comment-checker/cli.test.ts +0 -68
- package/oh-my-opencode/src/hooks/comment-checker/cli.ts +0 -221
- package/oh-my-opencode/src/hooks/comment-checker/downloader.ts +0 -196
- package/oh-my-opencode/src/hooks/comment-checker/index.ts +0 -171
- package/oh-my-opencode/src/hooks/comment-checker/types.ts +0 -33
- package/oh-my-opencode/src/hooks/compaction-context-injector/index.ts +0 -61
- package/oh-my-opencode/src/hooks/context-window-monitor.ts +0 -99
- package/oh-my-opencode/src/hooks/delegate-task-retry/index.test.ts +0 -119
- package/oh-my-opencode/src/hooks/delegate-task-retry/index.ts +0 -136
- package/oh-my-opencode/src/hooks/directory-agents-injector/constants.ts +0 -9
- package/oh-my-opencode/src/hooks/directory-agents-injector/index.ts +0 -182
- package/oh-my-opencode/src/hooks/directory-agents-injector/storage.ts +0 -48
- package/oh-my-opencode/src/hooks/directory-agents-injector/types.ts +0 -5
- package/oh-my-opencode/src/hooks/directory-readme-injector/constants.ts +0 -9
- package/oh-my-opencode/src/hooks/directory-readme-injector/index.ts +0 -177
- package/oh-my-opencode/src/hooks/directory-readme-injector/storage.ts +0 -48
- package/oh-my-opencode/src/hooks/directory-readme-injector/types.ts +0 -5
- package/oh-my-opencode/src/hooks/edit-error-recovery/index.test.ts +0 -126
- package/oh-my-opencode/src/hooks/edit-error-recovery/index.ts +0 -57
- package/oh-my-opencode/src/hooks/empty-task-response-detector.ts +0 -27
- package/oh-my-opencode/src/hooks/index.ts +0 -32
- package/oh-my-opencode/src/hooks/interactive-bash-session/constants.ts +0 -15
- package/oh-my-opencode/src/hooks/interactive-bash-session/index.ts +0 -262
- package/oh-my-opencode/src/hooks/interactive-bash-session/storage.ts +0 -59
- package/oh-my-opencode/src/hooks/interactive-bash-session/types.ts +0 -11
- package/oh-my-opencode/src/hooks/keyword-detector/constants.ts +0 -300
- package/oh-my-opencode/src/hooks/keyword-detector/detector.ts +0 -52
- package/oh-my-opencode/src/hooks/keyword-detector/index.test.ts +0 -529
- package/oh-my-opencode/src/hooks/keyword-detector/index.ts +0 -100
- package/oh-my-opencode/src/hooks/keyword-detector/types.ts +0 -4
- package/oh-my-opencode/src/hooks/non-interactive-env/constants.ts +0 -70
- package/oh-my-opencode/src/hooks/non-interactive-env/detector.ts +0 -19
- package/oh-my-opencode/src/hooks/non-interactive-env/index.test.ts +0 -323
- package/oh-my-opencode/src/hooks/non-interactive-env/index.ts +0 -63
- package/oh-my-opencode/src/hooks/non-interactive-env/types.ts +0 -3
- package/oh-my-opencode/src/hooks/prometheus-md-only/constants.ts +0 -32
- package/oh-my-opencode/src/hooks/prometheus-md-only/index.test.ts +0 -488
- package/oh-my-opencode/src/hooks/prometheus-md-only/index.ts +0 -136
- package/oh-my-opencode/src/hooks/ralph-loop/constants.ts +0 -5
- package/oh-my-opencode/src/hooks/ralph-loop/index.test.ts +0 -835
- package/oh-my-opencode/src/hooks/ralph-loop/index.ts +0 -417
- package/oh-my-opencode/src/hooks/ralph-loop/storage.ts +0 -115
- package/oh-my-opencode/src/hooks/ralph-loop/types.ts +0 -19
- package/oh-my-opencode/src/hooks/rules-injector/constants.ts +0 -30
- package/oh-my-opencode/src/hooks/rules-injector/finder.test.ts +0 -381
- package/oh-my-opencode/src/hooks/rules-injector/finder.ts +0 -263
- package/oh-my-opencode/src/hooks/rules-injector/index.ts +0 -223
- package/oh-my-opencode/src/hooks/rules-injector/matcher.ts +0 -63
- package/oh-my-opencode/src/hooks/rules-injector/parser.test.ts +0 -226
- package/oh-my-opencode/src/hooks/rules-injector/parser.ts +0 -211
- package/oh-my-opencode/src/hooks/rules-injector/storage.ts +0 -59
- package/oh-my-opencode/src/hooks/rules-injector/types.ts +0 -57
- package/oh-my-opencode/src/hooks/session-notification-utils.ts +0 -140
- package/oh-my-opencode/src/hooks/session-notification.test.ts +0 -361
- package/oh-my-opencode/src/hooks/session-notification.ts +0 -330
- package/oh-my-opencode/src/hooks/session-recovery/constants.ts +0 -10
- package/oh-my-opencode/src/hooks/session-recovery/index.test.ts +0 -223
- package/oh-my-opencode/src/hooks/session-recovery/index.ts +0 -435
- package/oh-my-opencode/src/hooks/session-recovery/storage.ts +0 -390
- package/oh-my-opencode/src/hooks/session-recovery/types.ts +0 -98
- package/oh-my-opencode/src/hooks/start-work/index.test.ts +0 -402
- package/oh-my-opencode/src/hooks/start-work/index.ts +0 -242
- package/oh-my-opencode/src/hooks/task-resume-info/index.ts +0 -36
- package/oh-my-opencode/src/hooks/think-mode/detector.ts +0 -57
- package/oh-my-opencode/src/hooks/think-mode/index.test.ts +0 -353
- package/oh-my-opencode/src/hooks/think-mode/index.ts +0 -89
- package/oh-my-opencode/src/hooks/think-mode/switcher.test.ts +0 -461
- package/oh-my-opencode/src/hooks/think-mode/switcher.ts +0 -222
- package/oh-my-opencode/src/hooks/think-mode/types.ts +0 -21
- package/oh-my-opencode/src/hooks/thinking-block-validator/index.ts +0 -171
- package/oh-my-opencode/src/hooks/todo-continuation-enforcer.test.ts +0 -876
- package/oh-my-opencode/src/hooks/todo-continuation-enforcer.ts +0 -480
- package/oh-my-opencode/src/hooks/tool-output-truncator.test.ts +0 -168
- package/oh-my-opencode/src/hooks/tool-output-truncator.ts +0 -61
- package/oh-my-opencode/src/index.ts +0 -589
- package/oh-my-opencode/src/mcp/AGENTS.md +0 -70
- package/oh-my-opencode/src/mcp/context7.ts +0 -6
- package/oh-my-opencode/src/mcp/grep-app.ts +0 -6
- package/oh-my-opencode/src/mcp/index.test.ts +0 -86
- package/oh-my-opencode/src/mcp/index.ts +0 -32
- package/oh-my-opencode/src/mcp/types.ts +0 -9
- package/oh-my-opencode/src/mcp/websearch.ts +0 -10
- package/oh-my-opencode/src/plugin-config.test.ts +0 -119
- package/oh-my-opencode/src/plugin-config.ts +0 -135
- package/oh-my-opencode/src/plugin-handlers/config-handler.test.ts +0 -103
- package/oh-my-opencode/src/plugin-handlers/config-handler.ts +0 -399
- package/oh-my-opencode/src/plugin-handlers/index.ts +0 -1
- package/oh-my-opencode/src/plugin-state.ts +0 -30
- package/oh-my-opencode/src/shared/AGENTS.md +0 -63
- package/oh-my-opencode/src/shared/agent-tool-restrictions.ts +0 -44
- package/oh-my-opencode/src/shared/agent-variant.test.ts +0 -83
- package/oh-my-opencode/src/shared/agent-variant.ts +0 -40
- package/oh-my-opencode/src/shared/claude-config-dir.test.ts +0 -60
- package/oh-my-opencode/src/shared/claude-config-dir.ts +0 -11
- package/oh-my-opencode/src/shared/command-executor.ts +0 -225
- package/oh-my-opencode/src/shared/config-errors.ts +0 -18
- package/oh-my-opencode/src/shared/config-path.ts +0 -47
- package/oh-my-opencode/src/shared/data-path.ts +0 -22
- package/oh-my-opencode/src/shared/deep-merge.test.ts +0 -336
- package/oh-my-opencode/src/shared/deep-merge.ts +0 -53
- package/oh-my-opencode/src/shared/dynamic-truncator.ts +0 -193
- package/oh-my-opencode/src/shared/external-plugin-detector.test.ts +0 -133
- package/oh-my-opencode/src/shared/external-plugin-detector.ts +0 -132
- package/oh-my-opencode/src/shared/file-reference-resolver.ts +0 -85
- package/oh-my-opencode/src/shared/file-utils.ts +0 -40
- package/oh-my-opencode/src/shared/first-message-variant.test.ts +0 -32
- package/oh-my-opencode/src/shared/first-message-variant.ts +0 -28
- package/oh-my-opencode/src/shared/frontmatter.test.ts +0 -262
- package/oh-my-opencode/src/shared/frontmatter.ts +0 -31
- package/oh-my-opencode/src/shared/hook-disabled.ts +0 -22
- package/oh-my-opencode/src/shared/index.ts +0 -29
- package/oh-my-opencode/src/shared/jsonc-parser.test.ts +0 -266
- package/oh-my-opencode/src/shared/jsonc-parser.ts +0 -66
- package/oh-my-opencode/src/shared/logger.ts +0 -20
- package/oh-my-opencode/src/shared/migration.test.ts +0 -602
- package/oh-my-opencode/src/shared/migration.ts +0 -191
- package/oh-my-opencode/src/shared/model-resolver.test.ts +0 -101
- package/oh-my-opencode/src/shared/model-resolver.ts +0 -35
- package/oh-my-opencode/src/shared/model-sanitizer.ts +0 -12
- package/oh-my-opencode/src/shared/opencode-config-dir.test.ts +0 -318
- package/oh-my-opencode/src/shared/opencode-config-dir.ts +0 -142
- package/oh-my-opencode/src/shared/opencode-version.test.ts +0 -223
- package/oh-my-opencode/src/shared/opencode-version.ts +0 -72
- package/oh-my-opencode/src/shared/pattern-matcher.ts +0 -29
- package/oh-my-opencode/src/shared/permission-compat.test.ts +0 -134
- package/oh-my-opencode/src/shared/permission-compat.ts +0 -77
- package/oh-my-opencode/src/shared/session-cursor.test.ts +0 -66
- package/oh-my-opencode/src/shared/session-cursor.ts +0 -85
- package/oh-my-opencode/src/shared/shell-env.test.ts +0 -278
- package/oh-my-opencode/src/shared/shell-env.ts +0 -111
- package/oh-my-opencode/src/shared/snake-case.ts +0 -49
- package/oh-my-opencode/src/shared/system-directive.ts +0 -40
- package/oh-my-opencode/src/shared/tool-name.ts +0 -26
- package/oh-my-opencode/src/shared/zip-extractor.ts +0 -83
- package/oh-my-opencode/src/tools/AGENTS.md +0 -74
- package/oh-my-opencode/src/tools/ast-grep/cli.ts +0 -230
- package/oh-my-opencode/src/tools/ast-grep/constants.ts +0 -261
- package/oh-my-opencode/src/tools/ast-grep/downloader.ts +0 -128
- package/oh-my-opencode/src/tools/ast-grep/index.ts +0 -13
- package/oh-my-opencode/src/tools/ast-grep/tools.ts +0 -112
- package/oh-my-opencode/src/tools/ast-grep/types.ts +0 -61
- package/oh-my-opencode/src/tools/ast-grep/utils.ts +0 -102
- package/oh-my-opencode/src/tools/background-task/constants.ts +0 -7
- package/oh-my-opencode/src/tools/background-task/index.ts +0 -7
- package/oh-my-opencode/src/tools/background-task/tools.ts +0 -479
- package/oh-my-opencode/src/tools/background-task/types.ts +0 -16
- package/oh-my-opencode/src/tools/call-omo-agent/constants.ts +0 -7
- package/oh-my-opencode/src/tools/call-omo-agent/index.ts +0 -3
- package/oh-my-opencode/src/tools/call-omo-agent/tools.ts +0 -338
- package/oh-my-opencode/src/tools/call-omo-agent/types.ts +0 -27
- package/oh-my-opencode/src/tools/delegate-task/constants.ts +0 -205
- package/oh-my-opencode/src/tools/delegate-task/index.ts +0 -3
- package/oh-my-opencode/src/tools/delegate-task/tools.test.ts +0 -1575
- package/oh-my-opencode/src/tools/delegate-task/tools.ts +0 -885
- package/oh-my-opencode/src/tools/delegate-task/types.ts +0 -9
- package/oh-my-opencode/src/tools/glob/cli.test.ts +0 -158
- package/oh-my-opencode/src/tools/glob/cli.ts +0 -191
- package/oh-my-opencode/src/tools/glob/constants.ts +0 -12
- package/oh-my-opencode/src/tools/glob/index.ts +0 -3
- package/oh-my-opencode/src/tools/glob/tools.ts +0 -41
- package/oh-my-opencode/src/tools/glob/types.ts +0 -22
- package/oh-my-opencode/src/tools/glob/utils.ts +0 -26
- package/oh-my-opencode/src/tools/grep/cli.ts +0 -229
- package/oh-my-opencode/src/tools/grep/constants.ts +0 -127
- package/oh-my-opencode/src/tools/grep/downloader.test.ts +0 -103
- package/oh-my-opencode/src/tools/grep/downloader.ts +0 -145
- package/oh-my-opencode/src/tools/grep/index.ts +0 -3
- package/oh-my-opencode/src/tools/grep/tools.ts +0 -40
- package/oh-my-opencode/src/tools/grep/types.ts +0 -39
- package/oh-my-opencode/src/tools/grep/utils.ts +0 -53
- package/oh-my-opencode/src/tools/index.ts +0 -72
- package/oh-my-opencode/src/tools/interactive-bash/constants.ts +0 -18
- package/oh-my-opencode/src/tools/interactive-bash/index.ts +0 -4
- package/oh-my-opencode/src/tools/interactive-bash/tools.ts +0 -126
- package/oh-my-opencode/src/tools/interactive-bash/utils.ts +0 -71
- package/oh-my-opencode/src/tools/look-at/constants.ts +0 -3
- package/oh-my-opencode/src/tools/look-at/index.ts +0 -3
- package/oh-my-opencode/src/tools/look-at/tools.test.ts +0 -73
- package/oh-my-opencode/src/tools/look-at/tools.ts +0 -173
- package/oh-my-opencode/src/tools/look-at/types.ts +0 -4
- package/oh-my-opencode/src/tools/lsp/client.ts +0 -596
- package/oh-my-opencode/src/tools/lsp/config.test.ts +0 -130
- package/oh-my-opencode/src/tools/lsp/config.ts +0 -285
- package/oh-my-opencode/src/tools/lsp/constants.ts +0 -390
- package/oh-my-opencode/src/tools/lsp/index.ts +0 -7
- package/oh-my-opencode/src/tools/lsp/tools.ts +0 -261
- package/oh-my-opencode/src/tools/lsp/types.ts +0 -124
- package/oh-my-opencode/src/tools/lsp/utils.ts +0 -406
- package/oh-my-opencode/src/tools/session-manager/constants.ts +0 -97
- package/oh-my-opencode/src/tools/session-manager/index.ts +0 -3
- package/oh-my-opencode/src/tools/session-manager/storage.test.ts +0 -315
- package/oh-my-opencode/src/tools/session-manager/storage.ts +0 -238
- package/oh-my-opencode/src/tools/session-manager/tools.test.ts +0 -124
- package/oh-my-opencode/src/tools/session-manager/tools.ts +0 -146
- package/oh-my-opencode/src/tools/session-manager/types.ts +0 -99
- package/oh-my-opencode/src/tools/session-manager/utils.test.ts +0 -160
- package/oh-my-opencode/src/tools/session-manager/utils.ts +0 -199
- package/oh-my-opencode/src/tools/skill/constants.ts +0 -8
- package/oh-my-opencode/src/tools/skill/index.ts +0 -3
- package/oh-my-opencode/src/tools/skill/tools.test.ts +0 -239
- package/oh-my-opencode/src/tools/skill/tools.ts +0 -200
- package/oh-my-opencode/src/tools/skill/types.ts +0 -31
- package/oh-my-opencode/src/tools/skill-mcp/constants.ts +0 -3
- package/oh-my-opencode/src/tools/skill-mcp/index.ts +0 -3
- package/oh-my-opencode/src/tools/skill-mcp/tools.test.ts +0 -215
- package/oh-my-opencode/src/tools/skill-mcp/tools.ts +0 -172
- package/oh-my-opencode/src/tools/skill-mcp/types.ts +0 -8
- package/oh-my-opencode/src/tools/slashcommand/index.ts +0 -2
- package/oh-my-opencode/src/tools/slashcommand/tools.ts +0 -252
- package/oh-my-opencode/src/tools/slashcommand/types.ts +0 -28
- package/oh-my-opencode/test-setup.ts +0 -6
- package/oh-my-opencode/tsconfig.json +0 -20
|
@@ -1,835 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import { tmpdir } from "node:os"
|
|
5
|
-
import { createRalphLoopHook } from "./index"
|
|
6
|
-
import { readState, writeState, clearState } from "./storage"
|
|
7
|
-
import type { RalphLoopState } from "./types"
|
|
8
|
-
|
|
9
|
-
describe("ralph-loop", () => {
|
|
10
|
-
const TEST_DIR = join(tmpdir(), "ralph-loop-test-" + Date.now())
|
|
11
|
-
let promptCalls: Array<{ sessionID: string; text: string }>
|
|
12
|
-
let toastCalls: Array<{ title: string; message: string; variant: string }>
|
|
13
|
-
let messagesCalls: Array<{ sessionID: string }>
|
|
14
|
-
let mockSessionMessages: Array<{ info?: { role?: string }; parts?: Array<{ type: string; text?: string }> }>
|
|
15
|
-
|
|
16
|
-
function createMockPluginInput() {
|
|
17
|
-
return {
|
|
18
|
-
client: {
|
|
19
|
-
session: {
|
|
20
|
-
prompt: async (opts: { path: { id: string }; body: { parts: Array<{ type: string; text: string }> } }) => {
|
|
21
|
-
promptCalls.push({
|
|
22
|
-
sessionID: opts.path.id,
|
|
23
|
-
text: opts.body.parts[0].text,
|
|
24
|
-
})
|
|
25
|
-
return {}
|
|
26
|
-
},
|
|
27
|
-
messages: async (opts: { path: { id: string } }) => {
|
|
28
|
-
messagesCalls.push({ sessionID: opts.path.id })
|
|
29
|
-
return { data: mockSessionMessages }
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
tui: {
|
|
33
|
-
showToast: async (opts: { body: { title: string; message: string; variant: string } }) => {
|
|
34
|
-
toastCalls.push({
|
|
35
|
-
title: opts.body.title,
|
|
36
|
-
message: opts.body.message,
|
|
37
|
-
variant: opts.body.variant,
|
|
38
|
-
})
|
|
39
|
-
return {}
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
directory: TEST_DIR,
|
|
44
|
-
} as unknown as Parameters<typeof createRalphLoopHook>[0]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
promptCalls = []
|
|
49
|
-
toastCalls = []
|
|
50
|
-
messagesCalls = []
|
|
51
|
-
mockSessionMessages = []
|
|
52
|
-
|
|
53
|
-
if (!existsSync(TEST_DIR)) {
|
|
54
|
-
mkdirSync(TEST_DIR, { recursive: true })
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
clearState(TEST_DIR)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
clearState(TEST_DIR)
|
|
62
|
-
if (existsSync(TEST_DIR)) {
|
|
63
|
-
rmSync(TEST_DIR, { recursive: true, force: true })
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe("storage", () => {
|
|
68
|
-
test("should write and read state correctly", () => {
|
|
69
|
-
// #given - a state object
|
|
70
|
-
const state: RalphLoopState = {
|
|
71
|
-
active: true,
|
|
72
|
-
iteration: 1,
|
|
73
|
-
max_iterations: 50,
|
|
74
|
-
completion_promise: "DONE",
|
|
75
|
-
started_at: "2025-12-30T01:00:00Z",
|
|
76
|
-
prompt: "Build a REST API",
|
|
77
|
-
session_id: "test-session-123",
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// #when - write and read state
|
|
81
|
-
const writeSuccess = writeState(TEST_DIR, state)
|
|
82
|
-
const readResult = readState(TEST_DIR)
|
|
83
|
-
|
|
84
|
-
// #then - state should match
|
|
85
|
-
expect(writeSuccess).toBe(true)
|
|
86
|
-
expect(readResult).not.toBeNull()
|
|
87
|
-
expect(readResult?.active).toBe(true)
|
|
88
|
-
expect(readResult?.iteration).toBe(1)
|
|
89
|
-
expect(readResult?.max_iterations).toBe(50)
|
|
90
|
-
expect(readResult?.completion_promise).toBe("DONE")
|
|
91
|
-
expect(readResult?.prompt).toBe("Build a REST API")
|
|
92
|
-
expect(readResult?.session_id).toBe("test-session-123")
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test("should handle ultrawork field", () => {
|
|
96
|
-
// #given - a state object with ultrawork enabled
|
|
97
|
-
const state: RalphLoopState = {
|
|
98
|
-
active: true,
|
|
99
|
-
iteration: 1,
|
|
100
|
-
max_iterations: 50,
|
|
101
|
-
completion_promise: "DONE",
|
|
102
|
-
started_at: "2025-12-30T01:00:00Z",
|
|
103
|
-
prompt: "Build a REST API",
|
|
104
|
-
session_id: "test-session-123",
|
|
105
|
-
ultrawork: true,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// #when - write and read state
|
|
109
|
-
writeState(TEST_DIR, state)
|
|
110
|
-
const readResult = readState(TEST_DIR)
|
|
111
|
-
|
|
112
|
-
// #then - ultrawork field should be preserved
|
|
113
|
-
expect(readResult?.ultrawork).toBe(true)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
test("should return null for non-existent state", () => {
|
|
117
|
-
// #given - no state file exists
|
|
118
|
-
// #when - read state
|
|
119
|
-
const result = readState(TEST_DIR)
|
|
120
|
-
|
|
121
|
-
// #then - should return null
|
|
122
|
-
expect(result).toBeNull()
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
test("should clear state correctly", () => {
|
|
126
|
-
// #given - existing state
|
|
127
|
-
const state: RalphLoopState = {
|
|
128
|
-
active: true,
|
|
129
|
-
iteration: 1,
|
|
130
|
-
max_iterations: 50,
|
|
131
|
-
completion_promise: "DONE",
|
|
132
|
-
started_at: "2025-12-30T01:00:00Z",
|
|
133
|
-
prompt: "Test prompt",
|
|
134
|
-
}
|
|
135
|
-
writeState(TEST_DIR, state)
|
|
136
|
-
|
|
137
|
-
// #when - clear state
|
|
138
|
-
const clearSuccess = clearState(TEST_DIR)
|
|
139
|
-
const readResult = readState(TEST_DIR)
|
|
140
|
-
|
|
141
|
-
// #then - state should be cleared
|
|
142
|
-
expect(clearSuccess).toBe(true)
|
|
143
|
-
expect(readResult).toBeNull()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test("should handle multiline prompts", () => {
|
|
147
|
-
// #given - state with multiline prompt
|
|
148
|
-
const state: RalphLoopState = {
|
|
149
|
-
active: true,
|
|
150
|
-
iteration: 1,
|
|
151
|
-
max_iterations: 10,
|
|
152
|
-
completion_promise: "FINISHED",
|
|
153
|
-
started_at: "2025-12-30T02:00:00Z",
|
|
154
|
-
prompt: "Build a feature\nwith multiple lines\nand requirements",
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// #when - write and read
|
|
158
|
-
writeState(TEST_DIR, state)
|
|
159
|
-
const readResult = readState(TEST_DIR)
|
|
160
|
-
|
|
161
|
-
// #then - multiline prompt preserved
|
|
162
|
-
expect(readResult?.prompt).toBe("Build a feature\nwith multiple lines\nand requirements")
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
describe("hook", () => {
|
|
167
|
-
test("should start loop and write state", () => {
|
|
168
|
-
// #given - hook instance
|
|
169
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
170
|
-
|
|
171
|
-
// #when - start loop
|
|
172
|
-
const success = hook.startLoop("session-123", "Build something", {
|
|
173
|
-
maxIterations: 25,
|
|
174
|
-
completionPromise: "FINISHED",
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
// #then - state should be written
|
|
178
|
-
expect(success).toBe(true)
|
|
179
|
-
const state = hook.getState()
|
|
180
|
-
expect(state?.active).toBe(true)
|
|
181
|
-
expect(state?.iteration).toBe(1)
|
|
182
|
-
expect(state?.max_iterations).toBe(25)
|
|
183
|
-
expect(state?.completion_promise).toBe("FINISHED")
|
|
184
|
-
expect(state?.prompt).toBe("Build something")
|
|
185
|
-
expect(state?.session_id).toBe("session-123")
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
test("should accept ultrawork option in startLoop", () => {
|
|
189
|
-
// #given - hook instance
|
|
190
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
191
|
-
|
|
192
|
-
// #when - start loop with ultrawork
|
|
193
|
-
hook.startLoop("session-123", "Build something", { ultrawork: true })
|
|
194
|
-
|
|
195
|
-
// #then - state should have ultrawork=true
|
|
196
|
-
const state = hook.getState()
|
|
197
|
-
expect(state?.ultrawork).toBe(true)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
test("should handle missing ultrawork option in startLoop", () => {
|
|
201
|
-
// #given - hook instance
|
|
202
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
203
|
-
|
|
204
|
-
// #when - start loop without ultrawork
|
|
205
|
-
hook.startLoop("session-123", "Build something")
|
|
206
|
-
|
|
207
|
-
// #then - state should have ultrawork=undefined
|
|
208
|
-
const state = hook.getState()
|
|
209
|
-
expect(state?.ultrawork).toBeUndefined()
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
test("should inject continuation when loop active and no completion detected", async () => {
|
|
213
|
-
// #given - active loop state
|
|
214
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
215
|
-
hook.startLoop("session-123", "Build a feature", { maxIterations: 10 })
|
|
216
|
-
|
|
217
|
-
// #when - session goes idle
|
|
218
|
-
await hook.event({
|
|
219
|
-
event: {
|
|
220
|
-
type: "session.idle",
|
|
221
|
-
properties: { sessionID: "session-123" },
|
|
222
|
-
},
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// #then - continuation should be injected
|
|
226
|
-
expect(promptCalls.length).toBe(1)
|
|
227
|
-
expect(promptCalls[0].sessionID).toBe("session-123")
|
|
228
|
-
expect(promptCalls[0].text).toContain("RALPH LOOP")
|
|
229
|
-
expect(promptCalls[0].text).toContain("Build a feature")
|
|
230
|
-
expect(promptCalls[0].text).toContain("2/10")
|
|
231
|
-
|
|
232
|
-
// #then - iteration should be incremented
|
|
233
|
-
const state = hook.getState()
|
|
234
|
-
expect(state?.iteration).toBe(2)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
test("should stop loop when max iterations reached", async () => {
|
|
238
|
-
// #given - loop at max iteration
|
|
239
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
240
|
-
hook.startLoop("session-123", "Build something", { maxIterations: 2 })
|
|
241
|
-
|
|
242
|
-
const state = hook.getState()!
|
|
243
|
-
state.iteration = 2
|
|
244
|
-
writeState(TEST_DIR, state)
|
|
245
|
-
|
|
246
|
-
// #when - session goes idle
|
|
247
|
-
await hook.event({
|
|
248
|
-
event: {
|
|
249
|
-
type: "session.idle",
|
|
250
|
-
properties: { sessionID: "session-123" },
|
|
251
|
-
},
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
// #then - no continuation injected
|
|
255
|
-
expect(promptCalls.length).toBe(0)
|
|
256
|
-
|
|
257
|
-
// #then - warning toast shown
|
|
258
|
-
expect(toastCalls.length).toBe(1)
|
|
259
|
-
expect(toastCalls[0].title).toBe("Ralph Loop Stopped")
|
|
260
|
-
expect(toastCalls[0].variant).toBe("warning")
|
|
261
|
-
|
|
262
|
-
// #then - state should be cleared
|
|
263
|
-
expect(hook.getState()).toBeNull()
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
test("should cancel loop via cancelLoop", () => {
|
|
267
|
-
// #given - active loop
|
|
268
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
269
|
-
hook.startLoop("session-123", "Test task")
|
|
270
|
-
|
|
271
|
-
// #when - cancel loop
|
|
272
|
-
const success = hook.cancelLoop("session-123")
|
|
273
|
-
|
|
274
|
-
// #then - loop cancelled
|
|
275
|
-
expect(success).toBe(true)
|
|
276
|
-
expect(hook.getState()).toBeNull()
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
test("should not cancel loop for different session", () => {
|
|
280
|
-
// #given - active loop for session-123
|
|
281
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
282
|
-
hook.startLoop("session-123", "Test task")
|
|
283
|
-
|
|
284
|
-
// #when - try to cancel for different session
|
|
285
|
-
const success = hook.cancelLoop("session-456")
|
|
286
|
-
|
|
287
|
-
// #then - cancel should fail
|
|
288
|
-
expect(success).toBe(false)
|
|
289
|
-
expect(hook.getState()).not.toBeNull()
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
test("should skip injection during recovery", async () => {
|
|
293
|
-
// #given - active loop and session in recovery
|
|
294
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
295
|
-
hook.startLoop("session-123", "Test task")
|
|
296
|
-
|
|
297
|
-
await hook.event({
|
|
298
|
-
event: {
|
|
299
|
-
type: "session.error",
|
|
300
|
-
properties: { sessionID: "session-123", error: new Error("test") },
|
|
301
|
-
},
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
// #when - session goes idle immediately
|
|
305
|
-
await hook.event({
|
|
306
|
-
event: {
|
|
307
|
-
type: "session.idle",
|
|
308
|
-
properties: { sessionID: "session-123" },
|
|
309
|
-
},
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
// #then - no continuation injected
|
|
313
|
-
expect(promptCalls.length).toBe(0)
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
test("should clear state on session deletion", async () => {
|
|
317
|
-
// #given - active loop
|
|
318
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
319
|
-
hook.startLoop("session-123", "Test task")
|
|
320
|
-
|
|
321
|
-
// #when - session deleted
|
|
322
|
-
await hook.event({
|
|
323
|
-
event: {
|
|
324
|
-
type: "session.deleted",
|
|
325
|
-
properties: { info: { id: "session-123" } },
|
|
326
|
-
},
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
// #then - state should be cleared
|
|
330
|
-
expect(hook.getState()).toBeNull()
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
test("should not inject for different session than loop owner", async () => {
|
|
334
|
-
// #given - loop owned by session-123
|
|
335
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
336
|
-
hook.startLoop("session-123", "Test task")
|
|
337
|
-
|
|
338
|
-
// #when - different session goes idle
|
|
339
|
-
await hook.event({
|
|
340
|
-
event: {
|
|
341
|
-
type: "session.idle",
|
|
342
|
-
properties: { sessionID: "session-456" },
|
|
343
|
-
},
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
// #then - no continuation injected
|
|
347
|
-
expect(promptCalls.length).toBe(0)
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
test("should clear orphaned state when original session no longer exists", async () => {
|
|
351
|
-
// #given - state file exists from a previous session that no longer exists
|
|
352
|
-
const state: RalphLoopState = {
|
|
353
|
-
active: true,
|
|
354
|
-
iteration: 3,
|
|
355
|
-
max_iterations: 50,
|
|
356
|
-
completion_promise: "DONE",
|
|
357
|
-
started_at: "2025-12-30T01:00:00Z",
|
|
358
|
-
prompt: "Build something",
|
|
359
|
-
session_id: "orphaned-session-999", // This session no longer exists
|
|
360
|
-
}
|
|
361
|
-
writeState(TEST_DIR, state)
|
|
362
|
-
|
|
363
|
-
// Mock sessionExists to return false for the orphaned session
|
|
364
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
365
|
-
checkSessionExists: async (sessionID: string) => {
|
|
366
|
-
// Orphaned session doesn't exist, current session does
|
|
367
|
-
return sessionID !== "orphaned-session-999"
|
|
368
|
-
},
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
// #when - a new session goes idle (different from the orphaned session in state)
|
|
372
|
-
await hook.event({
|
|
373
|
-
event: {
|
|
374
|
-
type: "session.idle",
|
|
375
|
-
properties: { sessionID: "new-session-456" },
|
|
376
|
-
},
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// #then - orphaned state should be cleared
|
|
380
|
-
expect(hook.getState()).toBeNull()
|
|
381
|
-
// #then - no continuation injected (state was cleared, not resumed)
|
|
382
|
-
expect(promptCalls.length).toBe(0)
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
test("should NOT clear state when original session still exists (different active session)", async () => {
|
|
386
|
-
// #given - state file exists from a session that still exists
|
|
387
|
-
const state: RalphLoopState = {
|
|
388
|
-
active: true,
|
|
389
|
-
iteration: 2,
|
|
390
|
-
max_iterations: 50,
|
|
391
|
-
completion_promise: "DONE",
|
|
392
|
-
started_at: "2025-12-30T01:00:00Z",
|
|
393
|
-
prompt: "Build something",
|
|
394
|
-
session_id: "active-session-123", // This session still exists
|
|
395
|
-
}
|
|
396
|
-
writeState(TEST_DIR, state)
|
|
397
|
-
|
|
398
|
-
// Mock sessionExists to return true for the active session
|
|
399
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
400
|
-
checkSessionExists: async (sessionID: string) => {
|
|
401
|
-
// Original session still exists
|
|
402
|
-
return sessionID === "active-session-123" || sessionID === "new-session-456"
|
|
403
|
-
},
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
// #when - a different session goes idle
|
|
407
|
-
await hook.event({
|
|
408
|
-
event: {
|
|
409
|
-
type: "session.idle",
|
|
410
|
-
properties: { sessionID: "new-session-456" },
|
|
411
|
-
},
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
// #then - state should NOT be cleared (original session still active)
|
|
415
|
-
expect(hook.getState()).not.toBeNull()
|
|
416
|
-
expect(hook.getState()?.session_id).toBe("active-session-123")
|
|
417
|
-
// #then - no continuation injected (it's a different session's loop)
|
|
418
|
-
expect(promptCalls.length).toBe(0)
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
test("should use default config values", () => {
|
|
422
|
-
// #given - hook with config
|
|
423
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
424
|
-
config: {
|
|
425
|
-
enabled: true,
|
|
426
|
-
default_max_iterations: 200,
|
|
427
|
-
},
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
// #when - start loop without options
|
|
431
|
-
hook.startLoop("session-123", "Test task")
|
|
432
|
-
|
|
433
|
-
// #then - should use config defaults
|
|
434
|
-
const state = hook.getState()
|
|
435
|
-
expect(state?.max_iterations).toBe(200)
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
test("should not inject when no loop is active", async () => {
|
|
439
|
-
// #given - no active loop
|
|
440
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
441
|
-
|
|
442
|
-
// #when - session goes idle
|
|
443
|
-
await hook.event({
|
|
444
|
-
event: {
|
|
445
|
-
type: "session.idle",
|
|
446
|
-
properties: { sessionID: "session-123" },
|
|
447
|
-
},
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
// #then - no continuation injected
|
|
451
|
-
expect(promptCalls.length).toBe(0)
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
test("should detect completion promise and stop loop", async () => {
|
|
455
|
-
// #given - active loop with transcript containing completion
|
|
456
|
-
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
|
|
457
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
458
|
-
getTranscriptPath: () => transcriptPath,
|
|
459
|
-
})
|
|
460
|
-
hook.startLoop("session-123", "Build something", { completionPromise: "COMPLETE" })
|
|
461
|
-
|
|
462
|
-
writeFileSync(transcriptPath, JSON.stringify({ content: "Task done <promise>COMPLETE</promise>" }))
|
|
463
|
-
|
|
464
|
-
// #when - session goes idle (transcriptPath now derived from sessionID via getTranscriptPath)
|
|
465
|
-
await hook.event({
|
|
466
|
-
event: {
|
|
467
|
-
type: "session.idle",
|
|
468
|
-
properties: { sessionID: "session-123" },
|
|
469
|
-
},
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
// #then - loop completed, no continuation
|
|
473
|
-
expect(promptCalls.length).toBe(0)
|
|
474
|
-
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(true)
|
|
475
|
-
expect(hook.getState()).toBeNull()
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
test("should detect completion promise via session messages API", async () => {
|
|
479
|
-
// #given - active loop with assistant message containing completion promise
|
|
480
|
-
mockSessionMessages = [
|
|
481
|
-
{ info: { role: "user" }, parts: [{ type: "text", text: "Build something" }] },
|
|
482
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "I have completed the task. <promise>API_DONE</promise>" }] },
|
|
483
|
-
]
|
|
484
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
485
|
-
getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"),
|
|
486
|
-
})
|
|
487
|
-
hook.startLoop("session-123", "Build something", { completionPromise: "API_DONE" })
|
|
488
|
-
|
|
489
|
-
// #when - session goes idle
|
|
490
|
-
await hook.event({
|
|
491
|
-
event: {
|
|
492
|
-
type: "session.idle",
|
|
493
|
-
properties: { sessionID: "session-123" },
|
|
494
|
-
},
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
// #then - loop completed via API detection, no continuation
|
|
498
|
-
expect(promptCalls.length).toBe(0)
|
|
499
|
-
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(true)
|
|
500
|
-
expect(hook.getState()).toBeNull()
|
|
501
|
-
|
|
502
|
-
// #then - messages API was called with correct session ID
|
|
503
|
-
expect(messagesCalls.length).toBe(1)
|
|
504
|
-
expect(messagesCalls[0].sessionID).toBe("session-123")
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
test("should handle multiple iterations correctly", async () => {
|
|
508
|
-
// #given - active loop
|
|
509
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
510
|
-
hook.startLoop("session-123", "Build feature", { maxIterations: 5 })
|
|
511
|
-
|
|
512
|
-
// #when - multiple idle events
|
|
513
|
-
await hook.event({
|
|
514
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
515
|
-
})
|
|
516
|
-
await hook.event({
|
|
517
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
// #then - iteration incremented correctly
|
|
521
|
-
expect(hook.getState()?.iteration).toBe(3)
|
|
522
|
-
expect(promptCalls.length).toBe(2)
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
test("should include prompt and promise in continuation message", async () => {
|
|
526
|
-
// #given - loop with specific prompt and promise
|
|
527
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
528
|
-
hook.startLoop("session-123", "Create a calculator app", {
|
|
529
|
-
completionPromise: "CALCULATOR_DONE",
|
|
530
|
-
maxIterations: 10,
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
// #when - session goes idle
|
|
534
|
-
await hook.event({
|
|
535
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
// #then - continuation includes original task and promise
|
|
539
|
-
expect(promptCalls[0].text).toContain("Create a calculator app")
|
|
540
|
-
expect(promptCalls[0].text).toContain("<promise>CALCULATOR_DONE</promise>")
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
test("should clear loop state on user abort (MessageAbortedError)", async () => {
|
|
544
|
-
// #given - active loop
|
|
545
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
546
|
-
hook.startLoop("session-123", "Build something")
|
|
547
|
-
expect(hook.getState()).not.toBeNull()
|
|
548
|
-
|
|
549
|
-
// #when - user aborts (Ctrl+C)
|
|
550
|
-
await hook.event({
|
|
551
|
-
event: {
|
|
552
|
-
type: "session.error",
|
|
553
|
-
properties: {
|
|
554
|
-
sessionID: "session-123",
|
|
555
|
-
error: { name: "MessageAbortedError", message: "User aborted" },
|
|
556
|
-
},
|
|
557
|
-
},
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
// #then - loop state should be cleared immediately
|
|
561
|
-
expect(hook.getState()).toBeNull()
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
test("should NOT set recovery mode on user abort", async () => {
|
|
565
|
-
// #given - active loop
|
|
566
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
567
|
-
hook.startLoop("session-123", "Build something")
|
|
568
|
-
|
|
569
|
-
// #when - user aborts (Ctrl+C)
|
|
570
|
-
await hook.event({
|
|
571
|
-
event: {
|
|
572
|
-
type: "session.error",
|
|
573
|
-
properties: {
|
|
574
|
-
sessionID: "session-123",
|
|
575
|
-
error: { name: "MessageAbortedError" },
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
// Start a new loop
|
|
581
|
-
hook.startLoop("session-123", "New task")
|
|
582
|
-
|
|
583
|
-
// #when - session goes idle immediately (should work, no recovery mode)
|
|
584
|
-
await hook.event({
|
|
585
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
// #then - continuation should be injected (not blocked by recovery)
|
|
589
|
-
expect(promptCalls.length).toBe(1)
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
test("should only check LAST assistant message for completion", async () => {
|
|
593
|
-
// #given - multiple assistant messages, only first has completion promise
|
|
594
|
-
mockSessionMessages = [
|
|
595
|
-
{ info: { role: "user" }, parts: [{ type: "text", text: "Start task" }] },
|
|
596
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "I'll work on it. <promise>DONE</promise>" }] },
|
|
597
|
-
{ info: { role: "user" }, parts: [{ type: "text", text: "Continue" }] },
|
|
598
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "Working on more features..." }] },
|
|
599
|
-
]
|
|
600
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
601
|
-
getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"),
|
|
602
|
-
})
|
|
603
|
-
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
|
|
604
|
-
|
|
605
|
-
// #when - session goes idle
|
|
606
|
-
await hook.event({
|
|
607
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
608
|
-
})
|
|
609
|
-
|
|
610
|
-
// #then - loop should continue (last message has no completion promise)
|
|
611
|
-
expect(promptCalls.length).toBe(1)
|
|
612
|
-
expect(hook.getState()?.iteration).toBe(2)
|
|
613
|
-
})
|
|
614
|
-
|
|
615
|
-
test("should detect completion only in LAST assistant message", async () => {
|
|
616
|
-
// #given - last assistant message has completion promise
|
|
617
|
-
mockSessionMessages = [
|
|
618
|
-
{ info: { role: "user" }, parts: [{ type: "text", text: "Start task" }] },
|
|
619
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "Starting work..." }] },
|
|
620
|
-
{ info: { role: "user" }, parts: [{ type: "text", text: "Continue" }] },
|
|
621
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "Task complete! <promise>DONE</promise>" }] },
|
|
622
|
-
]
|
|
623
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
624
|
-
getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"),
|
|
625
|
-
})
|
|
626
|
-
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
|
|
627
|
-
|
|
628
|
-
// #when - session goes idle
|
|
629
|
-
await hook.event({
|
|
630
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
// #then - loop should complete (last message has completion promise)
|
|
634
|
-
expect(promptCalls.length).toBe(0)
|
|
635
|
-
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(true)
|
|
636
|
-
expect(hook.getState()).toBeNull()
|
|
637
|
-
})
|
|
638
|
-
|
|
639
|
-
test("should allow starting new loop while previous loop is active (different session)", async () => {
|
|
640
|
-
// #given - active loop in session A
|
|
641
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
642
|
-
hook.startLoop("session-A", "First task", { maxIterations: 10 })
|
|
643
|
-
expect(hook.getState()?.session_id).toBe("session-A")
|
|
644
|
-
expect(hook.getState()?.prompt).toBe("First task")
|
|
645
|
-
|
|
646
|
-
// #when - start new loop in session B (without completing A)
|
|
647
|
-
hook.startLoop("session-B", "Second task", { maxIterations: 20 })
|
|
648
|
-
|
|
649
|
-
// #then - state should be overwritten with session B's loop
|
|
650
|
-
expect(hook.getState()?.session_id).toBe("session-B")
|
|
651
|
-
expect(hook.getState()?.prompt).toBe("Second task")
|
|
652
|
-
expect(hook.getState()?.max_iterations).toBe(20)
|
|
653
|
-
expect(hook.getState()?.iteration).toBe(1)
|
|
654
|
-
|
|
655
|
-
// #when - session B goes idle
|
|
656
|
-
await hook.event({
|
|
657
|
-
event: { type: "session.idle", properties: { sessionID: "session-B" } },
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
// #then - continuation should be injected for session B
|
|
661
|
-
expect(promptCalls.length).toBe(1)
|
|
662
|
-
expect(promptCalls[0].sessionID).toBe("session-B")
|
|
663
|
-
expect(promptCalls[0].text).toContain("Second task")
|
|
664
|
-
expect(promptCalls[0].text).toContain("2/20")
|
|
665
|
-
|
|
666
|
-
// #then - iteration incremented
|
|
667
|
-
expect(hook.getState()?.iteration).toBe(2)
|
|
668
|
-
})
|
|
669
|
-
|
|
670
|
-
test("should allow starting new loop in same session (restart)", async () => {
|
|
671
|
-
// #given - active loop in session A at iteration 5
|
|
672
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
673
|
-
hook.startLoop("session-A", "First task", { maxIterations: 10 })
|
|
674
|
-
|
|
675
|
-
// Simulate some iterations
|
|
676
|
-
await hook.event({
|
|
677
|
-
event: { type: "session.idle", properties: { sessionID: "session-A" } },
|
|
678
|
-
})
|
|
679
|
-
await hook.event({
|
|
680
|
-
event: { type: "session.idle", properties: { sessionID: "session-A" } },
|
|
681
|
-
})
|
|
682
|
-
expect(hook.getState()?.iteration).toBe(3)
|
|
683
|
-
expect(promptCalls.length).toBe(2)
|
|
684
|
-
|
|
685
|
-
// #when - start NEW loop in same session (restart)
|
|
686
|
-
hook.startLoop("session-A", "Restarted task", { maxIterations: 50 })
|
|
687
|
-
|
|
688
|
-
// #then - state should be reset to iteration 1 with new prompt
|
|
689
|
-
expect(hook.getState()?.session_id).toBe("session-A")
|
|
690
|
-
expect(hook.getState()?.prompt).toBe("Restarted task")
|
|
691
|
-
expect(hook.getState()?.max_iterations).toBe(50)
|
|
692
|
-
expect(hook.getState()?.iteration).toBe(1)
|
|
693
|
-
|
|
694
|
-
// #when - session goes idle
|
|
695
|
-
promptCalls = [] // Reset to check new continuation
|
|
696
|
-
await hook.event({
|
|
697
|
-
event: { type: "session.idle", properties: { sessionID: "session-A" } },
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
// #then - continuation should use new task
|
|
701
|
-
expect(promptCalls.length).toBe(1)
|
|
702
|
-
expect(promptCalls[0].text).toContain("Restarted task")
|
|
703
|
-
expect(promptCalls[0].text).toContain("2/50")
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
test("should check transcript BEFORE API to optimize performance", async () => {
|
|
707
|
-
// #given - transcript has completion promise
|
|
708
|
-
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
|
|
709
|
-
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
|
|
710
|
-
mockSessionMessages = [
|
|
711
|
-
{ info: { role: "assistant" }, parts: [{ type: "text", text: "No promise here" }] },
|
|
712
|
-
]
|
|
713
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
714
|
-
getTranscriptPath: () => transcriptPath,
|
|
715
|
-
})
|
|
716
|
-
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
|
|
717
|
-
|
|
718
|
-
// #when - session goes idle
|
|
719
|
-
await hook.event({
|
|
720
|
-
event: {
|
|
721
|
-
type: "session.idle",
|
|
722
|
-
properties: { sessionID: "session-123" },
|
|
723
|
-
},
|
|
724
|
-
})
|
|
725
|
-
|
|
726
|
-
// #then - should complete via transcript (API not called when transcript succeeds)
|
|
727
|
-
expect(promptCalls.length).toBe(0)
|
|
728
|
-
expect(hook.getState()).toBeNull()
|
|
729
|
-
// API should NOT be called since transcript found completion
|
|
730
|
-
expect(messagesCalls.length).toBe(0)
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
test("should show ultrawork completion toast", async () => {
|
|
734
|
-
// #given - hook with ultrawork mode and completion in transcript
|
|
735
|
-
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
|
|
736
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
737
|
-
getTranscriptPath: () => transcriptPath,
|
|
738
|
-
})
|
|
739
|
-
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
|
|
740
|
-
hook.startLoop("test-id", "Build API", { ultrawork: true })
|
|
741
|
-
|
|
742
|
-
// #when - idle event triggered
|
|
743
|
-
await hook.event({ event: { type: "session.idle", properties: { sessionID: "test-id" } } })
|
|
744
|
-
|
|
745
|
-
// #then - ultrawork toast shown
|
|
746
|
-
const completionToast = toastCalls.find(t => t.title === "ULTRAWORK LOOP COMPLETE!")
|
|
747
|
-
expect(completionToast).toBeDefined()
|
|
748
|
-
expect(completionToast!.message).toMatch(/JUST ULW ULW!/)
|
|
749
|
-
})
|
|
750
|
-
|
|
751
|
-
test("should show regular completion toast when ultrawork disabled", async () => {
|
|
752
|
-
// #given - hook without ultrawork
|
|
753
|
-
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
|
|
754
|
-
const hook = createRalphLoopHook(createMockPluginInput(), {
|
|
755
|
-
getTranscriptPath: () => transcriptPath,
|
|
756
|
-
})
|
|
757
|
-
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
|
|
758
|
-
hook.startLoop("test-id", "Build API")
|
|
759
|
-
|
|
760
|
-
// #when - idle event triggered
|
|
761
|
-
await hook.event({ event: { type: "session.idle", properties: { sessionID: "test-id" } } })
|
|
762
|
-
|
|
763
|
-
// #then - regular toast shown
|
|
764
|
-
expect(toastCalls.some(t => t.title === "Ralph Loop Complete!")).toBe(true)
|
|
765
|
-
})
|
|
766
|
-
|
|
767
|
-
test("should prepend ultrawork to continuation prompt when ultrawork=true", async () => {
|
|
768
|
-
// #given - hook with ultrawork mode enabled
|
|
769
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
770
|
-
hook.startLoop("session-123", "Build API", { ultrawork: true })
|
|
771
|
-
|
|
772
|
-
// #when - session goes idle (continuation triggered)
|
|
773
|
-
await hook.event({
|
|
774
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
775
|
-
})
|
|
776
|
-
|
|
777
|
-
// #then - prompt should start with "ultrawork "
|
|
778
|
-
expect(promptCalls.length).toBe(1)
|
|
779
|
-
expect(promptCalls[0].text).toMatch(/^ultrawork /)
|
|
780
|
-
})
|
|
781
|
-
|
|
782
|
-
test("should NOT prepend ultrawork to continuation prompt when ultrawork=false", async () => {
|
|
783
|
-
// #given - hook without ultrawork mode
|
|
784
|
-
const hook = createRalphLoopHook(createMockPluginInput())
|
|
785
|
-
hook.startLoop("session-123", "Build API")
|
|
786
|
-
|
|
787
|
-
// #when - session goes idle (continuation triggered)
|
|
788
|
-
await hook.event({
|
|
789
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
790
|
-
})
|
|
791
|
-
|
|
792
|
-
// #then - prompt should NOT start with "ultrawork "
|
|
793
|
-
expect(promptCalls.length).toBe(1)
|
|
794
|
-
expect(promptCalls[0].text).not.toMatch(/^ultrawork /)
|
|
795
|
-
})
|
|
796
|
-
})
|
|
797
|
-
|
|
798
|
-
describe("API timeout protection", () => {
|
|
799
|
-
// FIXME: Flaky in CI - times out intermittently
|
|
800
|
-
test.skip("should not hang when session.messages() times out", async () => {
|
|
801
|
-
// #given - slow API that takes longer than timeout
|
|
802
|
-
const slowMock = {
|
|
803
|
-
...createMockPluginInput(),
|
|
804
|
-
client: {
|
|
805
|
-
...createMockPluginInput().client,
|
|
806
|
-
session: {
|
|
807
|
-
...createMockPluginInput().client.session,
|
|
808
|
-
messages: async () => {
|
|
809
|
-
// Simulate slow API (would hang without timeout)
|
|
810
|
-
await new Promise((resolve) => setTimeout(resolve, 10000))
|
|
811
|
-
return { data: [] }
|
|
812
|
-
},
|
|
813
|
-
},
|
|
814
|
-
},
|
|
815
|
-
}
|
|
816
|
-
const hook = createRalphLoopHook(slowMock as any, {
|
|
817
|
-
getTranscriptPath: () => join(TEST_DIR, "nonexistent.jsonl"),
|
|
818
|
-
apiTimeout: 100, // 100ms timeout for test
|
|
819
|
-
})
|
|
820
|
-
hook.startLoop("session-123", "Build something")
|
|
821
|
-
|
|
822
|
-
// #when - session goes idle (API will timeout)
|
|
823
|
-
const startTime = Date.now()
|
|
824
|
-
await hook.event({
|
|
825
|
-
event: { type: "session.idle", properties: { sessionID: "session-123" } },
|
|
826
|
-
})
|
|
827
|
-
const elapsed = Date.now() - startTime
|
|
828
|
-
|
|
829
|
-
// #then - should complete within timeout + buffer (not hang for 10s)
|
|
830
|
-
expect(elapsed).toBeLessThan(500)
|
|
831
|
-
// #then - loop should continue (API timeout = no completion detected)
|
|
832
|
-
expect(promptCalls.length).toBe(1)
|
|
833
|
-
})
|
|
834
|
-
})
|
|
835
|
-
})
|