Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 63 additions & 29 deletions docs/git-draft.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ IMPORTANT: `git-draft` is WIP.
[verse]
git draft [options] [--generate] [--accept... | --no-accept] [--bot BOT]
[--edit] [--reset | --no-reset] [TEMPLATE [VARIABLE...]]
git draft [options] --finalize [--delete]
git draft [options] --show-drafts [--json]
git draft [options] --show-prompts [--json] [PROMPT]
git draft [options] --finalize
git draft [options] --show-templates [--json | [--edit] TEMPLATE]


Expand All @@ -38,16 +36,11 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]
--no-accept::
Check out generated changes automatically.
Can be repeated.
This may fail if you manually edit files that the bot updates during generation.

-b BOT::
--bot=BOT::
Bot name.

-d::
--delete::
Delete finalized branch.

-e::
--edit::
Enable interactive editing of prompts and templates.
Expand All @@ -73,23 +66,9 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]
--log::
Show log path and exit.

--reset::
--no-reset::
Controls behavior when staged changes are present at the start of a generate command.
If enabled, these changes are automatically reset and combined with other working directory changes.
Otherwise an error is raised.

--root::
Repository search root.

-D::
--show-drafts::
List recently created drafts.

-P::
--show-prompts::
Lists recently used prompts.

-T::
--show-templates::
Lists available templates.
Expand All @@ -109,20 +88,75 @@ The workhorse command is `git draft --generate` which leverages AI to edit our c
A prompt can be specified as standard input, for example `echo "Add a test for compute_offset in chart.py" | git draft --generate`.
If no prompt is specified and stdin is a TTY, `$EDITOR` will be opened to enter the prompt.

If not on a draft branch, a new draft branch called `drafts/$parent/$hash` will be created (`$hash` is a random suffix used to guarantee uniqueness of branch names) and checked out.
By default any unstaged changes are then automatically added and committed (`draft! sync`).
This behavior can be disabled by passing in `--stash`, which will instead add them to the stash.
Staged changes are always committed.

The prompt automatically gets augmented with information about the files in the repository, and give the AI access to tools for reading and writing files.
Once the response has been received and changes applied, a commit is created (`draft! prompt: a short summary of the change`).
By default, the prompt gets augmented with information about the files in the repository, and give the AI access to tools for reading and writing files.
Once the response has been received and changes applied, a commit is created in a separate branch.

The `--generate` step can be repeated as many times as needed.
Once you are satisfied with the changes, run `git draft --finalize` to apply them.
This will check out the branch used when creating the draft, adding the final state of the draft to the worktree.
Note that you can come back to an existing draft anytime (by checking its branch out), but you will not be able to apply it if its origin branch has moved since the draft was created.


* Generate: create a new draft to the current folio, or create a new folio if none exists.
* Finalize
** Apply: include changes into origin branch.
** Discard: abandon folio.
** Save: return to original branch. Q: how to load after?
* List templates


o Foo (main)
o Sync (drafts/123/pub)

# After generate without accept
o Foo (main)
o Sync (drafts/123)
o draft! <prompt> (drafts/123+, refs/drafts/123/1)

# After generate with accept
o Foo (main)
o Sync
|\
| o draft! prompt: <prompt> (refs/drafts/123/1)
o | Sync
|/
o Merge (drafts/123/pub)


o Foo (main)
o draft! sync
|\
| o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/1)
o Something
o Also something (drafts/123)


o Foo (main)
o draft! sync
|\
| o draft! prompt: <prompt> (refs/drafts/123/1)
o Something
o Also something (drafts/123/pub)
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)


o Foo (main)
o draft! sync (drafts/123/pub)
|\
| o draft! prompt: <prompt> (refs/drafts/123/1)
\
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)

o Foo (main)
o draft! sync (drafts/123/pub)
|\
| o draft! prompt: <prompt> (refs/drafts/123/1)
|/
o draft! sync
\
o draft! prompt: <prompt> (drafts/123+, refs/drafts/123/2)


== See also

`git(1)`
3 changes: 2 additions & 1 deletion src/git_draft/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import logging

from .bots import Action, Bot, Goal, Toolbox
from .bots import Action, Bot, Goal
from .toolbox import Toolbox


__all__ = [
Expand Down
43 changes: 8 additions & 35 deletions src/git_draft/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ def callback(

add_command("finalize", help="apply current draft to original branch")
add_command("generate", help="create or update draft from a prompt")
add_command("show-drafts", short="D", help="show draft history")
add_command("show-prompts", short="P", help="show prompt history")
add_command("show-templates", short="T", help="show template information")

parser.add_option(
Expand All @@ -76,12 +74,6 @@ def callback(
dest="bot",
help="bot name",
)
parser.add_option(
"-d",
"--delete",
help="delete draft after finalizing",
action="store_true",
)
parser.add_option(
"-e",
"--edit",
Expand All @@ -102,18 +94,6 @@ def callback(
action="store_const",
const=0,
)
parser.add_option(
"--no-reset",
help="abort if there are any staged changes",
dest="reset",
action="store_false",
)
parser.add_option(
"--reset",
help="reset index before generating a new draft",
dest="reset",
action="store_true",
)
parser.add_option(
"--timeout",
dest="timeout",
Expand Down Expand Up @@ -213,33 +193,26 @@ def main() -> None: # noqa: PLR0912 PLR0915
prompt = sys.stdin.read()

accept = Accept(opts.accept or 0)
draft = drafter.generate_draft(
drafter.generate_draft(
prompt,
bot,
accept=accept,
bot_name=opts.bot,
prompt_transform=open_editor if editable else None,
tool_visitors=[ToolPrinter()],
reset=config.reset if opts.reset is None else opts.reset,
)
match accept:
case Accept.MANUAL:
print(f"Generated change in {draft.branch_name}.")
case Accept.CHECKOUT:
print(f"Applied change in {draft.branch_name}.")
case Accept.FINALIZE | Accept.NO_REGRETS:
print(f"Finalized change via {draft.branch_name}.")
print("Generated draft.")
case Accept.MERGE:
print("Merged draft.")
case Accept.FINALIZE:
print("Finalized draft.")
case _:
raise UnreachableError()
case "finalize":
draft = drafter.finalize_draft(delete=opts.delete)
print(f"Finalized {draft.branch_name}.")
case "show-drafts":
table = drafter.history_table(args[0] if args else None)
if table:
print(table.to_json() if opts.json else table)
case "show-prompts":
raise NotImplementedError() # TODO: Implement
drafter.finalize_folio()
print("Finalized draft folio.")
case "show-templates":
if args:
name = args[0]
Expand Down
23 changes: 0 additions & 23 deletions src/git_draft/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import logging
import os
from pathlib import Path
import random
import sqlite3
import string
import textwrap
import tomllib
from typing import Any, ClassVar, Self
Expand Down Expand Up @@ -41,7 +39,6 @@ class Config:

bots: Sequence[BotConfig] = dataclasses.field(default_factory=lambda: [])
log_level: int = logging.INFO
reset: bool = True

@staticmethod
def folder_path() -> Path:
Expand Down Expand Up @@ -73,31 +70,11 @@ class BotConfig:
pythonpath: str | None = None


type RepoID = str


@dataclasses.dataclass(frozen=True)
class RepoConfig: # TODO: Use
"""Repository-specific config"""

repo_id: str
bot_name: str | None = None


def config_string(arg: str) -> str:
"""Dereferences environment value if the input starts with `$`"""
return os.environ[arg[1:]] if arg and arg.startswith("$") else arg


_random = random.Random()
_alphabet = string.ascii_lowercase + string.digits


def random_id(n: int) -> str:
"""Generates a random length n string of lowercase letters and digits"""
return "".join(_random.choices(_alphabet, k=n))


class UnreachableError(RuntimeError):
"""Indicates unreachable code was unexpectedly executed"""

Expand Down
Loading