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
39 changes: 36 additions & 3 deletions src/kb/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1553,13 +1553,21 @@ def glossary() -> None:
@click.argument("expansion")
@click.option("--section", default="Acronyms", help="Glossary section heading.")
@output_options
@_commit_option
def glossary_add(
term: str, expansion: str, section: str, fmt: str, fields: list[str] | None, jq_expr: str | None
term: str,
expansion: str,
section: str,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
) -> None:
"""Add a term to the glossary."""
from kb.glossary import add_term

result = add_term(_find_project_root(), term, expansion, section=section)
_autocommit_after_write("add-glossary", term, no_commit=no_commit)
kb_output(result, fmt=fmt, fields=fields, jq_expr=jq_expr)


Expand All @@ -1578,7 +1586,8 @@ def glossary_list(fmt: str, fields: list[str] | None, jq_expr: str | None) -> No

@glossary.command("delete")
@click.argument("term")
def glossary_delete(term: str) -> None:
@_commit_option
def glossary_delete(term: str, no_commit: bool) -> None:
"""Delete a term from the glossary."""
from kb.glossary import delete_term

Expand All @@ -1587,15 +1596,22 @@ def glossary_delete(term: str) -> None:
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
_autocommit_after_write("delete-glossary", term, no_commit=no_commit)
click.echo(f"Deleted: {term}")


@glossary.command("edit")
@click.argument("term")
@click.argument("expansion")
@output_options
@_commit_option
def glossary_edit(
term: str, expansion: str, fmt: str, fields: list[str] | None, jq_expr: str | None
term: str,
expansion: str,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
) -> None:
"""Update an existing glossary term's expansion."""
from kb.glossary import edit_term
Expand All @@ -1605,6 +1621,7 @@ def glossary_edit(
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
_autocommit_after_write("edit-glossary", term, no_commit=no_commit)
kb_output(result, fmt=fmt, fields=fields, jq_expr=jq_expr)


Expand Down Expand Up @@ -2057,13 +2074,15 @@ def memory() -> None:
@click.option("--tags", "tags_str", default=None, help="Comma-separated tags.")
@click.option("--pin", "pin_flag", is_flag=True, help="Pin this note to context.")
@click.option("--date", "fact_date", default=None, help="Date (YYYY-MM-DD, default: today).")
@_commit_option
def memory_add(
text: str,
entity_name: str | None,
body: str | None,
tags_str: str | None,
pin_flag: bool,
fact_date: str | None,
no_commit: bool,
) -> None:
"""Record a fact about an entity, or create a searchable note.

Expand All @@ -2088,6 +2107,7 @@ def memory_add(
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
_autocommit_after_write("add-fact", str(result["entity"]), no_commit=no_commit)
return

# Read body from stdin if -
Expand All @@ -2107,6 +2127,7 @@ def memory_add(
click.echo(f"Note created: {result['path']}", err=True)
if pin_flag:
click.echo("Pinned to context.", err=True)
_autocommit_after_write("create-note", text, no_commit=no_commit)
finally:
kb.close()

Expand Down Expand Up @@ -2224,10 +2245,12 @@ def memory_similar_cmd(
@click.argument("fact_seq", type=int)
@click.option("--force", is_flag=True, help="Skip confirmation prompt.")
@output_options
@_commit_option
def memory_delete_fact(
entity_name: str,
fact_seq: int,
force: bool,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
Expand All @@ -2245,6 +2268,7 @@ def memory_delete_fact(
click.echo(f"Deleted fact #{fact_seq} on '{entity_name}'.", err=True)
else:
kb_output(result, fmt=fmt, fields=fields, jq_expr=jq_expr)
_autocommit_after_write("delete-fact", entity_name, no_commit=no_commit)
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
Expand All @@ -2258,11 +2282,13 @@ def memory_delete_fact(
@click.option("--text", default=None, help="New fact text.")
@click.option("--date", "fact_date", default=None, help="New fact date (YYYY-MM-DD).")
@output_options
@_commit_option
def memory_edit_fact(
entity_name: str,
fact_seq: int,
text: str | None,
fact_date: str | None,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
Expand All @@ -2281,6 +2307,7 @@ def memory_edit_fact(
click.echo(f"Updated fact #{fact_seq} on '{entity_name}'.", err=True)
else:
kb_output(result, fmt=fmt, fields=fields, jq_expr=jq_expr)
_autocommit_after_write("edit-fact", entity_name, no_commit=no_commit)
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
Expand Down Expand Up @@ -2384,13 +2411,15 @@ def note_list(
@click.option("--tags", "tags_str", default=None, help="Comma-separated tags (replaces existing).")
@click.option("--pin/--unpin", "pin_flag", default=None, help="Pin or unpin note.")
@output_options
@_commit_option
def note_edit(
target: str,
body: str | None,
append_text: str | None,
insert_under: str | None,
tags_str: str | None,
pin_flag: bool | None,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
Expand Down Expand Up @@ -2433,6 +2462,7 @@ def note_edit(
click.echo(f"Updated: {result['path']}", err=True)
else:
kb_output(result_data, fmt=fmt, fields=fields, jq_expr=jq_expr)
_autocommit_after_write("edit-note", str(result["path"]), no_commit=no_commit)
except ValueError as e:
click.echo(str(e), err=True)
raise SystemExit(1) from None
Expand All @@ -2444,9 +2474,11 @@ def note_edit(
@click.argument("target")
@click.option("--force", is_flag=True, help="Skip confirmation prompt.")
@output_options
@_commit_option
def note_delete(
target: str,
force: bool,
no_commit: bool,
fmt: str,
fields: list[str] | None,
jq_expr: str | None,
Expand Down Expand Up @@ -2495,6 +2527,7 @@ def note_delete(
click.echo(f"Deleted: {rel_path}", err=True)
else:
kb_output(result_data, fmt=fmt, fields=fields, jq_expr=jq_expr)
_autocommit_after_write("delete-note", str(rel_path), no_commit=no_commit)


# ---------------------------------------------------------------------------
Expand Down
84 changes: 84 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,90 @@ def test_person_delete_invokes_autocommit(self, runner, notes_env, monkeypatch):
assert kwargs["operation"].startswith("delete-")


class TestWriteAutoCommit:
"""memory / note / glossary write commands invoke auto-commit with the right op (#1).

Real git behaviour is in tests/test_autocommit.py; here maybe_auto_commit is mocked.
"""

def test_glossary_add_invokes_autocommit(self, runner, notes_env, monkeypatch):
from unittest.mock import MagicMock

_db, root, data_dir = notes_env
mock = MagicMock(return_value=None)
monkeypatch.setattr("kb.autocommit.maybe_auto_commit", mock)
result = invoke_cli_with_root(
runner, ["glossary", "add", "TLA", "Three Letter Acronym"], data_dir, root
)
assert result.exit_code == 0, result.output
mock.assert_called_once()
_, kwargs = mock.call_args
assert kwargs["operation"] == "add-glossary"
assert kwargs["target"] == "TLA"

def test_memory_add_note_invokes_autocommit(self, runner, notes_env, monkeypatch):
from unittest.mock import MagicMock

_db, root, data_dir = notes_env
mock = MagicMock(return_value=None)
monkeypatch.setattr("kb.autocommit.maybe_auto_commit", mock)
result = invoke_cli_with_root(
runner, ["memory", "add", "My Note", "--body", "content"], data_dir, root
)
assert result.exit_code == 0, result.output
mock.assert_called_once()
_, kwargs = mock.call_args
assert kwargs["operation"] == "create-note"

def test_memory_add_fact_invokes_autocommit(self, runner, notes_env, monkeypatch):
from unittest.mock import MagicMock

from kb.crud import create_entity

db, root, data_dir = notes_env
create_entity(db, root, "person", "Wren")
mock = MagicMock(return_value=None)
monkeypatch.setattr("kb.autocommit.maybe_auto_commit", mock)
result = invoke_cli_with_root(
runner, ["memory", "add", "ships fast", "--entity", "Wren"], data_dir, root
)
assert result.exit_code == 0, result.output
mock.assert_called_once()
_, kwargs = mock.call_args
assert kwargs["operation"] == "add-fact"

def test_note_edit_invokes_autocommit(self, runner, notes_env, monkeypatch):
from unittest.mock import MagicMock

_db, root, data_dir = notes_env
# create a real memory note first (note edit requires doc_type memory_note)
invoke_cli_with_root(runner, ["memory", "add", "Scratch", "--body", "x"], data_dir, root)
mock = MagicMock(return_value=None)
monkeypatch.setattr("kb.autocommit.maybe_auto_commit", mock)
result = invoke_cli_with_root(
runner, ["note", "edit", "Scratch", "--append", "extra"], data_dir, root
)
assert result.exit_code == 0, result.output
mock.assert_called_once()
_, kwargs = mock.call_args
assert kwargs["operation"] == "edit-note"

def test_note_delete_invokes_autocommit(self, runner, notes_env, monkeypatch):
from unittest.mock import MagicMock

_db, root, data_dir = notes_env
invoke_cli_with_root(runner, ["memory", "add", "Scratch", "--body", "x"], data_dir, root)
mock = MagicMock(return_value=None)
monkeypatch.setattr("kb.autocommit.maybe_auto_commit", mock)
result = invoke_cli_with_root(
runner, ["note", "delete", "Scratch", "--force"], data_dir, root
)
assert result.exit_code == 0, result.output
mock.assert_called_once()
_, kwargs = mock.call_args
assert kwargs["operation"] == "delete-note"


class TestDocumentPin:
def test_pin_by_path(self, runner, notes_env):
"""kb pin <path> sets the pinned flag."""
Expand Down