filekeeper-cli 0.1.6__tar.gz → 0.1.7__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.
Files changed (36) hide show
  1. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/PKG-INFO +12 -2
  2. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/README.md +11 -1
  3. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/pyproject.toml +1 -1
  4. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/pyproject.toml.orig +1 -1
  5. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/__init__.py +1 -1
  6. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/history/undo.py +6 -1
  7. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/models.py +23 -11
  8. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/LICENSE +0 -0
  9. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/cli/__init__.py +0 -0
  10. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/cli/app.py +0 -0
  11. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/cli/exit_codes.py +0 -0
  12. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/cli/output.py +0 -0
  13. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/__init__.py +0 -0
  14. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/config.py +0 -0
  15. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/database.py +0 -0
  16. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/diffing.py +0 -0
  17. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/errors.py +0 -0
  18. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/history/__init__.py +0 -0
  19. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/history/blobs.py +0 -0
  20. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/history/journal.py +0 -0
  21. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/history/repository.py +0 -0
  22. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/__init__.py +0 -0
  23. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/directory.py +0 -0
  24. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/edit.py +0 -0
  25. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/read.py +0 -0
  26. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/search.py +0 -0
  27. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/operations/write.py +0 -0
  28. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/paths.py +0 -0
  29. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/__init__.py +0 -0
  30. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/atomic.py +0 -0
  31. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/hashing.py +0 -0
  32. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/sensitive.py +0 -0
  33. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/textdata.py +0 -0
  34. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/safety/vcs.py +0 -0
  35. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/service.py +0 -0
  36. {filekeeper_cli-0.1.6 → filekeeper_cli-0.1.7}/src/filekeeper/core/workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: filekeeper-cli
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Safe, predictable filesystem operations for humans and AI agents, with atomic writes and workspace confinement.
5
5
  Keywords: filesystem,atomic-write,cli,agents,mcp
6
6
  Author: sangingeum
@@ -333,7 +333,10 @@ meaning — the path the operation targets — so existing readers are unaffecte
333
333
  A refused undo is recorded too: the ledger gains an `undo_refused` row with the
334
334
  target path, the conflict code, and the paths that blocked the undo. It changed
335
335
  nothing, so it carries no items, is never offered as undoable, and `undo-last`
336
- skips it. Every recorded path is workspace-relative, never a resolved host path.
336
+ skips it. A refusal of a refusal is the one exception: undoing that
337
+ `undo_refused` row is refused with the same coded error, but writes no second row,
338
+ because the existing row already states the conflict. Every recorded path is
339
+ workspace-relative, never a resolved host path.
337
340
 
338
341
  `keeper file read` writes file content to standard output unchanged (line endings
339
342
  included). Metadata goes to stderr, or into the JSON object with `--json`.
@@ -407,6 +410,13 @@ preview was computed against. The same shape applies to every mutating command a
407
410
  to the MCP tools that mirror them. A committed operation still reports
408
411
  `"changed": true` with its observed `after_hash`.
409
412
 
413
+ `changed` and `would_change` answer different questions: `would_change` asks
414
+ whether the bytes would differ, while `changed` records whether an operation
415
+ executed and was journalled — so a real write of identical content reports
416
+ `"changed": true` and adds a history row, which callers may rely on as proof a
417
+ write completed. In a preview the two cannot disagree: when `would_change` is
418
+ false, every item reports `"unchanged"`.
419
+
410
420
  Editing results add `match_stage` and `match_count` (text replacement),
411
421
  `preview_diff` (dry run), and, for patches, `patch_files` with per-hunk status.
412
422
  A failure that was rolled back reports the restored paths in the error details.
@@ -312,7 +312,10 @@ meaning — the path the operation targets — so existing readers are unaffecte
312
312
  A refused undo is recorded too: the ledger gains an `undo_refused` row with the
313
313
  target path, the conflict code, and the paths that blocked the undo. It changed
314
314
  nothing, so it carries no items, is never offered as undoable, and `undo-last`
315
- skips it. Every recorded path is workspace-relative, never a resolved host path.
315
+ skips it. A refusal of a refusal is the one exception: undoing that
316
+ `undo_refused` row is refused with the same coded error, but writes no second row,
317
+ because the existing row already states the conflict. Every recorded path is
318
+ workspace-relative, never a resolved host path.
316
319
 
317
320
  `keeper file read` writes file content to standard output unchanged (line endings
318
321
  included). Metadata goes to stderr, or into the JSON object with `--json`.
@@ -386,6 +389,13 @@ preview was computed against. The same shape applies to every mutating command a
386
389
  to the MCP tools that mirror them. A committed operation still reports
387
390
  `"changed": true` with its observed `after_hash`.
388
391
 
392
+ `changed` and `would_change` answer different questions: `would_change` asks
393
+ whether the bytes would differ, while `changed` records whether an operation
394
+ executed and was journalled — so a real write of identical content reports
395
+ `"changed": true` and adds a history row, which callers may rely on as proof a
396
+ write completed. In a preview the two cannot disagree: when `would_change` is
397
+ false, every item reports `"unchanged"`.
398
+
389
399
  Editing results add `match_stage` and `match_count` (text replacement),
390
400
  `preview_diff` (dry run), and, for patches, `patch_files` with per-hunk status.
391
401
  A failure that was rolled back reports the restored paths in the error details.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "filekeeper-cli"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "Safe, predictable filesystem operations for humans and AI agents, with atomic writes and workspace confinement."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<3.13"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "filekeeper-cli"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "Safe, predictable filesystem operations for humans and AI agents, with atomic writes and workspace confinement."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<3.13"
@@ -23,7 +23,7 @@ from .core import (
23
23
  Workspace,
24
24
  )
25
25
 
26
- __version__ = "0.1.6"
26
+ __version__ = "0.1.7"
27
27
 
28
28
  __all__ = [
29
29
  "CallerSurface",
@@ -366,7 +366,12 @@ def undo_operation(
366
366
  try:
367
367
  steps = _plan(journal, record)
368
368
  except UndoConflictError as exc:
369
- _record_refusal(journal, record, exc, actor=actor, task_id=task_id)
369
+ # A refusal of another refusal adds nothing: the first row already
370
+ # records that this conflict was seen and nothing changed. Journalling
371
+ # the repeat would be noise, not new evidence, so only a refusal of a
372
+ # real operation is recorded.
373
+ if record.operation_type != UNDO_REFUSED:
374
+ _record_refusal(journal, record, exc, actor=actor, task_id=task_id)
370
375
  raise
371
376
  undo_items = _undo_items(record)
372
377
 
@@ -316,19 +316,22 @@ def as_dry_run(
316
316
  or item size, no operation id and nothing undoable. The prediction a caller
317
317
  may legitimately want survives as ``predicted_after_hash`` on the result,
318
318
  each item and each patch file.
319
+
320
+ Item ``change`` values agree with ``would_change``: a preview that would
321
+ alter nothing reports every item as ``"unchanged"``, so the item list never
322
+ contradicts the operation-level verdict. The item stays in the list, because
323
+ callers use it to map a preview to the paths it inspected.
319
324
  """
320
325
 
321
- return replace(
322
- result,
323
- changed=False,
324
- after_hash=None,
325
- operation_id=None,
326
- undo_available=False,
327
- would_change=would_change,
328
- predicted_after_hash=predicted_after_hash,
329
- items=tuple(
326
+ items: list[OperationItemResult] = []
327
+ for item in result.items:
328
+ change: Literal["created", "modified", "deleted", "moved", "unchanged"] = (
329
+ item.change if would_change else "unchanged"
330
+ )
331
+ items.append(
330
332
  replace(
331
333
  item,
334
+ change=change,
332
335
  after_hash=None,
333
336
  size_bytes=None,
334
337
  predicted_after_hash=(
@@ -337,8 +340,17 @@ def as_dry_run(
337
340
  else item.after_hash
338
341
  ),
339
342
  )
340
- for item in result.items
341
- ),
343
+ )
344
+
345
+ return replace(
346
+ result,
347
+ changed=False,
348
+ after_hash=None,
349
+ operation_id=None,
350
+ undo_available=False,
351
+ would_change=would_change,
352
+ predicted_after_hash=predicted_after_hash,
353
+ items=tuple(items),
342
354
  patch_files=tuple(
343
355
  replace(
344
356
  patch_file,
File without changes