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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Concurrent edits. By default `git-draft` does not touch the working directory.
* Customizable prompt templates.
* Extensible bot API.
* Local data collection for privacy-friendly analytics.


## Installation
Expand All @@ -23,8 +24,14 @@ pipx install git-draft[openai]
* Mechanism for reporting feedback from a bot, and possibly allowing user to
interactively respond.
* Add configuration option to auto sync and `--no-sync` flag. Similar to reset.
* Add "amend" commit when finalizing. This could be useful training data,
showing what the bot did not get right.
Also rename both options to `sync` and `reset`, this will make it more natural
to support a similar config option for `accept`.
* Add `--sync` `finalize` option which creates a additional commit when
finalizing if any changes were added to the bot's output. This could be useful
training data, showing what the bot did not get right.
* Convenience `--accept` functionality for simple cases: checkout option which
applies the changes, and finalizes the draft if specified multiple times. For
example `git draft -aa add-test symbol=foo`
* Support file rename tool.
* https://stackoverflow.com/q/49853177/1062617
* https://stackoverflow.com/q/6658313/1062617
32 changes: 22 additions & 10 deletions docs/git-draft.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,27 @@ IMPORTANT: `git-draft` is WIP.
== Synopsis

[verse]
git draft [options] [--generate] [--bot BOT] [--edit] [--reset | --no-reset] [--sync] [TEMPLATE [VARIABLE...]]
git draft [options] --finalize [--delete]
git draft [options] [--generate] [--accept... | no-accept] [--bot BOT] [--edit] [--reset | --no-reset] [--sync | --no-sync] [TEMPLATE [VARIABLE...]]
git draft [options] --finalize [--delete] [--sync | --no-sync]
git draft [options] --show-drafts [--json]
git draft [options] --show-prompts [--json] [PROMPT]
git draft [options] --show-templates [--json | [--edit] TEMPLATE]


== Description

`git-draft` is a git-centric way to edit code using AI.
`git-draft` is a git-centric way to develop using AI.


== Options

-a::
--accept::
--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.
Expand All @@ -42,15 +49,17 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]

-e::
--edit::
Edit.
Enable interactive editing of prompts and templates.
See `--generate` and `--show-templates` for details.

-F::
--finalize::
TODO
Go back to the draft's origin branch with the current working directory.

-G::
--generate::
TODO
Add an AI-generated commit.
If the `--edit` option is set, an interactive editor will be open with the rendered prompt to allow modification before it is forwarded to the bot.

-h::
--help::
Expand All @@ -65,22 +74,25 @@ git draft [options] --show-templates [--json | [--edit] TEMPLATE]

--reset::
--no-reset::
TODO
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::
TODO
List recently created drafts.

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

-T::
--show-templates::
TODO
Lists available templates.
With an template name argument, displays the corresponding template's contents or, if the `--edit` option is set, opens an interactive editor.

-s::
--sync::
Expand Down
126 changes: 67 additions & 59 deletions src/git_draft/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from .bots import load_bot
from .common import PROGRAM, Config, UnreachableError, ensure_state_home
from .drafter import Drafter
from .drafter import Accept, Drafter
from .editor import open_editor
from .prompt import Template, TemplatedPrompt, find_template, templates_table
from .store import Store
Expand Down Expand Up @@ -64,6 +64,12 @@ def callback(
add_command("show-prompts", short="P", help="show prompt history")
add_command("show-templates", short="T", help="show template information")

parser.add_option(
"-a",
"--accept",
help="apply generated changes",
action="count",
)
parser.add_option(
"-b",
"--bot",
Expand Down Expand Up @@ -171,67 +177,69 @@ def main() -> None: # noqa: PLR0912 PLR0915
logging.basicConfig(level=config.log_level, filename=str(log_path))

drafter = Drafter.create(store=Store.persistent(), path=opts.root)
command = getattr(opts, "command", "generate")
if command == "generate":
bot_config = None
if opts.bot:
bot_configs = [c for c in config.bots if c.name == opts.bot]
if len(bot_configs) != 1:
raise ValueError(f"Found {len(bot_configs)} matching bots")
bot_config = bot_configs[0]
elif config.bots:
bot_config = config.bots[0]
bot = load_bot(bot_config)

prompt: str | TemplatedPrompt
editable = opts.edit
if args:
prompt = TemplatedPrompt.parse(args[0], *args[1:])
elif opts.edit:
editable = False
prompt = edit(
text=drafter.latest_draft_prompt() or _PROMPT_PLACEHOLDER
match getattr(opts, "command", "generate"):
case "generate":
bot_config = None
if opts.bot:
bot_configs = [c for c in config.bots if c.name == opts.bot]
if len(bot_configs) != 1:
raise ValueError(f"Found {len(bot_configs)} matching bots")
bot_config = bot_configs[0]
elif config.bots:
bot_config = config.bots[0]
bot = load_bot(bot_config)

prompt: str | TemplatedPrompt
editable = opts.edit
if args:
prompt = TemplatedPrompt.parse(args[0], *args[1:])
elif opts.edit:
editable = False
prompt = edit(
text=drafter.latest_draft_prompt() or _PROMPT_PLACEHOLDER
)
else:
prompt = sys.stdin.read()

accept = Accept(opts.accept or 0)
name = drafter.generate_draft(
prompt,
bot,
accept=accept,
bot_name=opts.bot,
prompt_transform=open_editor if editable else None,
tool_visitors=[ToolPrinter()],
reset=config.auto_reset if opts.reset is None else opts.reset,
sync=opts.sync,
)
else:
prompt = sys.stdin.read()

name = drafter.generate_draft(
prompt,
bot,
bot_name=opts.bot,
prompt_transform=open_editor if editable else None,
tool_visitors=[ToolPrinter()],
reset=config.auto_reset if opts.reset is None else opts.reset,
sync=opts.sync,
)
print(f"Refined {name}.")
elif command == "finalize":
name = drafter.finalize_draft(delete=opts.delete)
print(f"Finalized {name}.")
elif command == "show-drafts":
table = drafter.history_table(args[0] if args else None)
if table:
print(table.to_json() if opts.json else table)
elif command == "show-prompts":
raise NotImplementedError() # TODO: Implement
elif command == "show-templates":
if args:
name = args[0]
tpl = find_template(name)
if opts.edit:
if tpl:
edit(path=tpl.local_path(), text=tpl.source)
print(f"Generated change in {name}.")
case "finalize":
name = drafter.finalize_draft(delete=opts.delete)
print(f"Finalized {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
case "show-templates":
if args:
name = args[0]
tpl = find_template(name)
if opts.edit:
if tpl:
edit(path=tpl.local_path(), text=tpl.source)
else:
edit(path=Template.local_path_for(name))
else:
edit(path=Template.local_path_for(name))
if not tpl:
raise ValueError(f"No template named {name!r}")
print(tpl.source)
else:
if not tpl:
raise ValueError(f"No template named {name!r}")
print(tpl.source)
else:
table = templates_table()
print(table.to_json() if opts.json else table)
else:
raise UnreachableError()
table = templates_table()
print(table.to_json() if opts.json else table)
case _:
raise UnreachableError()


if __name__ == "__main__":
Expand Down
Loading