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.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
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,3 @@
1
+ """Troven - Git-native CLI for tracking change intents and speculative development."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """Entry point for python -m troven."""
2
+
3
+ import sys
4
+ from troven.cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
@@ -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}'.")