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
31 changes: 25 additions & 6 deletions stacklets/memory/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@
)
BRAIN_SEED_COMMIT_MESSAGE = "seed: initial brain projection scaffold"
BRAIN_MIGRATION_TOKEN_NAME = "memory-brain-migration"
GENERATED_PAGE_MARKER = "<!-- begin: generated -->"
ONTOLOGY_PATH_IN_REPO = "ontology.toml"
INSTALL_COMMIT_MESSAGE = "seed: initial memory from famstack {version}"

Expand Down Expand Up @@ -1227,6 +1226,25 @@ def seed_brain(
return {"created": created, "skipped": skipped}


def _declares_generated(content: str) -> bool:
"""True when a page's frontmatter declares the whole file generated.

The splice marker only brackets a *region*, and a hand-written page may
reserve one for the wiki to fill in later — the seeded root `index.md`
does exactly that. Treating the marker as proof of a generated page
deleted that welcome page on every start, which left a fresh install
serving 404 at the wiki root until the first nightly sweep.

`generated: true` is the whole-page declaration, and the generator
stamps it on every page it publishes (`_with_generated_marker`), so
every real projection artifact still matches.
"""
value = _parse_frontmatter(content).get("generated")
if value is True:
return True
return isinstance(value, str) and value.strip().lower() == "true"


def purge_generated_memory_pages(
client: ForgejoClient,
*,
Expand All @@ -1236,9 +1254,10 @@ def purge_generated_memory_pages(

B1 moves generation to `family/brain`, but upgraded instances can
already have generated wiki pages in the source repo from pre-B1
runs. The migration is marker-based so it does not depend on stale
path conventions: any markdown file with the generated splice marker
is a projection artifact and is removed from memory.
runs. The migration keys off the page's own frontmatter rather than
stale path conventions: any markdown file declaring `generated: true`
is a projection artifact and is removed from memory. See
`_declares_generated` for why the splice marker alone is not enough.
"""
deleted: list[str] = []
tree = client.list_tree(REPO_OWNER, REPO_NAME)
Expand All @@ -1252,7 +1271,7 @@ def purge_generated_memory_pages(
if not existing:
continue
content = existing.get("content", "")
if GENERATED_PAGE_MARKER not in content:
if not _declares_generated(content):
continue
client.delete_file(
REPO_OWNER, REPO_NAME, path,
Expand Down Expand Up @@ -1281,7 +1300,7 @@ def purge_local_generated_memory_pages(vault_path: Path) -> dict:
content = md.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
if GENERATED_PAGE_MARKER not in content:
if not _declares_generated(content):
continue
try:
rel = str(md.relative_to(vault_path))
Expand Down
11 changes: 10 additions & 1 deletion stacklets/memory/seeds/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ To add or edit a page, open the **family/memory** repository in Forgejo.
Every change is saved as a version you can always look back on.

<!-- begin: generated -->
<!-- Your family overview appears here once you run `stack memory wiki`. -->
## Your family overview

Nothing has been summarised yet. The wiki writes this section for you from
the documents you file and the notes you save, and refreshes it every night.

To build it now instead of waiting, run:

```sh
stack memory wiki
```
<!-- end: generated -->

30 changes: 27 additions & 3 deletions tests/stacklets/test_memory_install_seeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def test_deletes_generated_markdown_from_memory(self, httpserver):
method="GET",
).respond_with_json(_file_response(
"index.md",
"---\ntitle: Home\n---\n<!-- begin: generated -->\nbody\n<!-- end: generated -->\n",
"---\ntitle: Home\ngenerated: true\n---\n<!-- begin: generated -->\nbody\n<!-- end: generated -->\n",
))
httpserver.expect_request(
f"/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/contents/index.md",
Expand All @@ -393,7 +393,7 @@ def test_deletes_generated_markdown_from_memory(self, httpserver):
method="GET",
).respond_with_json(_file_response(
"homer/about.md",
"---\ntitle: Homer\n---\n<!-- begin: generated -->\nbody\n<!-- end: generated -->\n",
"---\ntitle: Homer\ngenerated: true\n---\n<!-- begin: generated -->\nbody\n<!-- end: generated -->\n",
))
httpserver.expect_request(
f"/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/contents/homer/about.md",
Expand All @@ -419,7 +419,7 @@ def test_deletes_generated_markdown_but_keeps_readme_and_sources(self, tmp_path)
generated = tmp_path / "homer" / "about.md"
generated.parent.mkdir(parents=True)
generated.write_text(
"---\ntitle: Homer\n---\n<!-- begin: generated -->\nbody\n",
"---\ntitle: Homer\ngenerated: true\n---\n<!-- begin: generated -->\nbody\n",
encoding="utf-8",
)
readme = tmp_path / "family" / "correspondents" / "README.md"
Expand All @@ -435,3 +435,27 @@ def test_deletes_generated_markdown_but_keeps_readme_and_sources(self, tmp_path)
assert not generated.exists()
assert readme.exists()
assert source.exists()

def test_keeps_a_seeded_page_that_merely_reserves_a_generated_region(
self, tmp_path,
):
"""The seeded root `index.md` is hand-written: a welcome page that
reserves an empty generated region for the wiki to fill in later.

It is not a projection artifact, and deleting it is what left a
fresh install serving 404 at the wiki root until the first nightly
sweep. Only frontmatter saying `generated: true` marks a whole page
as generated, and the generator writes that on every page it
publishes.
"""
seeded = tmp_path / "index.md"
seeded.write_text(
"---\ntitle: Family Memory\n---\n\nWelcome to your family's memory.\n"
"<!-- begin: generated -->\n<!-- end: generated -->\n",
encoding="utf-8",
)

result = purge_local_generated_memory_pages(tmp_path)

assert result == {"deleted": []}
assert seeded.exists()
Loading