Compare commits
3 Commits
fix/issue-
...
990bc46477
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
990bc46477 | ||
| e1050cb70a | |||
|
|
0f66de2929 |
67
.github/ISSUES/fix-queue-daemon-excess-agents.md
vendored
67
.github/ISSUES/fix-queue-daemon-excess-agents.md
vendored
@@ -1,67 +0,0 @@
|
||||
# Fix: Queue daemon spawning excess agents due to race condition
|
||||
|
||||
## Problem
|
||||
|
||||
When enqueueing multiple tasks (e.g., 6 tasks), the queue daemon was spawning many more subagents than expected, eventually exhausting container memory.
|
||||
|
||||
**Root Cause:** The combination of:
|
||||
1. `process_queue()` calling `opencode run` directly instead of `kugetsu start`, bypassing all concurrency logic
|
||||
2. `count_active_dev_sessions()` counting `pm-agent.json` toward `MAX_CONCURRENT_AGENTS`, reducing effective dev agent slots
|
||||
3. No atomic locking around session count check + session file creation (TOCTOU race condition)
|
||||
4. Background spawning of multiple concurrent processes in `process_queue()`
|
||||
|
||||
**Expected behavior:** With `MAX_CONCURRENT_AGENTS=3` and 6 tasks:
|
||||
- Tasks should be processed sequentially via `kugetsu start`
|
||||
- Only 3 dev agents should run at a time
|
||||
- Tasks should queue and wait for slots to free up
|
||||
|
||||
## Solution
|
||||
|
||||
### 1. `count_active_dev_sessions()` - Exclude pm-agent
|
||||
Only count actual dev agent session files (exclude `pm-agent.json`).
|
||||
|
||||
### 2. `process_queue()` - Call `kugetsu start` directly + retry logic
|
||||
- Call `kugetsu start` directly (foreground, sequential) instead of spawning `opencode run` background process
|
||||
- Dynamic batch size = available slots (removes need for `QUEUE_DAEMON_BATCH_SIZE`)
|
||||
- Retry logic (max 3 attempts) on failure
|
||||
- On failure: cleanup worktree/session and revert to `pending` state
|
||||
- Save `fork_pid` to queue item for timeout handling
|
||||
|
||||
### 3. `cmd_start()` - Add flock
|
||||
- Add flock around critical section (count check + fork)
|
||||
- Track `fork_pid` for queue item timeout handling
|
||||
|
||||
### 4. Notification System
|
||||
New notification types:
|
||||
| Event | Type |
|
||||
|-------|------|
|
||||
| Task enqueued | `task_queued` |
|
||||
| Task dequeued | `task_dequeued` |
|
||||
| Task started | `task_started` |
|
||||
| Task completed | `task_completed` |
|
||||
| Task error | `task_error` |
|
||||
|
||||
### 5. Config
|
||||
- Remove `QUEUE_DAEMON_BATCH_SIZE` (no longer needed - batch size is now dynamic)
|
||||
|
||||
## Notification Flow
|
||||
|
||||
| Event | Location | Type |
|
||||
|-------|----------|------|
|
||||
| Task enqueued | `enqueue_task()` | `task_queued` |
|
||||
| Task dequeued | `process_queue()` after state change to `notified` | `task_dequeued` |
|
||||
| Task started | `cmd_start()` after session file created | `task_started` |
|
||||
| Task completed | `update_queue_item_state()` | `task_completed` |
|
||||
| Task error | `update_queue_item_state()` | `task_error` |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Re-check loop in cmd_start (checking if session DB is reliable) - deferred to separate research issue
|
||||
- Buffer mechanism for excess forking (safety failsafe only)
|
||||
|
||||
## Status
|
||||
|
||||
- [x] Issue created
|
||||
- [x] Implementation
|
||||
- [x] PR created (#147)
|
||||
- [ ] Merged
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create a branch for your work: `git checkout -b fix/issue-N-name` or `git checkout -b feat/issue-N-feature-name`
|
||||
1. Create a branch for your work: `git checkout -b fix/issue-N-name` or `git checkout -b docs/topic-name`
|
||||
2. Make changes and commit with clear messages
|
||||
3. Open a Pull Request for review
|
||||
4. Do not merge directly to `main` or `develop` for reviewable changes
|
||||
4. Do not merge directly to `master` for reviewable changes
|
||||
5. After approval, squash and merge
|
||||
|
||||
## Guidelines
|
||||
@@ -14,53 +14,10 @@
|
||||
- Keep PRs focused and reasonably sized
|
||||
- Document any non-obvious decisions
|
||||
- Test changes before submitting
|
||||
- See [VERSIONING.md](VERSIONING.md) for backport compatibility rules
|
||||
|
||||
## Branches
|
||||
|
||||
### Primary Branches
|
||||
|
||||
- `main` — stable 0.1.x releases, production-ready code
|
||||
- `develop` — experimental 0.2.x work, next major version
|
||||
|
||||
### Feature Branches
|
||||
|
||||
- `master` — stable, reviewed content only
|
||||
- `fix/*` — bug fixes
|
||||
- `feat/*` — new features
|
||||
- `docs/*` — documentation updates
|
||||
- `refactor/*` — code refactoring (no behavior change)
|
||||
|
||||
## Branch Model
|
||||
|
||||
```
|
||||
main (0.1.x stable)
|
||||
└── v0.1.0, v0.1.1, v0.1.2, ...
|
||||
|
||||
develop (0.2.x experimental)
|
||||
└── (next major version work)
|
||||
```
|
||||
|
||||
### Which Branch to Target?
|
||||
|
||||
| Change Type | Target Branch | Backport? |
|
||||
|-------------|---------------|-----------|
|
||||
| Bug fix | `main` | N/A |
|
||||
| Documentation | `main` | N/A |
|
||||
| New feature (backport-compatible) | `main` | Can cherry-pick to `develop` |
|
||||
| Experimental feature | `develop` | No |
|
||||
| Breaking change | `develop` | No |
|
||||
|
||||
## Backport Compatibility
|
||||
|
||||
Before merging, consider if your change is backport-compatible:
|
||||
|
||||
- **YES**: Bug fixes, docs, adding new optional inputs
|
||||
- **NO**: Changing behavior, changing defaults, removing features
|
||||
|
||||
See [VERSIONING.md](VERSIONING.md) for full policy.
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Bug fixes and docs → directly to `main`
|
||||
2. New features → `develop` or feature branches → `develop`
|
||||
3. When `develop` is stable enough → merge to `main` for release
|
||||
- `research/*` — new research notes
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# Versioning Policy
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
Kugetsu uses a dual-branch model:
|
||||
|
||||
| Branch | Purpose | Version | Stability |
|
||||
|--------|---------|---------|-----------|
|
||||
| `main` | Stable releases | 0.1.x | Production-ready |
|
||||
| `develop` | Experimental work | 0.2.x | Active development |
|
||||
|
||||
### Branch Definitions
|
||||
|
||||
- **`main`**: Contains the latest stable 0.1.x releases. All changes here should be production-ready and backport-compatible when possible.
|
||||
|
||||
- **`develop`**: Contains work for the next major version (0.2.x). This branch may contain experimental features that could change or be removed.
|
||||
|
||||
## Version Format
|
||||
|
||||
Versions follow [Semantic Versioning](https://semver.org/):
|
||||
```
|
||||
MAJOR.MINOR.PATCH
|
||||
```
|
||||
|
||||
- **MAJOR**: Incompatible API/behavior changes
|
||||
- **MINOR**: New functionality (backward-compatible)
|
||||
- **PATCH**: Bug fixes (backward-compatible)
|
||||
|
||||
## Backport Compatibility
|
||||
|
||||
### Backport-Compatible Changes (0.1.x)
|
||||
- Bug fixes
|
||||
- Documentation updates
|
||||
- Performance improvements
|
||||
- Adding new inputs/options (must have sensible defaults)
|
||||
- Changes that only affect 0.2.x-specific features
|
||||
|
||||
### NOT Backport-Compatible
|
||||
- Removing or renaming existing options
|
||||
- Changing default values of existing options
|
||||
- Changing behavior of existing commands
|
||||
- Introducing breaking changes to the API/shell interface
|
||||
|
||||
## Deprecation Policy
|
||||
|
||||
When introducing breaking changes:
|
||||
|
||||
1. **Deprecate in minor X**: Add warning messages, document the change
|
||||
2. **Remove in major X+1**: The breaking change is removed in the next major version
|
||||
|
||||
Example:
|
||||
- Option `--old-flag` deprecated in v0.1.5
|
||||
- Option `--old-flag` removed in v1.0.0 (not v0.2.0)
|
||||
|
||||
## What Constitutes a Version Bump
|
||||
|
||||
| Change Type | Version Bump |
|
||||
|-------------|--------------|
|
||||
| Add new command/option | MINOR |
|
||||
| Bug fix | PATCH |
|
||||
| Change default value | MINOR (may warrant PATCH) |
|
||||
| Add new required input | MAJOR |
|
||||
| Remove deprecated feature | MAJOR |
|
||||
| Change behavior of existing command | MINOR (needs deprecation first) |
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Changes are developed on feature branches
|
||||
2. PRs are opened against `main` for 0.1.x changes, or `develop` for 0.2.x
|
||||
3. After review and approval, changes are squash-merged
|
||||
4. Releases are tagged from `main` after significant changes
|
||||
@@ -1,111 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to kugetsu are documented here.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.2.1] - 2026-04-03
|
||||
|
||||
### Fixed
|
||||
- Prevent excess agent spawning with flock + sequential processing
|
||||
|
||||
## [v0.2.0] - 2026-03-30
|
||||
|
||||
### Added
|
||||
- Queue system with background daemon
|
||||
- Agent timeout handling
|
||||
- Context dump/load for session isolation
|
||||
- PR tracking and safe destroy
|
||||
|
||||
## [v0.1.13] - 2026-03-29
|
||||
|
||||
### Fixed
|
||||
- Add missing closing parenthesis in process_queue Python extraction
|
||||
|
||||
## [v0.1.12] - 2026-03-25
|
||||
|
||||
### Added
|
||||
- Post-comment helper for PM agent
|
||||
|
||||
## [v0.1.11] - 2026-03-20
|
||||
|
||||
### Fixed
|
||||
- Wrap cmd_continue in subshell with cd for correct worktree dir
|
||||
|
||||
## [v0.1.10] - 2026-03-15
|
||||
|
||||
### Fixed
|
||||
- destroy --base now also deletes PM agent session
|
||||
|
||||
## [v0.1.9] - 2026-03-10
|
||||
|
||||
### Added
|
||||
- init creates base session in ~/.kugetsu-worktrees
|
||||
- Adds context to forked sessions
|
||||
- Clears logs on init
|
||||
|
||||
## [v0.1.8] - 2026-03-05
|
||||
|
||||
### Fixed
|
||||
- destroy --base and --pm-agent actually delete opencode sessions
|
||||
|
||||
## [v0.1.7] - 2026-02-28
|
||||
|
||||
### Fixed
|
||||
- Warn if init run from non-empty directory
|
||||
|
||||
## [v0.1.6] - 2026-02-20
|
||||
|
||||
### Fixed
|
||||
- Detect session via DB query instead of opencode session list
|
||||
|
||||
## [v0.1.5] - 2026-02-15
|
||||
|
||||
### Fixed
|
||||
- Update forked session permissions after detection
|
||||
|
||||
## [v0.1.4] - 2026-02-10
|
||||
|
||||
### Fixed
|
||||
- Call fix_session_permissions before forking
|
||||
|
||||
## [v0.1.3] - 2026-02-05
|
||||
|
||||
### Fixed
|
||||
- Session detection ordering bug and debugging
|
||||
|
||||
## [v0.1.2] - 2026-01-28
|
||||
|
||||
### Fixed
|
||||
- Improve session detection in cmd_start with retry logic and logging
|
||||
|
||||
## [v0.1.1] - 2026-01-20
|
||||
|
||||
### Fixed
|
||||
- Use cd + worktree inside parent dir instead of --dir flag
|
||||
|
||||
## [v0.1.0] - 2026-01-15
|
||||
|
||||
### Added
|
||||
- KUGETSU_VERBOSITY for PM agent output control
|
||||
- Initial documented release
|
||||
|
||||
[Unreleased]: https://git.fbrns.co/shoko/kugetsu/compare/v0.2.1...HEAD
|
||||
[v0.2.1]: https://git.fbrns.co/shoko/kugetsu/compare/v0.2.0...v0.2.1
|
||||
[v0.2.0]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.13...v0.2.0
|
||||
[v0.1.13]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.12...v0.1.13
|
||||
[v0.1.12]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.11...v0.1.12
|
||||
[v0.1.11]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.10...v0.1.11
|
||||
[v0.1.10]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.9...v0.1.10
|
||||
[v0.1.9]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.8...v0.1.9
|
||||
[v0.1.8]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.7...v0.1.8
|
||||
[v0.1.7]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.6...v0.1.7
|
||||
[v0.1.6]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.5...v0.1.6
|
||||
[v0.1.5]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.4...v0.1.5
|
||||
[v0.1.4]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.3...v0.1.4
|
||||
[v0.1.3]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.2...v0.1.3
|
||||
[v0.1.2]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.1...v0.1.2
|
||||
[v0.1.1]: https://git.fbrns.co/shoko/kugetsu/compare/v0.1.0...v0.1.1
|
||||
[v0.1.0]: https://git.fbrns.co/shoko/kugetsu/releases/tag/v0.1.0
|
||||
@@ -326,7 +326,7 @@ When a Coding Agent starts, it:
|
||||
| Phase 1 | ✅ Complete | SSH + Tailscale remote access |
|
||||
| Phase 1b | ✅ Complete | Tailscale VPN setup |
|
||||
| Phase 2 | 📋 Planned | API Interface |
|
||||
| Phase 3 | ✅ Implemented | Chat Integration (Telegram) |
|
||||
| Phase 3 | 🔄 In Progress | Chat Integration (Telegram) |
|
||||
| Phase 4 | 📋 Planned | Web Dashboard |
|
||||
|
||||
### 6.2 Current Implementation
|
||||
|
||||
@@ -48,9 +48,6 @@ A default config file is created during `kugetsu init` with commented examples:
|
||||
|----------|---------|-------------|
|
||||
| `MAX_CONCURRENT_AGENTS` | 3 | Maximum number of concurrent dev agents |
|
||||
| `KUGETSU_TEMP_DIR` | `~/.local/share/opencode/tool-output` | Temp directory for subagent tool output (useful in headless environments where /tmp is restricted) |
|
||||
| `KUGETSU_VERBOSITY` | `default` | PM agent verbosity level: `verbose`, `default`, or `quiet` |
|
||||
| `QUEUE_DAEMON_INTERVAL_MINUTES` | 5 | How often daemon polls queue (in minutes) |
|
||||
| `QUEUE_CLEANUP_AGE_DAYS` | 7 | Auto-cleanup completed/error items older than N days |
|
||||
|
||||
### Environment Variables for Agents
|
||||
|
||||
@@ -113,10 +110,6 @@ Each issue session gets its own git worktree to prevent conflicts:
|
||||
├── worktrees/
|
||||
│ ├── github.com-shoko-kugetsu-14/ # Isolated workdir for issue #14
|
||||
│ └── github.com-shoko-kugetsu-15/ # Isolated workdir for issue #15
|
||||
├── queue/
|
||||
│ ├── items/ # Queue item JSON files
|
||||
│ ├── daemon.pid # Daemon process ID
|
||||
│ └── daemon.log # Daemon log output
|
||||
└── index.json # Maps session IDs and issue refs to session files
|
||||
```
|
||||
|
||||
@@ -262,152 +255,23 @@ kugetsu destroy --base -y
|
||||
|
||||
**Note**: Destroying base also destroys PM agent since PM depends on base.
|
||||
|
||||
### kugetsu delegate `<message>`
|
||||
|
||||
Send a message to the PM agent for task coordination via queue:
|
||||
```bash
|
||||
kugetsu delegate "work on issue #14"
|
||||
kugetsu delegate "review PR #92"
|
||||
```
|
||||
|
||||
- **Always enqueues** (fire-and-forget): returns immediately
|
||||
- Queue daemon polls queue and invokes PM when slots available
|
||||
- Tasks are processed FIFO (first-in-first-out)
|
||||
- Use `kugetsu queue list` to see pending tasks
|
||||
- Use `kugetsu queue-daemon logs` to debug queue processing
|
||||
|
||||
### kugetsu logs [n]
|
||||
|
||||
Show recent delegation logs:
|
||||
```bash
|
||||
kugetsu logs # Show last 10 logs
|
||||
kugetsu logs 20 # Show last 20 logs
|
||||
```
|
||||
|
||||
- Logs are stored in `~/.kugetsu/logs/`
|
||||
- Automatically deletes logs older than 7 days
|
||||
|
||||
### kugetsu status
|
||||
|
||||
Check if kugetsu is properly initialized:
|
||||
```bash
|
||||
kugetsu status
|
||||
```
|
||||
|
||||
Output:
|
||||
- `kugetsu_not_initialized` - No index file
|
||||
- `base_session_missing` - Base session not found
|
||||
- `pm_agent_missing` - PM agent not found
|
||||
- `ok` - Everything is initialized
|
||||
|
||||
### kugetsu doctor [--fix]
|
||||
|
||||
Diagnose and fix kugetsu issues:
|
||||
```bash
|
||||
kugetsu doctor # Show diagnostic info
|
||||
kugetsu doctor --fix # Attempt automatic repairs
|
||||
```
|
||||
|
||||
- Checks index file existence
|
||||
- Validates base and PM agent sessions
|
||||
- With `--fix`: recreates PM agent if missing
|
||||
- With `--fix-permissions`: fixes session permissions in opencode database
|
||||
|
||||
### kugetsu notify [list|clear]
|
||||
|
||||
Show or clear notifications from PM agent:
|
||||
```bash
|
||||
kugetsu notify list # Show unread notifications (default)
|
||||
kugetsu notify clear # Mark all as read
|
||||
```
|
||||
|
||||
- PM agent writes task completion notifications to `~/.kugetsu/notifications.json`
|
||||
- Shows timestamp, type, message, and issue ref for each notification
|
||||
|
||||
### kugetsu server <list|add|remove|default|get>
|
||||
|
||||
Manage git server configurations:
|
||||
```bash
|
||||
kugetsu server list # List all configured servers
|
||||
kugetsu server add github https://github.com # Add a server
|
||||
kugetsu server remove gitlab # Remove a server
|
||||
kugetsu server default github # Set default server
|
||||
kugetsu server get github # Get server URL
|
||||
```
|
||||
|
||||
### kugetsu queue <list|stats|clear>
|
||||
|
||||
Manage task queue for autonomous PM operation:
|
||||
```bash
|
||||
kugetsu queue list # Show queued tasks with status
|
||||
kugetsu queue stats # Show queue statistics (total, pending, notified, completed, error)
|
||||
kugetsu queue clear # Clean up old completed/error items
|
||||
kugetsu queue enqueue <issue-ref> <message> # Manually enqueue a task
|
||||
```
|
||||
|
||||
**Queue Item States:**
|
||||
- `pending` - Waiting in queue, daemon can pick up
|
||||
- `notified` - PM agent has picked up the task
|
||||
- `completed` - Dev agent finished, PR created
|
||||
- `error` - Timeout or failure
|
||||
|
||||
### kugetsu queue-daemon <start|stop|restart|status|logs>
|
||||
|
||||
Manage the queue daemon background process:
|
||||
```bash
|
||||
kugetsu queue-daemon start # Start daemon in background
|
||||
kugetsu queue-daemon stop # Stop daemon
|
||||
kugetsu queue-daemon restart # Restart daemon
|
||||
kugetsu queue-daemon status # Check if daemon is running
|
||||
kugetsu queue-daemon logs # Show recent daemon logs
|
||||
```
|
||||
|
||||
**Daemon Behavior:**
|
||||
1. Runs at configurable interval (default: 5 minutes)
|
||||
2. Checks if active agents < MAX_CONCURRENT_AGENTS
|
||||
3. Picks 1-N pending items (configurable batch size)
|
||||
4. Forks PM session for each picked item
|
||||
5. PM decides whether to use `start` or `continue`
|
||||
|
||||
**Queue Directory:**
|
||||
```
|
||||
~/.kugetsu/queue/
|
||||
├── items/ # Queue item JSON files
|
||||
│ ├── q_1234567890.json # One file per queued task
|
||||
│ └── q_1234567891.json
|
||||
├── daemon.pid # Daemon process ID
|
||||
├── daemon.lock # Daemon lock file
|
||||
└── daemon.log # Daemon log output
|
||||
```
|
||||
|
||||
## Workflow Example
|
||||
|
||||
### First-time Setup
|
||||
```bash
|
||||
# Initialize kugetsu (requires TTY)
|
||||
# First-time setup (requires TTY)
|
||||
kugetsu init
|
||||
# Creates: base session + pm-agent session
|
||||
|
||||
# Start the queue daemon (for autonomous operation)
|
||||
kugetsu queue-daemon start
|
||||
```
|
||||
# Start work on issue
|
||||
kugetsu start github.com/shoko/kugetsu#14 "implement feature X"
|
||||
# Creates: worktree at ~/.kugetsu/worktrees/github.com-shoko-kugetsu-14/
|
||||
|
||||
### Normal Workflow
|
||||
```bash
|
||||
# Enqueue tasks via delegate - agents will process them automatically
|
||||
kugetsu delegate "work on issue #14"
|
||||
kugetsu delegate "review PR #92"
|
||||
|
||||
# Check queue status
|
||||
kugetsu queue list # See pending tasks
|
||||
kugetsu queue stats # See statistics
|
||||
|
||||
# Debug queue daemon
|
||||
kugetsu queue-daemon status # Is daemon running?
|
||||
kugetsu queue-daemon logs # See daemon logs
|
||||
|
||||
# Continue work on existing issue
|
||||
# Continue later
|
||||
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
|
||||
|
||||
# Continue again
|
||||
kugetsu continue github.com/shoko/kugetsu#14 "fix failing test"
|
||||
|
||||
# List all sessions
|
||||
kugetsu list
|
||||
|
||||
@@ -418,21 +282,6 @@ kugetsu prune --force
|
||||
kugetsu destroy github.com/shoko/kugetsu#14
|
||||
```
|
||||
|
||||
### Queue Daemon Management
|
||||
```bash
|
||||
# Check if daemon is running
|
||||
kugetsu queue-daemon status
|
||||
|
||||
# View daemon logs for debugging
|
||||
kugetsu queue-daemon logs
|
||||
|
||||
# Restart daemon if needed
|
||||
kugetsu queue-daemon restart
|
||||
|
||||
# Stop daemon
|
||||
kugetsu queue-daemon stop
|
||||
```
|
||||
|
||||
## Headless Operation
|
||||
|
||||
This design solves the headless CLI limitation discovered in Issue #14:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
277
skills/kugetsu/tests/test-kugetsu.sh
Executable file
277
skills/kugetsu/tests/test-kugetsu.sh
Executable file
@@ -0,0 +1,277 @@
|
||||
#!/bin/bash
|
||||
# kugetsu test suite
|
||||
# Run with: bash skills/kugetsu/tests/test-kugetsu.sh
|
||||
#
|
||||
# Memory management approach:
|
||||
# - Sequential test execution (no parallel)
|
||||
# - Cleanup between tests that spawn opencode
|
||||
# - No hard memory cap (ulimit -v breaks Bun/opencode)
|
||||
# - If OOM occurs, it is a known failure mode
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
KUGETSU="./skills/kugetsu/scripts/kugetsu"
|
||||
TEST_SESSION_PREFIX="kugetsu-test-"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
cleanup_sessions() {
|
||||
for dir in ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}*; do
|
||||
[ -d "$dir" ] && rm -rf "$dir" 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_opencode() {
|
||||
pkill -f "opencode.*${TEST_SESSION_PREFIX}" 2>/dev/null || true
|
||||
pkill -f "kugetsu.*${TEST_SESSION_PREFIX}" 2>/dev/null || true
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
cleanup_sessions
|
||||
cleanup_opencode
|
||||
}
|
||||
|
||||
pass() {
|
||||
echo "✅ PASS: $1"
|
||||
PASS=$((PASS + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "❌ FAIL: $1"
|
||||
FAIL=$((FAIL + 1))
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
echo "=== kugetsu Test Suite ==="
|
||||
echo ""
|
||||
|
||||
# Test 1: Help
|
||||
echo "--- Test: help ---"
|
||||
if $KUGETSU help 2>&1 | grep -q "kugetsu - OpenCode Session Manager"; then
|
||||
pass "help displays usage"
|
||||
else
|
||||
fail "help displays usage"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: List empty
|
||||
echo "--- Test: list (empty) ---"
|
||||
if $KUGETSU list 2>&1 | grep -q "SESSION_ID"; then
|
||||
pass "list shows header even when empty"
|
||||
else
|
||||
fail "list shows header even when empty"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 3: List --all empty
|
||||
echo "--- Test: list --all (empty) ---"
|
||||
if $KUGETSU list --all 2>&1 | grep -q "SESSION_ID"; then
|
||||
pass "list --all shows header even when empty"
|
||||
else
|
||||
fail "list --all shows header even when empty"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 4: Start session (quick exit)
|
||||
echo "--- Test: start session ---"
|
||||
if timeout 15 bash -c "$KUGETSU start ${TEST_SESSION_PREFIX}start-test 'echo hello'" 2>&1; then
|
||||
if [ -d ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}start-test ]; then
|
||||
pass "start creates session directory"
|
||||
else
|
||||
fail "start creates session directory"
|
||||
fi
|
||||
else
|
||||
fail "start runs successfully"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 5: List shows only left by default
|
||||
echo "--- Test: list default filters non-left ---"
|
||||
if ! $KUGETSU list 2>&1 | grep -q "${TEST_SESSION_PREFIX}start-test"; then
|
||||
pass "list default hides idle sessions"
|
||||
else
|
||||
fail "list default hides idle sessions"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 6: List --all shows all
|
||||
echo "--- Test: list --all shows all states ---"
|
||||
if $KUGETSU list --all 2>&1 | grep -q "${TEST_SESSION_PREFIX}start-test"; then
|
||||
pass "list --all shows all sessions"
|
||||
else
|
||||
fail "list --all shows all sessions"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 7: Resume with auto-fill
|
||||
echo "--- Test: resume auto-fill ---"
|
||||
mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-test
|
||||
echo "left" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-test/state
|
||||
echo "continue this task" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-test/message
|
||||
|
||||
OUTPUT=$(timeout 10 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}resume-test" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "Auto-filled message: continue this task"; then
|
||||
pass "resume auto-fills stored message"
|
||||
else
|
||||
fail "resume auto-fills stored message"
|
||||
fi
|
||||
cleanup
|
||||
echo ""
|
||||
|
||||
# Test 8: Resume with provided message overrides
|
||||
echo "--- Test: resume with message overrides ---"
|
||||
mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-override
|
||||
echo "left" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-override/state
|
||||
echo "original message" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}resume-override/message
|
||||
|
||||
OUTPUT=$(timeout 30 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}resume-override 'new message'" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "new message" && ! echo "$OUTPUT" | grep -q "Auto-filled message"; then
|
||||
pass "resume uses provided message over auto-fill"
|
||||
else
|
||||
fail "resume uses provided message over auto-fill: $OUTPUT"
|
||||
fi
|
||||
cleanup
|
||||
echo ""
|
||||
|
||||
# Test 9: Resume idle session fails
|
||||
echo "--- Test: resume idle session fails ---"
|
||||
rm -rf ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}idle-test 2>/dev/null
|
||||
mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}idle-test
|
||||
echo "idle" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}idle-test/state
|
||||
|
||||
OUTPUT=$(timeout 5 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}idle-test" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "cannot be resumed"; then
|
||||
pass "resume idle session fails with message"
|
||||
else
|
||||
echo "DEBUG: $OUTPUT"
|
||||
fail "resume idle session fails with message"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 10: Resume non-existent session fails
|
||||
echo "--- Test: resume non-existent session fails ---"
|
||||
rm -rf ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}nonexistent 2>/dev/null
|
||||
OUTPUT=$(timeout 5 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}nonexistent" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "not found"; then
|
||||
pass "resume non-existent session fails"
|
||||
else
|
||||
echo "DEBUG: $OUTPUT"
|
||||
fail "resume non-existent session fails"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 11: Stop non-used session fails
|
||||
echo "--- Test: stop non-used session fails ---"
|
||||
rm -rf ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}notused 2>/dev/null
|
||||
mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}notused
|
||||
echo "idle" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}notused/state
|
||||
|
||||
OUTPUT=$(timeout 5 bash -c "$KUGETSU stop ${TEST_SESSION_PREFIX}notused" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "not in use"; then
|
||||
pass "stop non-used session fails"
|
||||
else
|
||||
echo "DEBUG: $OUTPUT"
|
||||
fail "stop non-used session fails"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 12: Start existing left session resumes instead
|
||||
echo "--- Test: start on left session resumes ---"
|
||||
mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}left-start
|
||||
echo "left" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}left-start/state
|
||||
echo "original task" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}left-start/message
|
||||
|
||||
OUTPUT=$(timeout 10 bash -c "$KUGETSU start ${TEST_SESSION_PREFIX}left-start 'new task'" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "Resuming instead"; then
|
||||
pass "start on left session resumes"
|
||||
else
|
||||
fail "start on left session resumes"
|
||||
fi
|
||||
cleanup
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# FLAKY TESTS - Commented out due to timing/process behavior issues
|
||||
# ============================================================================
|
||||
|
||||
# Test: Stop active session (FLAKY - timing dependent)
|
||||
# echo "--- Test: stop active session (FLAKY) ---"
|
||||
# (
|
||||
# timeout 20 bash -c "$KUGETSU start ${TEST_SESSION_PREFIX}stop-test 'sleep 30'" 2>&1 &
|
||||
# KUGETSU_PID=$!
|
||||
# sleep 3
|
||||
#
|
||||
# # Check session is in use
|
||||
# if ! $KUGETSU list --all 2>&1 | grep -q "${TEST_SESSION_PREFIX}stop-test.*used"; then
|
||||
# echo "⚠️ SKIP (FLAKY): Could not verify session was used"
|
||||
# elif timeout 5 bash -c "$KUGETSU stop ${TEST_SESSION_PREFIX}stop-test" 2>&1; then
|
||||
# if [ "$(cat ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}stop-test/state 2>/dev/null)" = "idle" ]; then
|
||||
# echo "✅ PASS (FLAKY): stop transitions to idle"
|
||||
# else
|
||||
# echo "❌ FAIL (FLAKY): stop does not transition to idle"
|
||||
# fi
|
||||
# else
|
||||
# echo "❌ FAIL (FLAKY): stop command failed"
|
||||
# fi
|
||||
#
|
||||
# wait $KUGETSU_PID 2>/dev/null || true
|
||||
# ) 2>&1 || true
|
||||
|
||||
# Test: Interrupt session leaves state as left (FLAKY - opencode signal handling)
|
||||
# echo "--- Test: interrupt session leaves left (FLAKY) ---"
|
||||
# (
|
||||
# bash -c "$KUGETSU start ${TEST_SESSION_PREFIX}interrupt-test 'sleep 30'" 2>&1 &
|
||||
# KUGETSU_PID=$!
|
||||
# sleep 3
|
||||
#
|
||||
# # Find and kill opencode process
|
||||
# OPENCODE_PID=$(pgrep -f "opencode.*${TEST_SESSION_PREFIX}interrupt-test" | head -1 || true)
|
||||
# if [ -n "$OPENCODE_PID" ]; then
|
||||
# kill -9 $OPENCODE_PID 2>/dev/null || true
|
||||
# fi
|
||||
#
|
||||
# wait $KUGETSU_PID 2>/dev/null || true
|
||||
# sleep 1
|
||||
#
|
||||
# STATE=$(cat ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}interrupt-test/state 2>/dev/null || echo "unknown")
|
||||
# if [ "$STATE" = "left" ]; then
|
||||
# echo "✅ PASS (FLAKY): interrupt leaves state as left"
|
||||
# else
|
||||
# echo "❌ FAIL (FLAKY): interrupt left state=$STATE (expected left)"
|
||||
# fi
|
||||
# ) 2>&1 || true
|
||||
|
||||
# Test: Concurrent resume attempts (FLAKY - race condition)
|
||||
# echo "--- Test: concurrent resume (FLAKY) ---"
|
||||
# mkdir -p ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}concurrent
|
||||
# echo "left" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}concurrent/state
|
||||
# echo "test task" > ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}concurrent/message
|
||||
#
|
||||
# (
|
||||
# timeout 10 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}concurrent" 2>&1 &
|
||||
# timeout 10 bash -c "$KUGETSU resume ${TEST_SESSION_PREFIX}concurrent" 2>&1
|
||||
# ) 2>&1 || true
|
||||
#
|
||||
# echo "⚠️ NOTE (FLAKY): This test is informational only - no assertion"
|
||||
# rm -rf ~/.kugetsu/sessions/${TEST_SESSION_PREFIX}concurrent
|
||||
|
||||
# ============================================================================
|
||||
# Cleanup
|
||||
# ============================================================================
|
||||
cleanup
|
||||
|
||||
echo ""
|
||||
echo "=== Test Summary ==="
|
||||
echo "Passed: $PASS"
|
||||
echo "Failed: $FAIL"
|
||||
echo ""
|
||||
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "All tests passed!"
|
||||
exit 0
|
||||
else
|
||||
echo "Some tests failed."
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user