Merge origin/main into fix/issue-116-modularize-script (fix leftover conflict markers)
This commit is contained in:
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Workflow
|
## 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
|
2. Make changes and commit with clear messages
|
||||||
3. Open a Pull Request for review
|
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
|
5. After approval, squash and merge
|
||||||
|
|
||||||
## Guidelines
|
## Guidelines
|
||||||
@@ -14,10 +14,53 @@
|
|||||||
- Keep PRs focused and reasonably sized
|
- Keep PRs focused and reasonably sized
|
||||||
- Document any non-obvious decisions
|
- Document any non-obvious decisions
|
||||||
- Test changes before submitting
|
- Test changes before submitting
|
||||||
|
- See [VERSIONING.md](VERSIONING.md) for backport compatibility rules
|
||||||
|
|
||||||
## Branches
|
## 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
|
- `fix/*` — bug fixes
|
||||||
|
- `feat/*` — new features
|
||||||
- `docs/*` — documentation updates
|
- `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
|
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() {
|
cmd_queue() {
|
||||||
local action="${1:-list}"
|
local action="${1:-list}"
|
||||||
shift
|
shift
|
||||||
|
|||||||
Reference in New Issue
Block a user