refactor: modularize kugetsu shell script #151
@@ -2,10 +2,10 @@
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create a branch for your work: `git checkout -b fix/issue-N-name` or `git checkout -b docs/topic-name`
|
||||
1. Create a branch for your work: `git checkout -b fix/issue-N-name` or `git checkout -b feat/issue-N-feature-name`
|
||||
2. Make changes and commit with clear messages
|
||||
3. Open a Pull Request for review
|
||||
4. Do not merge directly to `master` for reviewable changes
|
||||
4. Do not merge directly to `main` or `develop` for reviewable changes
|
||||
5. After approval, squash and merge
|
||||
|
||||
## Guidelines
|
||||
@@ -14,10 +14,53 @@
|
||||
- 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
|
||||
|
||||
- `master` — stable, reviewed content only
|
||||
### Primary Branches
|
||||
|
||||
- `main` — stable 0.1.x releases, production-ready code
|
||||
- `develop` — experimental 0.2.x work, next major version
|
||||
|
||||
### Feature Branches
|
||||
|
||||
- `fix/*` — bug fixes
|
||||
- `feat/*` — new features
|
||||
- `docs/*` — documentation updates
|
||||
- `research/*` — new research notes
|
||||
- `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
|
||||
|
||||
71
VERSIONING.md
Normal file
71
VERSIONING.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# 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
|
||||
111
docs/CHANGELOG.md
Normal file
111
docs/CHANGELOG.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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
|
||||
@@ -447,6 +447,533 @@ check_task_timeouts() {
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_old_queue_items() {
|
||||
local days="${QUEUE_CLEANUP_AGE_DAYS:-7}"
|
||||
|
||||
if [ ! -d "$QUEUE_ITEMS_DIR" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
find "$QUEUE_ITEMS_DIR" -name "*.json" -type f -mtime "+$days" 2>/dev/null | while read -r file; do
|
||||
local state=$(python3 -c "import json; print(json.load(open('$file')).get('state', ''))" 2>/dev/null || echo "")
|
||||
if [ "$state" = "completed" ] || [ "$state" = "error" ]; then
|
||||
rm -f "$file"
|
||||
echo "Cleaned up: $(basename "$file")"
|
||||
fi
|
||||
done
|
||||
}
|
||||
update_session_pr_url() {
|
||||
local issue_ref="$1"
|
||||
local pr_url="$2"
|
||||
|
||||
if [ -z "$issue_ref" ] || [ -z "$pr_url" ]; then
|
||||
echo "Error: update_session_pr_url requires <issue-ref> and <pr-url>" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local session_file=$(get_session_for_issue "$issue_ref")
|
||||
if [ -z "$session_file" ] || [ "$session_file" = "null" ]; then
|
||||
echo "Error: No session found for '$issue_ref'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local session_path="$SESSIONS_DIR/$session_file"
|
||||
|
||||
if [ ! -f "$session_path" ]; then
|
||||
echo "Error: Session file not found: $session_path" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
|
||||
session_path = "$session_path"
|
||||
pr_url = "$pr_url"
|
||||
|
||||
with open(session_path, 'r') as f:
|
||||
session = json.load(f)
|
||||
|
||||
session['pr_url'] = pr_url
|
||||
|
||||
with open(session_path, 'w') as f:
|
||||
json.dump(session, f, indent=2)
|
||||
|
||||
print(f"Updated pr_url to: {pr_url}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
read_index() {
|
||||
if [ -f "$INDEX_FILE" ]; then
|
||||
cat "$INDEX_FILE"
|
||||
else
|
||||
echo '{"base": null, "pm_agent": null, "issues": {}}'
|
||||
fi
|
||||
}
|
||||
|
||||
write_index() {
|
||||
local base="$1"
|
||||
local pm_agent="$2"
|
||||
local issues_json="$3"
|
||||
local temp_file="$INDEX_FILE.tmp.$$"
|
||||
printf '{"base": %s, "pm_agent": %s, "issues": %s}\n' "$base" "$pm_agent" "$issues_json" > "$temp_file"
|
||||
mv "$temp_file" "$INDEX_FILE"
|
||||
}
|
||||
|
||||
get_base_session_id() {
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or '')"
|
||||
}
|
||||
|
||||
get_pm_agent_session_id() {
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('pm_agent') or '')"
|
||||
}
|
||||
|
||||
get_session_for_issue() {
|
||||
local issue_ref="$1"
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['issues'].get('$issue_ref') or '')"
|
||||
}
|
||||
|
||||
set_base_in_index() {
|
||||
local base_session_id="$1"
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local issues_json=$(read_index | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base_session_id\"" "null" "$issues_json"
|
||||
else
|
||||
write_index "\"$base_session_id\"" "\"$pm_agent\"" "$issues_json"
|
||||
fi
|
||||
}
|
||||
|
||||
set_pm_agent_in_index() {
|
||||
local pm_agent_session_id="$1"
|
||||
local base=$(get_base_session_id)
|
||||
local issues_json=$(read_index | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
write_index "null" "\"$pm_agent_session_id\"" "$issues_json"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent_session_id\"" "$issues_json"
|
||||
fi
|
||||
}
|
||||
|
||||
add_issue_to_index() {
|
||||
local issue_ref="$1"
|
||||
local session_file="$2"
|
||||
local index=$(read_index)
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
local new_issues=$(echo "$issues" | python3 -c "import sys, json; d=json.load(sys.stdin); d['$issue_ref']='$session_file'; print(json.dumps(d))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "null" "null" "$new_issues"
|
||||
else
|
||||
write_index "null" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
else
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base\"" "null" "$new_issues"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
remove_issue_from_index() {
|
||||
local issue_ref="$1"
|
||||
local index=$(read_index)
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local new_issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); d['issues'].pop('$issue_ref', None); print(json.dumps(d['issues']))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "null" "null" "$new_issues"
|
||||
else
|
||||
write_index "null" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
else
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base\"" "null" "$new_issues"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
validate_issue_ref() {
|
||||
local issue_ref="$1"
|
||||
if [[ ! "$issue_ref" =~ ^[^/]+/[^/]+/[^#]+#[0-9]+$ ]]; then
|
||||
echo "Error: invalid issue ref format" >&2
|
||||
echo "Expected: instance/user/repo#number" >&2
|
||||
echo "Example: github.com/shoko/kugetsu#14" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_opencode_session_exists() {
|
||||
local session_id="$1"
|
||||
opencode session list --format json 2>/dev/null | grep -q "\"$session_id\""
|
||||
}
|
||||
|
||||
kugetsu_get_pm_context() {
|
||||
local user_pm_context="${KUGETSU_DIR}/pm-agent.md"
|
||||
local skill_pm_context="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../pm/SKILL.md"
|
||||
|
||||
if [ -f "$user_pm_context" ]; then
|
||||
cat "$user_pm_context"
|
||||
elif [ -f "$skill_pm_context" ]; then
|
||||
cat "$skill_pm_context"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
kugetsu_get_fork_context() {
|
||||
local issue_ref="$1"
|
||||
local context=""
|
||||
|
||||
context="## IMPORTANT WORKING RULES
|
||||
|
||||
1. You are working on issue: $issue_ref
|
||||
2. If you encounter ANY error, blocker, or cannot complete the task:
|
||||
- STOP immediately
|
||||
- Log what happened and why you cannot proceed
|
||||
- Do NOT switch to other work or try alternative approaches
|
||||
3. Do NOT work on other issues or PRs unless explicitly asked
|
||||
4. Environment variables are available in ~/.kugetsu/env/
|
||||
|
||||
"
|
||||
|
||||
if [ -f "$REPOS_CONFIG" ]; then
|
||||
context="${context}
|
||||
## REPOSITORIES CONFIG
|
||||
$(cat "$REPOS_CONFIG")
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
if [ -f "$ENV_DIR/default.env" ]; then
|
||||
context="${context}
|
||||
## ENVIRONMENT (available at ~/.kugetsu/env/)
|
||||
Environment file exists at: $ENV_DIR/default.env
|
||||
Source it with: source ~/.kugetsu/env/default.env
|
||||
"
|
||||
fi
|
||||
|
||||
echo "$context"
|
||||
}
|
||||
|
||||
kugetsu_add_notification() {
|
||||
local type="$1"
|
||||
local message="$2"
|
||||
local issue_ref="${3:-}"
|
||||
local gitea_url="${4:-}"
|
||||
|
||||
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
|
||||
|
||||
local notification=$(python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
notification = {
|
||||
"type": "$type",
|
||||
"message": "$message",
|
||||
"issue_ref": "$issue_ref" if "$issue_ref" else None,
|
||||
"gitea_url": "$gitea_url" if "$gitea_url" else None,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"read": False
|
||||
}
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
notifications = []
|
||||
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
except:
|
||||
notifications = []
|
||||
|
||||
notifications.append(notification)
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(notifications, f, indent=2)
|
||||
|
||||
print("Notification added")
|
||||
PYEOF
|
||||
)
|
||||
echo "$notification"
|
||||
}
|
||||
|
||||
kugetsu_get_notifications() {
|
||||
local limit="${1:-10}"
|
||||
|
||||
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
print("[]")
|
||||
exit(0)
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
|
||||
unread = [n for n in notifications if not n.get("read", False)]
|
||||
unread.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||
|
||||
for n in unread[:$limit]:
|
||||
ts = n.get("timestamp", "unknown")
|
||||
ntype = n.get("type", "info")
|
||||
msg = n.get("message", "")
|
||||
issue = n.get("issue_ref", "")
|
||||
gitea = n.get("gitea_url", "")
|
||||
|
||||
print(f"[{ts}] {ntype}: {msg}")
|
||||
if issue:
|
||||
print(f" Issue: {issue}")
|
||||
if gitea:
|
||||
print(f" Link: {gitea}")
|
||||
print()
|
||||
|
||||
if not unread:
|
||||
print("No unread notifications.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error reading notifications: {e}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
kugetsu_clear_notifications() {
|
||||
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
exit(0)
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
|
||||
for n in notifications:
|
||||
n["read"] = True
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(notifications, f, indent=2)
|
||||
|
||||
print("Notifications marked as read")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
cmd_notify() {
|
||||
local action="${1:-}"
|
||||
|
||||
case "$action" in
|
||||
""|"list"|"show")
|
||||
kugetsu_get_notifications 10
|
||||
;;
|
||||
"clear")
|
||||
kugetsu_clear_notifications
|
||||
;;
|
||||
*)
|
||||
echo "Usage: kugetsu notify [list|clear]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
if [ ! -f "$INDEX_FILE" ]; then
|
||||
echo "kugetsu_not_initialized"
|
||||
return
|
||||
fi
|
||||
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
echo "base_session_missing"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ] || [ "$pm_agent" = "None" ]; then
|
||||
echo "pm_agent_missing"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
get_verbosity_context() {
|
||||
local verbosity="${KUGETSU_VERBOSITY:-default}"
|
||||
local verbosity_file="$VERBOSITY_DIR/${verbosity}.md"
|
||||
|
||||
if [ -f "$verbosity_file" ]; then
|
||||
cat "$verbosity_file"
|
||||
else
|
||||
echo "## Verbosity: $verbosity"
|
||||
fi
|
||||
}
|
||||
|
||||
init_verbosity_templates() {
|
||||
mkdir -p "$VERBOSITY_DIR"
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/verbose.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/verbose.md" << 'EOF'
|
||||
## Verbosity: Verbose
|
||||
|
||||
You are operating in HIGH verbosity mode. Include ALL available context:
|
||||
- Full command outputs and their results
|
||||
- Detailed reasoning and thinking process
|
||||
- All file changes with diffs when relevant
|
||||
- Complete log excerpts
|
||||
- Comprehensive status updates
|
||||
- Ask clarifying questions when uncertain
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/default.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/default.md" << 'EOF'
|
||||
## Verbosity: Default
|
||||
|
||||
You are operating in NORMAL verbosity mode. Provide balanced output:
|
||||
- Standard command outputs and key results
|
||||
- Moderate reasoning detail
|
||||
- Important file changes summarized
|
||||
- Regular status updates
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/quiet.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/quiet.md" << 'EOF'
|
||||
## Verbosity: Quiet
|
||||
|
||||
You are operating in QUIET verbosity mode. Keep output minimal:
|
||||
- Only essential information
|
||||
- Brief status updates (1-2 sentences)
|
||||
- Final decisions only
|
||||
- Yes/No answers when appropriate
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
parse_issue_ref_from_message() {
|
||||
local message="$1"
|
||||
|
||||
local gitserver=""
|
||||
local owner=""
|
||||
local repo=""
|
||||
local issue_number=""
|
||||
|
||||
if echo "$message" | grep -qE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(issues|pull)/[0-9]+'; then
|
||||
gitserver=$(echo "$message" | grep -oE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+' | head -1 | sed 's/\/[^/]*\/[^/]*$//')
|
||||
local full_path=$(echo "$message" | grep -oE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(issues|pull)/[0-9]+' | head -1)
|
||||
owner=$(echo "$full_path" | cut -d'/' -f2)
|
||||
repo=$(echo "$full_path" | cut -d'/' -f3)
|
||||
issue_number=$(echo "$full_path" | grep -oE '[0-9]+$' | head -1)
|
||||
elif echo "$message" | grep -qE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#[0-9]+'; then
|
||||
gitserver=$(echo "$message" | grep -oE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+' | head -1)
|
||||
owner=$(echo "$gitserver" | cut -d'/' -f2)
|
||||
repo=$(echo "$gitserver" | cut -d'/' -f3)
|
||||
issue_number=$(echo "$message" | grep -oE '#[0-9]+' | grep -oE '[0-9]+' | head -1)
|
||||
elif echo "$message" | grep -qE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#([0-9]+)'; then
|
||||
owner=$(echo "$message" | grep -oE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#' | sed 's/#$//' | cut -d'/' -f1)
|
||||
repo=$(echo "$message" | grep -oE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#' | sed 's/#$//' | cut -d'/' -f2)
|
||||
issue_number=$(echo "$message" | grep -oE '#[0-9]+' | grep -oE '[0-9]+' | head -1)
|
||||
fi
|
||||
|
||||
echo "${gitserver}|${owner}|${repo}|${issue_number}"
|
||||
}
|
||||
|
||||
get_missing_info() {
|
||||
local parsed="$1"
|
||||
local gitserver=$(echo "$parsed" | cut -d'|' -f1)
|
||||
local owner=$(echo "$parsed" | cut -d'|' -f2)
|
||||
local repo=$(echo "$parsed" | cut -d'|' -f3)
|
||||
local issue_number=$(echo "$parsed" | cut -d'|' -f4)
|
||||
|
||||
local missing=""
|
||||
[ -z "$gitserver" ] && missing="${missing}git server, "
|
||||
[ -z "$owner" ] && missing="${missing}owner, "
|
||||
[ -z "$repo" ] && missing="${missing}repository, "
|
||||
[ -z "$issue_number" ] && missing="${missing}issue number, "
|
||||
|
||||
echo "$missing" | sed 's/, $//'
|
||||
}
|
||||
|
||||
build_missing_info_context() {
|
||||
local missing="$1"
|
||||
if [ -n "$missing" ]; then
|
||||
echo ""
|
||||
echo "NOTE: This task delegation has no information about: ${missing}."
|
||||
echo "We need them if user wants to work on a specific issue. Otherwise we don't need it."
|
||||
fi
|
||||
}
|
||||
|
||||
find_worktrees_by_issue_number() {
|
||||
local issue_number="$1"
|
||||
local results=""
|
||||
|
||||
if [ ! -d "$WORKTREES_DIR/.kugetsu-worktrees" ]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
for wt in "$WORKTREES_DIR/.kugetsu-worktrees"/*; do
|
||||
if [ -d "$wt" ]; then
|
||||
local wt_issue_number=$(echo "$wt" | grep -oE '#?[0-9]+$' | grep -oE '[0-9]+' | head -1)
|
||||
if [ "$wt_issue_number" = "$issue_number" ]; then
|
||||
results="${results}${wt}:worktree
|
||||
"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$results"
|
||||
}
|
||||
|
||||
find_sessions_by_issue_number() {
|
||||
local issue_number="$1"
|
||||
local results=""
|
||||
|
||||
if [ ! -d "$SESSIONS_DIR" ]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
for session_file in "$SESSIONS_DIR"/*.json; do
|
||||
if [ -f "$session_file" ]; then
|
||||
local session_issue_ref=$(basename "$session_file" .json | sed 's/_/\//g')
|
||||
local session_issue_number=$(echo "$session_issue_ref" | grep -oE '#?[0-9]+$' | grep -oE '[0-9]+' | head -1)
|
||||
if [ "$session_issue_number" = "$issue_number" ]; then
|
||||
results="${results}${session_file}:session
|
||||
"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$results"
|
||||
}
|
||||
|
||||
cmd_queue() {
|
||||
local action="${1:-list}"
|
||||
shift
|
||||
|
||||
Reference in New Issue
Block a user