feat(reflect): --if-stale to skip redundant runs; agent uses it at session start

Both the agent (session start) and the post-commit hook can run reflect; the runs
are deterministic and idempotent, but back-to-back ones are wasted work. Add
`graphify reflect --if-stale`, which no-ops when LESSONS.md is already at least as
new as every input (the memory docs and the graph). The skill's session-start
guidance now uses `--if-stale`, so when the hook just refreshed the file the
agent's run costs almost nothing, while a skill-only install still refreshes
on demand.

New lessons_fresh() helper + 5 tests (mtime freshness in each direction, and the
CLI skip/run behavior). Regenerated per-host references + re-blessed expected/;
all five skillgen guards pass; full suite 2388 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
safishamsi
2026-06-24 12:26:39 +01:00
co-authored by Claude Opus 4.8
parent c06db05351
commit d193b6277d
33 changed files with 153 additions and 46 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## Unreleased
- Fix: the work-memory loop no longer depends on the git hook. The skill now tells the agent to run `graphify reflect` itself at the start of graph work (it is cheap, deterministic, and a no-op when no outcomes have been saved), then read `LESSONS.md`. Previously a skill-only install (without `graphify hook install`) would keep recording outcomes via `save-result` but never regenerate `LESSONS.md`, so the lessons never surfaced. The post-commit hook is now an optimization for between-session freshness rather than a requirement.
- Fix: the work-memory loop no longer depends on the git hook. The skill now tells the agent to run `graphify reflect --if-stale` itself at the start of graph work (cheap, deterministic, a no-op when no outcomes have been saved), then read `LESSONS.md`. Previously a skill-only install (without `graphify hook install`) would keep recording outcomes via `save-result` but never regenerate `LESSONS.md`, so the lessons never surfaced. The post-commit hook is now an optimization for between-session freshness rather than a requirement. The new `--if-stale` flag skips the run when `LESSONS.md` is already newer than every input (the memory docs and the graph), so when the hook just refreshed it the agent's session-start run costs almost nothing.
## 0.8.47 (2026-06-24)
+23 -16
View File
@@ -2957,8 +2957,11 @@ def main() -> None:
help="signal weight halves every N days (default 30)")
p.add_argument("--min-corroboration", type=int, default=2,
help="distinct useful results to promote a node to preferred (default 2)")
p.add_argument("--if-stale", action="store_true",
help="skip when LESSONS.md is already newer than every input "
"(e.g. the git hook just refreshed it)")
opts = p.parse_args(sys.argv[2:])
from graphify.reflect import reflect as _reflect
from graphify.reflect import reflect as _reflect, lessons_fresh as _lessons_fresh
graph_arg = opts.graph
if graph_arg is None:
@@ -2966,21 +2969,25 @@ def main() -> None:
if default_graph.exists():
graph_arg = str(default_graph)
out_path, agg = _reflect(
memory_dir=Path(opts.memory_dir),
out_path=Path(opts.out),
graph_path=Path(graph_arg) if graph_arg else None,
analysis_path=Path(opts.analysis) if opts.analysis else None,
labels_path=Path(opts.labels) if opts.labels else None,
half_life_days=opts.half_life_days,
min_corroboration=opts.min_corroboration,
)
c = agg["counts"]
print(
f"Reflected {agg['total']} memories "
f"({c['useful']} useful, {c['dead_end']} dead ends, "
f"{c['corrected']} corrected) -> {out_path}"
)
_gp = Path(graph_arg) if graph_arg else None
if opts.if_stale and _lessons_fresh(Path(opts.out), Path(opts.memory_dir), _gp):
print(f"Lessons already up to date -> {opts.out} (skipped; omit --if-stale to force)")
else:
out_path, agg = _reflect(
memory_dir=Path(opts.memory_dir),
out_path=Path(opts.out),
graph_path=_gp,
analysis_path=Path(opts.analysis) if opts.analysis else None,
labels_path=Path(opts.labels) if opts.labels else None,
half_life_days=opts.half_life_days,
min_corroboration=opts.min_corroboration,
)
c = agg["counts"]
print(
f"Reflected {agg['total']} memories "
f"({c['useful']} useful, {c['dead_end']} dead ends, "
f"{c['corrected']} corrected) -> {out_path}"
)
elif cmd == "path":
if len(sys.argv) < 4:
print(
+33
View File
@@ -485,6 +485,39 @@ def render_lessons_md(agg: dict[str, Any]) -> str:
# --- orchestrator --------------------------------------------------------------
def lessons_fresh(out_path: Path, memory_dir: Path,
graph_path: Path | None = None) -> bool:
"""True if ``out_path`` exists and is at least as new as every input that
feeds it (the memory docs, and the graph when one is used).
Lets ``graphify reflect --if-stale`` skip a redundant run — e.g. when the git
post-commit hook just regenerated ``LESSONS.md`` and an agent then runs reflect
again at the start of a session. A missing output is never fresh (it must be
built). Mtime-based and best-effort; it only gates whether to *recompute*, not
what the recomputation produces (that stays deterministic).
"""
out_path = Path(out_path)
try:
out_mtime = out_path.stat().st_mtime
except OSError:
return False # missing/unreadable -> must build
newest = 0.0
md = Path(memory_dir)
if md.is_dir():
for f in md.glob("*.md"):
try:
newest = max(newest, f.stat().st_mtime)
except OSError:
pass
if graph_path is not None:
gp = Path(graph_path)
try:
newest = max(newest, gp.stat().st_mtime)
except OSError:
pass
return out_mtime >= newest
def reflect(memory_dir: Path, out_path: Path,
graph_path: Path | None = None,
analysis_path: Path | None = None,
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+1 -1
View File
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
+67
View File
@@ -22,6 +22,7 @@ import pytest
from graphify.ingest import save_query_result
from graphify.reflect import (
aggregate_lessons,
lessons_fresh,
load_memory_docs,
parse_memory_doc,
reflect,
@@ -564,3 +565,69 @@ def _make_graph(tmp_path: Path) -> Path:
json.dumps({str(cid): f"Community {cid}" for cid in communities})
)
return out
# --- lessons_fresh / `reflect --if-stale` -------------------------------------
def test_lessons_fresh_missing_output_is_not_fresh(tmp_path):
mem = tmp_path / "memory"; mem.mkdir()
(mem / "q.md").write_text("x", encoding="utf-8")
assert lessons_fresh(tmp_path / "LESSONS.md", mem) is False # must build
def test_lessons_fresh_true_when_output_newer_than_inputs(tmp_path):
import os
mem = tmp_path / "memory"; mem.mkdir()
doc = mem / "q.md"; doc.write_text("x", encoding="utf-8")
out = tmp_path / "LESSONS.md"; out.write_text("y", encoding="utf-8")
os.utime(doc, (1000, 1000))
os.utime(out, (2000, 2000))
assert lessons_fresh(out, mem) is True
def test_lessons_fresh_false_when_memory_newer(tmp_path):
import os
mem = tmp_path / "memory"; mem.mkdir()
doc = mem / "q.md"; doc.write_text("x", encoding="utf-8")
out = tmp_path / "LESSONS.md"; out.write_text("y", encoding="utf-8")
os.utime(out, (1000, 1000))
os.utime(doc, (2000, 2000)) # a new outcome was saved after the last reflect
assert lessons_fresh(out, mem) is False
def test_lessons_fresh_false_when_graph_newer(tmp_path):
import os
mem = tmp_path / "memory"; mem.mkdir()
(mem / "q.md").write_text("x", encoding="utf-8")
out = tmp_path / "LESSONS.md"; out.write_text("y", encoding="utf-8")
graph = tmp_path / "graph.json"; graph.write_text("{}", encoding="utf-8")
os.utime(mem / "q.md", (1000, 1000))
os.utime(out, (1500, 1500))
os.utime(graph, (2000, 2000)) # graph rebuilt since last reflect -> stale
assert lessons_fresh(out, mem, graph) is False
def test_cli_reflect_if_stale_skips_when_fresh(tmp_path):
"""`reflect --if-stale` skips the rebuild when LESSONS.md is already current,
and still runs when a new outcome arrives."""
out = _make_graph(tmp_path)
real = json.loads((out / "graph.json").read_text())["nodes"][0]["label"]
_run(["save-result", "--question", "q", "--answer", "a",
"--nodes", real, "--outcome", "useful"], tmp_path)
first = _run(["reflect"], tmp_path)
assert first.returncode == 0
lessons = out / "reflections" / "LESSONS.md"
body_before = lessons.read_text(encoding="utf-8")
# Second call with --if-stale: nothing changed -> skipped, file untouched.
skipped = _run(["reflect", "--if-stale"], tmp_path)
assert skipped.returncode == 0
assert "up to date" in (skipped.stdout + skipped.stderr).lower()
assert lessons.read_text(encoding="utf-8") == body_before
# A new outcome makes it stale -> --if-stale runs again.
_run(["save-result", "--question", "q2", "--answer", "a",
"--nodes", real, "--outcome", "useful"], tmp_path)
ran = _run(["reflect", "--if-stale"], tmp_path)
assert ran.returncode == 0
assert "up to date" not in (ran.stdout + ran.stderr).lower()
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---
@@ -179,7 +179,7 @@ Replace `ORIGINAL_QUESTION` with the user's verbatim question, `ANSWER` with you
- `dead_end` — the question/path led nowhere; don't re-derive it next time.
- `corrected` — the saved answer was wrong; `--correction` records what was right.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect` (cheap, deterministic, no LLM, and a no-op when no outcomes have been saved yet), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself means the lessons stay current even without the git hook installed; if the post-commit hook *is* installed, it also refreshes the file automatically after each rebuild.
At the **start** of graph work, refresh and read the lessons: run `graphify reflect --if-stale` (cheap, deterministic, no LLM; `--if-stale` makes it a no-op when `LESSONS.md` is already newer than every input, e.g. when the git hook just refreshed it), then read `graphify-out/reflections/LESSONS.md`. It lists **preferred sources** (start there), **known dead ends** (skip them), and prior **corrections**. Running `reflect` yourself keeps the lessons current even without the git hook installed; if the post-commit hook *is* installed, `--if-stale` means your session-start run costs almost nothing.
---