nanobot: 2026-09-11 09:44:11
This commit is contained in:
120
cron/jobs.json
120
cron/jobs.json
@@ -37,8 +37,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nextRunAtMs": 1789106400000,
|
"nextRunAtMs": 1789192800000,
|
||||||
"lastRunAtMs": 1789020000002,
|
"lastRunAtMs": 1789106400001,
|
||||||
"lastStatus": "ok",
|
"lastStatus": "ok",
|
||||||
"lastError": null,
|
"lastError": null,
|
||||||
"runHistory": [
|
"runHistory": [
|
||||||
@@ -47,11 +47,17 @@
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"durationMs": 6281,
|
"durationMs": 6281,
|
||||||
"error": null
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789106400001,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 6997,
|
||||||
|
"error": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"createdAtMs": 1788948314679,
|
"createdAtMs": 1788948314679,
|
||||||
"updatedAtMs": 1789020006283,
|
"updatedAtMs": 1789106406998,
|
||||||
"deleteAfterRun": false
|
"deleteAfterRun": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -78,8 +84,8 @@
|
|||||||
"originMetadata": {}
|
"originMetadata": {}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nextRunAtMs": 1789104981395,
|
"nextRunAtMs": 1789119381693,
|
||||||
"lastRunAtMs": 1789097781385,
|
"lastRunAtMs": 1789112181683,
|
||||||
"lastStatus": "ok",
|
"lastStatus": "ok",
|
||||||
"lastError": null,
|
"lastError": null,
|
||||||
"runHistory": [
|
"runHistory": [
|
||||||
@@ -136,11 +142,23 @@
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"durationMs": 10,
|
"durationMs": 10,
|
||||||
"error": null
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789104981671,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 11,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789112181683,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 10,
|
||||||
|
"error": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"createdAtMs": 1789032980434,
|
"createdAtMs": 1789032980434,
|
||||||
"updatedAtMs": 1789097781395,
|
"updatedAtMs": 1789112181693,
|
||||||
"deleteAfterRun": false
|
"deleteAfterRun": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -167,53 +185,11 @@
|
|||||||
"originMetadata": {}
|
"originMetadata": {}
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"nextRunAtMs": 1789101381496,
|
"nextRunAtMs": 1789113981697,
|
||||||
"lastRunAtMs": 1789099581496,
|
"lastRunAtMs": 1789112181696,
|
||||||
"lastStatus": "ok",
|
"lastStatus": "ok",
|
||||||
"lastError": null,
|
"lastError": null,
|
||||||
"runHistory": [
|
"runHistory": [
|
||||||
{
|
|
||||||
"runAtMs": 1789065381122,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 0,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789067181123,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 1,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789068981125,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 1,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789070781128,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 0,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789072581129,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 1,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789074381270,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 1,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"runAtMs": 1789076181272,
|
|
||||||
"status": "ok",
|
|
||||||
"durationMs": 0,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"runAtMs": 1789077981273,
|
"runAtMs": 1789077981273,
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@@ -291,11 +267,53 @@
|
|||||||
"status": "ok",
|
"status": "ok",
|
||||||
"durationMs": 0,
|
"durationMs": 0,
|
||||||
"error": null
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789101381497,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 1,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789103181499,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 0,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789104981682,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 0,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789106781682,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 1,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789108581684,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 0,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789110381686,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 1,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runAtMs": 1789112181696,
|
||||||
|
"status": "ok",
|
||||||
|
"durationMs": 1,
|
||||||
|
"error": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"createdAtMs": 1789032980437,
|
"createdAtMs": 1789032980437,
|
||||||
"updatedAtMs": 1789099581496,
|
"updatedAtMs": 1789112181697,
|
||||||
"deleteAfterRun": false
|
"deleteAfterRun": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,3 +18,5 @@
|
|||||||
zůstává i po jablku.
|
zůstává i po jablku.
|
||||||
- 2026-09-02: Potřebuje zajít na fyzio terapii — poradit s omezenou mobilitou pravého kyčle a zjistit, proč se pravá noha na rozdíl od levé tak snadno a často přetěžuje. Cíl: cvičit pravidelně, aspoň 2× týdně; protahování ještě častěji.
|
- 2026-09-02: Potřebuje zajít na fyzio terapii — poradit s omezenou mobilitou pravého kyčle a zjistit, proč se pravá noha na rozdíl od levé tak snadno a často přetěžuje. Cíl: cvičit pravidelně, aspoň 2× týdně; protahování ještě častěji.
|
||||||
- 2026-09-11: Byl na fyzio (10.9.2026). Směr: zlepšit mobilitu kyčle a hlavně srovnat pánev, která je nakřivo. Seznam cviků (rekonstrukce z paměti, nahraný si je nebyl) uložen do artifacts/fyzio-cviky-kycele.md. Otevřené otázky k dotázání fyzioterapeuta: počty opakování a délky výdrží u jednotlivých cviků.
|
- 2026-09-11: Byl na fyzio (10.9.2026). Směr: zlepšit mobilitu kyčle a hlavně srovnat pánev, která je nakřivo. Seznam cviků (rekonstrukce z paměti, nahraný si je nebyl) uložen do artifacts/fyzio-cviky-kycele.md. Otevřené otázky k dotázání fyzioterapeuta: počty opakování a délky výdrží u jednotlivých cviků.
|
||||||
|
- 2026-09-11: Cvičil dnes fyzio cviky (poprvé po návštěvě fyzioterapeuta 10.9.2026).
|
||||||
|
- 2026-09-11: Zjistěno z rešerše četnosti fyzio cviků (daily vs obden): u nízkointenzivních mobilitních/protahovacích cviků je dostačující denně i obden — intenzita určuje potřebnou frekvenci, neexistuje potřeba regenerace. Kohortní studie (Musculoskelet. Sci. Pract. 2025, 185 pacientů NLBP): vyšší předepsaná frekvence a dávka signifikantně snižují adherence → obden, který vydrží, je lepší než denní plán, který se rozpadne. Delphi konsensus expertů (2025): efekt protahování na ROM je chronický/kumulativní, vynechaný den nic nezruší. Závěr pro projekt: obden (3–4× týdně) plně dostačující a udržitelnější; denní večerní protahování (bez potu) ponechat jako levný návyk. Zdroje: cor-kinetic.com blog 6/2020, mskscienceandpractice.com S2468-7812(25)00186-9, PMC12305623.
|
||||||
|
|||||||
@@ -52,3 +52,6 @@ Denní vážení, vnímaná kondice, jestli mě něco bolí.
|
|||||||
- Cviky uložené v `artifacts/fyzio-cviky-kycele.md` (rekonstrukce z paměti).
|
- Cviky uložené v `artifacts/fyzio-cviky-kycele.md` (rekonstrukce z paměti).
|
||||||
- **Chybí**: počty opakování a délky výdrží — doladit s fyzioterapeutem při
|
- **Chybí**: počty opakování a délky výdrží — doladit s fyzioterapeutem při
|
||||||
další návštěvě.
|
další návštěvě.
|
||||||
|
- **Frekvence (rešerše 2026-09-11)**: obden (3–4× týdně) plně dostačující —
|
||||||
|
nízkointenzivní mobilitní cviky nepotřebují den regenerace; denní plán
|
||||||
|
navíc adherence reálně nezvládne. Denní jen levné protahování bez potu.
|
||||||
|
|||||||
@@ -89,14 +89,20 @@ dropping it.
|
|||||||
|
|
||||||
## Writing to memory.md
|
## Writing to memory.md
|
||||||
|
|
||||||
**Only ever through the script** — never `edit_file` or `write_file`. Pass the
|
**Only ever through the script** — never `edit_file` or `write_file` on
|
||||||
text on stdin with a quoted heredoc so quotes and apostrophes survive verbatim:
|
`memory.md` itself. Two steps: write the entry text to a scratch file, then hand
|
||||||
|
the script its path.
|
||||||
|
|
||||||
```
|
1. `write_file` the entry text to `tmp/project-entry.md`
|
||||||
uv run skills/project/scripts/project_cli.py log <slug> <<'NOTE'
|
2. `uv run skills/project/scripts/project_cli.py log <slug> --file tmp/project-entry.md`
|
||||||
<entry text>
|
|
||||||
NOTE
|
**Never put the entry text into the command line** — not as an argument, not in a
|
||||||
```
|
heredoc, not through a pipe. The exec safety guard scans the raw command string
|
||||||
|
and misreads ordinary prose as a filesystem path: a colon right after a letter
|
||||||
|
that follows a diacritic parses as a Windows drive, so Czech words like `Cíl:`,
|
||||||
|
`Závěr:` or `směr:` block the whole command with *path outside working dir*. The
|
||||||
|
guard has no shell parser, so quoting and heredocs do not help. A file path in
|
||||||
|
the command is unaffected.
|
||||||
|
|
||||||
The script prepends today's date and guarantees the entry starts on its own
|
The script prepends today's date and guarantees the entry starts on its own
|
||||||
line. Write the entry in the user's language, reformulated, not verbatim.
|
line. Write the entry in the user's language, reformulated, not verbatim.
|
||||||
@@ -168,5 +174,8 @@ prints each project with its file sizes; `(!)` marks an empty `state.md`.
|
|||||||
write project files in the user's language too.
|
write project files in the user's language too.
|
||||||
- Never fabricate project content — `prompt.md`, `memory.md`, and `state.md`
|
- Never fabricate project content — `prompt.md`, `memory.md`, and `state.md`
|
||||||
only grow from what the user actually said or what actually happened.
|
only grow from what the user actually said or what actually happened.
|
||||||
|
- Never write test or throwaway entries to a real project's `memory.md` — it is
|
||||||
|
append-only, so taking them back out means rewriting history. If you genuinely
|
||||||
|
need to try something, run `new <scratch-slug>` and use that.
|
||||||
- Never create a new project without the user's explicit confirmation.
|
- Never create a new project without the user's explicit confirmation.
|
||||||
- Don't force a project context onto an unrelated request.
|
- Don't force a project context onto an unrelated request.
|
||||||
|
|||||||
@@ -124,7 +124,12 @@ def cmd_activate(slug: str) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_log(slug: str, text: str | None) -> int:
|
def cmd_log(slug: str, entry_file: str | None) -> int:
|
||||||
|
"""Append the entry read from `entry_file` (or stdin when None) to memory.md.
|
||||||
|
|
||||||
|
The text is never passed on the command line: the exec safety guard scans the
|
||||||
|
raw command string and misreads prose as a filesystem path (see SKILL.md).
|
||||||
|
"""
|
||||||
directory = project_path(slug)
|
directory = project_path(slug)
|
||||||
if not directory.is_dir():
|
if not directory.is_dir():
|
||||||
slugs = existing_slugs()
|
slugs = existing_slugs()
|
||||||
@@ -132,7 +137,14 @@ def cmd_log(slug: str, text: str | None) -> int:
|
|||||||
print(f"No such project: {slug}. Existing: {listing}", file=sys.stderr)
|
print(f"No such project: {slug}. Existing: {listing}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
body = text if text is not None else sys.stdin.read()
|
if entry_file is None:
|
||||||
|
body = sys.stdin.read()
|
||||||
|
else:
|
||||||
|
source = Path(entry_file)
|
||||||
|
if not source.is_file():
|
||||||
|
print(f"No such file: {entry_file}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
body = source.read_text(encoding="utf-8")
|
||||||
body = body.strip()
|
body = body.strip()
|
||||||
if not body:
|
if not body:
|
||||||
print("Nothing to log (empty input).", file=sys.stderr)
|
print("Nothing to log (empty input).", file=sys.stderr)
|
||||||
@@ -191,7 +203,9 @@ def main() -> int:
|
|||||||
log = sub.add_parser("log", help="Append a dated entry to memory.md")
|
log = sub.add_parser("log", help="Append a dated entry to memory.md")
|
||||||
log.add_argument("slug")
|
log.add_argument("slug")
|
||||||
log.add_argument(
|
log.add_argument(
|
||||||
"--text", default=None, help="Entry text; if omitted, read from stdin"
|
"--file",
|
||||||
|
default=None,
|
||||||
|
help="Path to a file holding the entry text; if omitted, read from stdin",
|
||||||
)
|
)
|
||||||
|
|
||||||
sub.add_parser("list", help="List projects with file sizes")
|
sub.add_parser("list", help="List projects with file sizes")
|
||||||
@@ -203,7 +217,7 @@ def main() -> int:
|
|||||||
if args.command == "activate":
|
if args.command == "activate":
|
||||||
return cmd_activate(args.slug)
|
return cmd_activate(args.slug)
|
||||||
if args.command == "log":
|
if args.command == "log":
|
||||||
return cmd_log(args.slug, args.text)
|
return cmd_log(args.slug, args.file)
|
||||||
if args.command == "list":
|
if args.command == "list":
|
||||||
return cmd_list()
|
return cmd_list()
|
||||||
return cmd_new(args.slug)
|
return cmd_new(args.slug)
|
||||||
|
|||||||
@@ -34,65 +34,117 @@ def today():
|
|||||||
return datetime.now(ZoneInfo("Europe/Prague")).date().isoformat()
|
return datetime.now(ZoneInfo("Europe/Prague")).date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def entry_file(tmp_path):
|
||||||
|
"""Write entry text to a file and return its path, the way the skill does."""
|
||||||
|
|
||||||
|
def write(text):
|
||||||
|
path = tmp_path / "entry.md"
|
||||||
|
path.write_text(text, encoding="utf-8")
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
return write
|
||||||
|
|
||||||
|
|
||||||
# -- log ---------------------------------------------------------------------
|
# -- log ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_log_appends_with_today_date(projects, capsys):
|
def test_log_appends_with_today_date(projects, entry_file, capsys):
|
||||||
directory = make_project(projects, "chata")
|
directory = make_project(projects, "chata")
|
||||||
|
|
||||||
assert project_cli.cmd_log("chata", "Dřevo objednáno") == 0
|
assert project_cli.cmd_log("chata", entry_file("Dřevo objednáno")) == 0
|
||||||
|
|
||||||
memory = (directory / "memory.md").read_text(encoding="utf-8")
|
memory = (directory / "memory.md").read_text(encoding="utf-8")
|
||||||
assert memory == f"- {today()}: Dřevo objednáno\n"
|
assert memory == f"- {today()}: Dřevo objednáno\n"
|
||||||
assert json.loads(capsys.readouterr().out)["appended"].startswith(f"- {today()}:")
|
assert json.loads(capsys.readouterr().out)["appended"].startswith(f"- {today()}:")
|
||||||
|
|
||||||
|
|
||||||
def test_log_does_not_join_when_file_lacks_trailing_newline(projects):
|
def test_log_does_not_join_when_file_lacks_trailing_newline(projects, entry_file):
|
||||||
directory = make_project(projects, "chata", memory="- 2026-09-01: první")
|
directory = make_project(projects, "chata", memory="- 2026-09-01: první")
|
||||||
|
|
||||||
project_cli.cmd_log("chata", "druhý")
|
project_cli.cmd_log("chata", entry_file("druhý"))
|
||||||
|
|
||||||
lines = (directory / "memory.md").read_text(encoding="utf-8").splitlines()
|
lines = (directory / "memory.md").read_text(encoding="utf-8").splitlines()
|
||||||
assert lines == ["- 2026-09-01: první", f"- {today()}: druhý"]
|
assert lines == ["- 2026-09-01: první", f"- {today()}: druhý"]
|
||||||
|
|
||||||
|
|
||||||
def test_log_creates_missing_memory_file(projects):
|
def test_log_creates_missing_memory_file(projects, entry_file):
|
||||||
directory = projects / "chata"
|
directory = projects / "chata"
|
||||||
directory.mkdir()
|
directory.mkdir()
|
||||||
|
|
||||||
assert project_cli.cmd_log("chata", "první") == 0
|
assert project_cli.cmd_log("chata", entry_file("první")) == 0
|
||||||
assert (directory / "memory.md").read_text(encoding="utf-8").endswith("první\n")
|
assert (directory / "memory.md").read_text(encoding="utf-8").endswith("první\n")
|
||||||
|
|
||||||
|
|
||||||
def test_log_reads_stdin_verbatim(projects, monkeypatch):
|
def test_log_reads_file_verbatim(projects, entry_file):
|
||||||
directory = make_project(projects, "chata")
|
directory = make_project(projects, "chata")
|
||||||
text = "Uvozovky „takhle\" a apostrof ' a \"tohle\"\ndruhý řádek"
|
text = "Uvozovky „takhle\" a apostrof ' a \"tohle\"\ndruhý řádek"
|
||||||
monkeypatch.setattr(sys, "stdin", io.StringIO(text))
|
|
||||||
|
|
||||||
assert project_cli.cmd_log("chata", None) == 0
|
assert project_cli.cmd_log("chata", entry_file(text)) == 0
|
||||||
|
|
||||||
memory = (directory / "memory.md").read_text(encoding="utf-8")
|
memory = (directory / "memory.md").read_text(encoding="utf-8")
|
||||||
assert memory == f"- {today()}: {text}\n"
|
assert memory == f"- {today()}: {text}\n"
|
||||||
|
|
||||||
|
|
||||||
def test_log_does_not_shorten_long_entry(projects):
|
def test_log_accepts_text_the_exec_guard_would_reject_on_a_command_line(projects, entry_file):
|
||||||
|
"""Regression: `Cíl:` / `Závěr:` parse as Windows drive paths in the exec guard.
|
||||||
|
|
||||||
|
The guard scans the raw command string, so such text must reach the script
|
||||||
|
through a file, never as an argument, a heredoc or a pipe.
|
||||||
|
"""
|
||||||
|
directory = make_project(projects, "chata")
|
||||||
|
text = "Cíl: srovnat pánev. Diagnóza/směr: mobilita kyčle. Závěr: pokračovat."
|
||||||
|
|
||||||
|
assert project_cli.cmd_log("chata", entry_file(text)) == 0
|
||||||
|
assert text in (directory / "memory.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_reads_stdin_when_no_file_given(projects, monkeypatch):
|
||||||
|
directory = make_project(projects, "chata")
|
||||||
|
monkeypatch.setattr(sys, "stdin", io.StringIO("ze stdinu"))
|
||||||
|
|
||||||
|
assert project_cli.cmd_log("chata", None) == 0
|
||||||
|
|
||||||
|
assert (directory / "memory.md").read_text(encoding="utf-8") == f"- {today()}: ze stdinu\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_rejects_missing_file_without_touching_memory(projects, tmp_path, capsys):
|
||||||
|
directory = make_project(projects, "chata", memory="- 2026-09-01: první\n")
|
||||||
|
|
||||||
|
assert project_cli.cmd_log("chata", str(tmp_path / "chybi.md")) == 1
|
||||||
|
|
||||||
|
assert "No such file" in capsys.readouterr().err
|
||||||
|
assert (directory / "memory.md").read_text(encoding="utf-8") == "- 2026-09-01: první\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_does_not_shorten_long_entry(projects, entry_file):
|
||||||
directory = make_project(projects, "chata")
|
directory = make_project(projects, "chata")
|
||||||
text = "x" * 3000
|
text = "x" * 3000
|
||||||
|
|
||||||
project_cli.cmd_log("chata", text)
|
project_cli.cmd_log("chata", entry_file(text))
|
||||||
|
|
||||||
assert text in (directory / "memory.md").read_text(encoding="utf-8")
|
assert text in (directory / "memory.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_log_rejects_empty_input(projects):
|
def test_log_rejects_empty_input(projects, entry_file):
|
||||||
directory = make_project(projects, "chata", memory="- 2026-09-01: první\n")
|
directory = make_project(projects, "chata", memory="- 2026-09-01: první\n")
|
||||||
|
|
||||||
assert project_cli.cmd_log("chata", " ") == 1
|
assert project_cli.cmd_log("chata", entry_file(" ")) == 1
|
||||||
assert (directory / "memory.md").read_text(encoding="utf-8") == "- 2026-09-01: první\n"
|
assert (directory / "memory.md").read_text(encoding="utf-8") == "- 2026-09-01: první\n"
|
||||||
|
|
||||||
|
|
||||||
def test_log_rejects_unknown_project(projects):
|
def test_log_rejects_unknown_project(projects, entry_file):
|
||||||
assert project_cli.cmd_log("neznamy", "text") == 1
|
assert project_cli.cmd_log("neznamy", entry_file("text")) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_no_longer_accepts_text_on_the_command_line(monkeypatch):
|
||||||
|
"""`--text` is gone for good: prose in argv is what the exec guard blocks."""
|
||||||
|
monkeypatch.setattr(sys, "argv", ["project_cli.py", "log", "chata", "--text", "něco"])
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
|
project_cli.main()
|
||||||
|
|
||||||
|
assert excinfo.value.code == 2
|
||||||
|
|
||||||
|
|
||||||
# -- activate ----------------------------------------------------------------
|
# -- activate ----------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user