troven 0.1.0__tar.gz
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.
- troven-0.1.0/.gitignore +8 -0
- troven-0.1.0/LICENSE +21 -0
- troven-0.1.0/PKG-INFO +75 -0
- troven-0.1.0/README.md +65 -0
- troven-0.1.0/pyproject.toml +21 -0
- troven-0.1.0/skills/troven/SKILL.md +79 -0
- troven-0.1.0/src/troven/__init__.py +3 -0
- troven-0.1.0/src/troven/__main__.py +7 -0
- troven-0.1.0/src/troven/cli.py +144 -0
- troven-0.1.0/src/troven/commands/__init__.py +1 -0
- troven-0.1.0/src/troven/commands/accept.py +134 -0
- troven-0.1.0/src/troven/commands/create.py +193 -0
- troven-0.1.0/src/troven/commands/install_skill.py +51 -0
- troven-0.1.0/src/troven/commands/list_cmd.py +79 -0
- troven-0.1.0/src/troven/commands/open_cmd.py +137 -0
- troven-0.1.0/src/troven/commands/reject.py +91 -0
- troven-0.1.0/src/troven/commands/show.py +25 -0
- troven-0.1.0/src/troven/errors.py +68 -0
- troven-0.1.0/src/troven/git.py +271 -0
- troven-0.1.0/src/troven/skill/SKILL.md +79 -0
- troven-0.1.0/src/troven/state.py +257 -0
- troven-0.1.0/tests/test_troven.py +280 -0
troven-0.1.0/.gitignore
ADDED
troven-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rdarder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
troven-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: troven
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Git-native CLI for tracking change intents and speculative development
|
|
5
|
+
Author: rdarder
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Troven
|
|
12
|
+
|
|
13
|
+
> **Troven** is a Git-native CLI for tracking **change intents** and speculative development in software projects.
|
|
14
|
+
|
|
15
|
+
It maintains both **positive knowledge** (accepted changes) and **rejection knowledge** (dead ends, discarded hypotheses, benchmark findings) in structured, conflict-free records.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The Four Verbs
|
|
20
|
+
|
|
21
|
+
| Command | Action |
|
|
22
|
+
| :--- | :--- |
|
|
23
|
+
| `troven create <name> [--from <base>]` | Start a new troven worktree and initialize its record. |
|
|
24
|
+
| `troven open [<id>]` | Open an active or rejected troven in a worktree. |
|
|
25
|
+
| `troven accept [<id>] [--into <branch>]` | Fast-forward merge changes into target branch, recording outcome. |
|
|
26
|
+
| `troven reject [<id>]` | Archive branch to `refs/archive/troven/<id>`, recording rejection verdict. |
|
|
27
|
+
|
|
28
|
+
### Exploration & Visibility
|
|
29
|
+
|
|
30
|
+
| Command | Action |
|
|
31
|
+
| :--- | :--- |
|
|
32
|
+
| `troven list [--status <state>]` | Hierarchical DAG view of all active, accepted, and rejected trovens. |
|
|
33
|
+
| `troven show [<id>]` | Print full markdown record for a troven. |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Editable install via pip or uv:
|
|
41
|
+
pip install -e .
|
|
42
|
+
# or
|
|
43
|
+
uv tool install --editable .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Record Format (`.troven/<id>.md`)
|
|
49
|
+
|
|
50
|
+
Every troven is backed by a conflict-free markdown file:
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
---
|
|
54
|
+
version: 1
|
|
55
|
+
id: 2026-08-30-fast-lexer-simd
|
|
56
|
+
parent: 2026-08-30-fast-lexer
|
|
57
|
+
forked_from: null
|
|
58
|
+
status: active
|
|
59
|
+
base_branch: master
|
|
60
|
+
base_commit: 8f3c1a2d
|
|
61
|
+
target_ref: null
|
|
62
|
+
created_at: "2026-08-30T14:22:00-03:00"
|
|
63
|
+
resolved_at: null
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Summary
|
|
67
|
+
|
|
68
|
+
<!-- REQUIRED before accept or reject. Agent or user fills this in. -->
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Details
|
|
73
|
+
|
|
74
|
+
<!-- Optional append-only log. -->
|
|
75
|
+
```
|
troven-0.1.0/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Troven
|
|
2
|
+
|
|
3
|
+
> **Troven** is a Git-native CLI for tracking **change intents** and speculative development in software projects.
|
|
4
|
+
|
|
5
|
+
It maintains both **positive knowledge** (accepted changes) and **rejection knowledge** (dead ends, discarded hypotheses, benchmark findings) in structured, conflict-free records.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Four Verbs
|
|
10
|
+
|
|
11
|
+
| Command | Action |
|
|
12
|
+
| :--- | :--- |
|
|
13
|
+
| `troven create <name> [--from <base>]` | Start a new troven worktree and initialize its record. |
|
|
14
|
+
| `troven open [<id>]` | Open an active or rejected troven in a worktree. |
|
|
15
|
+
| `troven accept [<id>] [--into <branch>]` | Fast-forward merge changes into target branch, recording outcome. |
|
|
16
|
+
| `troven reject [<id>]` | Archive branch to `refs/archive/troven/<id>`, recording rejection verdict. |
|
|
17
|
+
|
|
18
|
+
### Exploration & Visibility
|
|
19
|
+
|
|
20
|
+
| Command | Action |
|
|
21
|
+
| :--- | :--- |
|
|
22
|
+
| `troven list [--status <state>]` | Hierarchical DAG view of all active, accepted, and rejected trovens. |
|
|
23
|
+
| `troven show [<id>]` | Print full markdown record for a troven. |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Editable install via pip or uv:
|
|
31
|
+
pip install -e .
|
|
32
|
+
# or
|
|
33
|
+
uv tool install --editable .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Record Format (`.troven/<id>.md`)
|
|
39
|
+
|
|
40
|
+
Every troven is backed by a conflict-free markdown file:
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
---
|
|
44
|
+
version: 1
|
|
45
|
+
id: 2026-08-30-fast-lexer-simd
|
|
46
|
+
parent: 2026-08-30-fast-lexer
|
|
47
|
+
forked_from: null
|
|
48
|
+
status: active
|
|
49
|
+
base_branch: master
|
|
50
|
+
base_commit: 8f3c1a2d
|
|
51
|
+
target_ref: null
|
|
52
|
+
created_at: "2026-08-30T14:22:00-03:00"
|
|
53
|
+
resolved_at: null
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Summary
|
|
57
|
+
|
|
58
|
+
<!-- REQUIRED before accept or reject. Agent or user fills this in. -->
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Details
|
|
63
|
+
|
|
64
|
+
<!-- Optional append-only log. -->
|
|
65
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "troven"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Git-native CLI for tracking change intents and speculative development"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "rdarder" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = []
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
troven = "troven.cli:main"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/troven"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: troven
|
|
3
|
+
description: >-
|
|
4
|
+
Manages speculative changes, experiments, and feature spikes using the Troven workflow.
|
|
5
|
+
Use when operating inside a troven worktree, accepting changes into master/target branch,
|
|
6
|
+
rejecting discarded changes with negative knowledge logs, opening past trovens, or inspecting change DAGs.
|
|
7
|
+
license: MIT
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Troven Workflow Guide
|
|
11
|
+
|
|
12
|
+
**Troven** tracks change intents and speculative software changes. It preserves both **positive knowledge** (accepted changes) and **rejection knowledge** (dead ends, discarded hypotheses, benchmark findings) in structured, conflict-free records.
|
|
13
|
+
|
|
14
|
+
The four core verbs are:
|
|
15
|
+
- `troven create <name> [--from <base>]` (CLI only, outside agent)
|
|
16
|
+
- `troven open [<id>]`
|
|
17
|
+
- `troven accept [<id>] [--into <branch>]`
|
|
18
|
+
- `troven reject [<id>]`
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Operating Inside an Active Troven Worktree
|
|
23
|
+
|
|
24
|
+
When an agent session runs inside a troven worktree:
|
|
25
|
+
|
|
26
|
+
1. **Check `README.md` First**:
|
|
27
|
+
- Consult `README.md` for project environment setup, dependencies, build, and test/validation procedures.
|
|
28
|
+
- Run required setup and verification commands per project documentation.
|
|
29
|
+
|
|
30
|
+
2. **Strict Workspace Confinement**:
|
|
31
|
+
- All code modifications, tests, and Git commits must stay confined to the troven worktree directory.
|
|
32
|
+
- Never modify or checkout `master` or other branches directly.
|
|
33
|
+
|
|
34
|
+
3. **Track Outcome in `.troven/<id>.md`**:
|
|
35
|
+
- The troven's record file lives at `.troven/<id>.md`.
|
|
36
|
+
- While working, optionally append notes, dismissed alternatives, or benchmark logs to the `## Details` section.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Transitioning: Accepting a Troven (`troven accept`)
|
|
41
|
+
|
|
42
|
+
When the user asks to accept, merge, or ship the troven (e.g. *"accept this"*, *"ship it"*, *"looks good, merge it"*):
|
|
43
|
+
|
|
44
|
+
1. **Validate**:
|
|
45
|
+
- Run the project's test suite per `README.md` to ensure all tests pass.
|
|
46
|
+
2. **Synthesize Summary**:
|
|
47
|
+
- Write a concise 1-paragraph summary into the `## Summary` section of `.troven/<id>.md`:
|
|
48
|
+
- What was attempted
|
|
49
|
+
- What was implemented
|
|
50
|
+
- Verification/validation results
|
|
51
|
+
3. **Execute Accept**:
|
|
52
|
+
- Run `troven accept` in the shell.
|
|
53
|
+
- Troven will fast-forward merge the changes into the target branch (rebasing if necessary), squash the `.troven/<id>.md` record update, and clean up the worktree and branch.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Transitioning: Rejecting a Troven (`troven reject`)
|
|
58
|
+
|
|
59
|
+
When an approach fails, performance degrades, or the user decides to abandon the troven (e.g. *"reject this"*, *"discard"*, *"this didn't work"*):
|
|
60
|
+
|
|
61
|
+
1. **Synthesize Rejection Knowledge** (Crucial Step):
|
|
62
|
+
- Write a clear, informative summary into `## Summary` of `.troven/<id>.md`:
|
|
63
|
+
- What hypothesis was tested
|
|
64
|
+
- What was found (e.g., benchmark numbers, architectural hurdles, regressions)
|
|
65
|
+
- The explicit reason why the approach was rejected
|
|
66
|
+
2. **Record Follow-up Details (Optional)**:
|
|
67
|
+
- Add notes to `## Details` regarding alternatives that might work, or where useful prototype snippets live.
|
|
68
|
+
3. **Execute Reject**:
|
|
69
|
+
- Run `troven reject` in the shell.
|
|
70
|
+
- Troven will archive the branch to `refs/archive/troven/<id>`, commit the rejection record to the base branch, and clean up the worktree and branch.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Opening and Inspecting Past Trovens
|
|
75
|
+
|
|
76
|
+
- **`troven list`**: View the full hierarchical tree of active, accepted, and rejected trovens.
|
|
77
|
+
- **`troven show <id>`**: Read the complete markdown record for any past troven.
|
|
78
|
+
- **`troven open <id>`**: Mount an active or rejected troven in a worktree to inspect code or run benchmarks.
|
|
79
|
+
- **`troven create <name> --from <id>`**: Fork a new active troven starting from an existing or rejected troven.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""CLI argument parsing and command dispatch for Troven."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from troven import __version__
|
|
9
|
+
from troven.commands.accept import execute_accept
|
|
10
|
+
from troven.commands.create import execute_create
|
|
11
|
+
from troven.commands.list_cmd import execute_list
|
|
12
|
+
from troven.commands.open_cmd import execute_open
|
|
13
|
+
from troven.commands.reject import execute_reject
|
|
14
|
+
from troven.commands.show import execute_show
|
|
15
|
+
from troven.errors import TrovenError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="troven",
|
|
21
|
+
description="Git-native CLI for tracking change intents and speculative development.",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(dest="subcommand", title="subcommands", required=True)
|
|
26
|
+
|
|
27
|
+
# create
|
|
28
|
+
create_p = subparsers.add_parser(
|
|
29
|
+
"create",
|
|
30
|
+
help="Create a new troven worktree and initialize its change intent record.",
|
|
31
|
+
)
|
|
32
|
+
create_p.add_argument("name", help="Dash-separated name for the troven (e.g. 'fast-lexer-simd')")
|
|
33
|
+
create_p.add_argument("--from", dest="from_ref", help="Base branch, commit, or parent troven ID")
|
|
34
|
+
create_p.add_argument("--repo", dest="repo_url", help="Git repository URL to clone first")
|
|
35
|
+
create_p.add_argument("-f", "--force", action="store_true", help="Skip confirmation prompts")
|
|
36
|
+
create_p.add_argument("--no-exec", action="store_true", help="Initialize worktree without launching agent")
|
|
37
|
+
create_p.add_argument("command", nargs="*", help="Custom command to run in worktree (after --)")
|
|
38
|
+
|
|
39
|
+
# open
|
|
40
|
+
open_p = subparsers.add_parser(
|
|
41
|
+
"open",
|
|
42
|
+
help="Open an existing active or rejected troven in a worktree.",
|
|
43
|
+
)
|
|
44
|
+
open_p.add_argument("id", nargs="?", help="Troven ID (defaults to current branch/worktree)")
|
|
45
|
+
open_p.add_argument("--no-exec", action="store_true", help="Mount worktree without launching agent")
|
|
46
|
+
open_p.add_argument("command", nargs="*", help="Custom command to run in worktree (after --)")
|
|
47
|
+
|
|
48
|
+
# accept
|
|
49
|
+
accept_p = subparsers.add_parser(
|
|
50
|
+
"accept",
|
|
51
|
+
help="Incorporate an active troven's changes into the target branch.",
|
|
52
|
+
)
|
|
53
|
+
accept_p.add_argument("id", nargs="?", help="Troven ID (defaults to current worktree/branch)")
|
|
54
|
+
accept_p.add_argument("--into", dest="into_branch", help="Target branch (defaults to base_branch)")
|
|
55
|
+
accept_p.add_argument("-f", "--force", action="store_true", help="Bypass foreign repo confirmation")
|
|
56
|
+
|
|
57
|
+
# reject
|
|
58
|
+
reject_p = subparsers.add_parser(
|
|
59
|
+
"reject",
|
|
60
|
+
help="Close an active troven without merging; preserve rejection knowledge in archive ref.",
|
|
61
|
+
)
|
|
62
|
+
reject_p.add_argument("id", nargs="?", help="Troven ID (defaults to current worktree/branch)")
|
|
63
|
+
|
|
64
|
+
# list
|
|
65
|
+
list_p = subparsers.add_parser(
|
|
66
|
+
"list",
|
|
67
|
+
help="List all trovens in a hierarchical tree view.",
|
|
68
|
+
)
|
|
69
|
+
list_p.add_argument("--status", choices=["active", "accepted", "rejected"], help="Filter by status")
|
|
70
|
+
|
|
71
|
+
# show
|
|
72
|
+
show_p = subparsers.add_parser(
|
|
73
|
+
"show",
|
|
74
|
+
help="Print the full markdown record for a troven.",
|
|
75
|
+
)
|
|
76
|
+
show_p.add_argument("id", nargs="?", help="Troven ID (defaults to current worktree/branch)")
|
|
77
|
+
|
|
78
|
+
# install-skill
|
|
79
|
+
install_skill_p = subparsers.add_parser(
|
|
80
|
+
"install-skill",
|
|
81
|
+
help="Install the troven agent skill to ~/.agents/skills/troven and ~/.gemini/config/skills/troven.",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return parser
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main(argv: list[str] | None = None) -> int:
|
|
88
|
+
parser = build_parser()
|
|
89
|
+
args = parser.parse_args(argv)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
if args.subcommand == "create":
|
|
93
|
+
cmd = args.command
|
|
94
|
+
if cmd and cmd[0] == "--":
|
|
95
|
+
cmd = cmd[1:]
|
|
96
|
+
execute_create(
|
|
97
|
+
name=args.name,
|
|
98
|
+
from_ref=args.from_ref,
|
|
99
|
+
repo_url=args.repo_url,
|
|
100
|
+
force=args.force,
|
|
101
|
+
command=cmd,
|
|
102
|
+
no_exec=args.no_exec,
|
|
103
|
+
)
|
|
104
|
+
elif args.subcommand == "open":
|
|
105
|
+
cmd = args.command
|
|
106
|
+
if cmd and cmd[0] == "--":
|
|
107
|
+
cmd = cmd[1:]
|
|
108
|
+
execute_open(
|
|
109
|
+
troven_id=args.id,
|
|
110
|
+
command=cmd,
|
|
111
|
+
no_exec=args.no_exec,
|
|
112
|
+
)
|
|
113
|
+
elif args.subcommand == "accept":
|
|
114
|
+
execute_accept(
|
|
115
|
+
troven_id=args.id,
|
|
116
|
+
into_branch=args.into_branch,
|
|
117
|
+
force=args.force,
|
|
118
|
+
)
|
|
119
|
+
elif args.subcommand == "reject":
|
|
120
|
+
execute_reject(
|
|
121
|
+
troven_id=args.id,
|
|
122
|
+
)
|
|
123
|
+
elif args.subcommand == "list":
|
|
124
|
+
execute_list(
|
|
125
|
+
status_filter=args.status,
|
|
126
|
+
)
|
|
127
|
+
elif args.subcommand == "show":
|
|
128
|
+
execute_show(
|
|
129
|
+
troven_id=args.id,
|
|
130
|
+
)
|
|
131
|
+
elif args.subcommand == "install-skill":
|
|
132
|
+
from troven.commands.install_skill import execute_install_skill
|
|
133
|
+
execute_install_skill()
|
|
134
|
+
return 0
|
|
135
|
+
except TrovenError as err:
|
|
136
|
+
print(f"\033[31mError:\033[0m {err}", file=sys.stderr)
|
|
137
|
+
return 1
|
|
138
|
+
except KeyboardInterrupt:
|
|
139
|
+
print("\nAborted.", file=sys.stderr)
|
|
140
|
+
return 130
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Troven command implementations."""
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Implementation of 'troven accept' subcommand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from troven import git
|
|
10
|
+
from troven.commands.open_cmd import detect_current_troven_id
|
|
11
|
+
from troven.errors import (
|
|
12
|
+
InvalidStateError,
|
|
13
|
+
MergeConflictError,
|
|
14
|
+
RebaseConflictError,
|
|
15
|
+
TrovenError,
|
|
16
|
+
TrovenNotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from troven.state import (
|
|
19
|
+
get_troven_file,
|
|
20
|
+
load_troven,
|
|
21
|
+
save_troven,
|
|
22
|
+
validate_summary_non_empty,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def execute_accept(
|
|
27
|
+
troven_id: str | None = None,
|
|
28
|
+
into_branch: str | None = None,
|
|
29
|
+
force: bool = False,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Incorporate active troven changes into the target branch."""
|
|
32
|
+
repo_root = git.get_main_repo_root()
|
|
33
|
+
|
|
34
|
+
if not troven_id:
|
|
35
|
+
detected = detect_current_troven_id(repo_root)
|
|
36
|
+
if not detected:
|
|
37
|
+
raise TrovenError(
|
|
38
|
+
"Troven ID required when not run from inside a troven worktree.",
|
|
39
|
+
remediation="Specify a troven ID: 'troven accept <id>' or run inside the worktree.",
|
|
40
|
+
)
|
|
41
|
+
troven_id = detected
|
|
42
|
+
|
|
43
|
+
clean_id = troven_id.removeprefix("troven/")
|
|
44
|
+
branch_name = f"troven/{clean_id}"
|
|
45
|
+
|
|
46
|
+
if not git.branch_exists(branch_name, cwd=repo_root):
|
|
47
|
+
raise TrovenNotFoundError(
|
|
48
|
+
f"Active branch '{branch_name}' not found.",
|
|
49
|
+
remediation=f"Check 'troven list' to view available trovens.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Locate worktree
|
|
53
|
+
worktree_dir = git.find_worktree_for_branch(branch_name, cwd=repo_root)
|
|
54
|
+
|
|
55
|
+
# Load and validate troven record from worktree if present, else base repo
|
|
56
|
+
if worktree_dir and worktree_dir.exists() and get_troven_file(worktree_dir, clean_id).exists():
|
|
57
|
+
md_source_dir = worktree_dir
|
|
58
|
+
else:
|
|
59
|
+
md_source_dir = repo_root
|
|
60
|
+
record = load_troven(md_source_dir, clean_id)
|
|
61
|
+
md_path = get_troven_file(md_source_dir, clean_id)
|
|
62
|
+
|
|
63
|
+
if record.status != "active":
|
|
64
|
+
raise InvalidStateError(
|
|
65
|
+
f"Troven '{clean_id}' is in state '{record.status}', not 'active'.",
|
|
66
|
+
remediation="Only active trovens can be accepted.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Gate-check summary
|
|
70
|
+
validate_summary_non_empty(record, md_path)
|
|
71
|
+
|
|
72
|
+
target_branch = into_branch or record.base_branch or "master"
|
|
73
|
+
|
|
74
|
+
# Foreign repo warning check
|
|
75
|
+
if not (repo_root / ".troven").exists() and not force and sys.stdin.isatty():
|
|
76
|
+
ans = input(
|
|
77
|
+
"This repository does not appear to use troven in its target branch.\n"
|
|
78
|
+
f"Accepting will add .troven/ to '{target_branch}'. Confirm? [y/N]: "
|
|
79
|
+
).strip()
|
|
80
|
+
if ans.lower() not in ("y", "yes"):
|
|
81
|
+
print("Aborted.")
|
|
82
|
+
sys.exit(0)
|
|
83
|
+
|
|
84
|
+
# Ensure any pending .troven record changes inside worktree are committed to the branch
|
|
85
|
+
if worktree_dir and worktree_dir.exists():
|
|
86
|
+
wt_md = get_troven_file(worktree_dir, clean_id)
|
|
87
|
+
if wt_md.exists():
|
|
88
|
+
git.commit_files([wt_md], f"troven: record summary for {clean_id}", cwd=worktree_dir)
|
|
89
|
+
|
|
90
|
+
# Switch base repo to target branch
|
|
91
|
+
git.checkout_branch(target_branch, cwd=repo_root)
|
|
92
|
+
|
|
93
|
+
# Attempt fast-forward merge
|
|
94
|
+
print(f"Fast-forward merging '{branch_name}' into '{target_branch}'...")
|
|
95
|
+
if not git.merge_ff_only(branch_name, cwd=repo_root):
|
|
96
|
+
print(f"Branches have diverged. Attempting rebase inside worktree...")
|
|
97
|
+
if not worktree_dir or not worktree_dir.exists():
|
|
98
|
+
raise MergeConflictError(
|
|
99
|
+
f"Fast-forward merge failed and worktree for '{branch_name}' is not found locally.",
|
|
100
|
+
remediation=f"Open the troven with 'troven open {clean_id}', rebase onto '{target_branch}', and try again.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not git.rebase(target_branch, cwd=worktree_dir):
|
|
104
|
+
git.rebase_abort(cwd=worktree_dir)
|
|
105
|
+
raise RebaseConflictError(
|
|
106
|
+
f"Rebase of '{branch_name}' onto '{target_branch}' encountered conflicts.",
|
|
107
|
+
remediation=f"Resolve conflicts manually inside '{worktree_dir}' and run 'troven accept {clean_id}'.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# After rebase, retry fast-forward merge
|
|
111
|
+
if not git.merge_ff_only(branch_name, cwd=repo_root):
|
|
112
|
+
raise MergeConflictError(
|
|
113
|
+
f"Fast-forward merge still failed after rebase.",
|
|
114
|
+
remediation=f"Inspect branch '{branch_name}' in '{worktree_dir}'.",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Update troven record status to accepted
|
|
118
|
+
record.status = "accepted"
|
|
119
|
+
record.resolved_at = datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
|
120
|
+
saved_md = save_troven(repo_root, record)
|
|
121
|
+
|
|
122
|
+
# Squash update of .troven record to target branch
|
|
123
|
+
git.commit_files([saved_md], f"troven: accept {clean_id}", cwd=repo_root)
|
|
124
|
+
|
|
125
|
+
# Cleanup worktree and branch
|
|
126
|
+
if worktree_dir and worktree_dir.exists():
|
|
127
|
+
print(f"Removing worktree at {worktree_dir}...")
|
|
128
|
+
git.remove_worktree(worktree_dir, force=True, cwd=repo_root)
|
|
129
|
+
|
|
130
|
+
print(f"Deleting branch '{branch_name}'...")
|
|
131
|
+
git.delete_branch(branch_name, force=True, cwd=repo_root)
|
|
132
|
+
git.prune_worktrees(cwd=repo_root)
|
|
133
|
+
|
|
134
|
+
print(f"\n\033[32m[Success]\033[0m Troven '{clean_id}' accepted into '{target_branch}'.")
|