personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(journal-skill): delete 003_seed_agents_md maint task and tests

Remove the journal seeding maint task now that the journal template is committed,
update the root agent guidance, drop the dead rename-gate allowlist, and replace
seeder coverage with journal skill/template tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+111 -167
+1 -5
AGENTS.md
··· 1 1 # solstone Developer Guide 2 2 3 - This is the developer-facing documentation for the solstone codebase. If you're an AI agent working **inside a journal**, read the journal's own `AGENTS.md` instead — it's seeded from `docs/JOURNAL.md` and tells you about the journal layout. 3 + This is the developer-facing documentation for the solstone codebase. If you're an AI agent working **inside a journal**, read the journal template at `journal/AGENTS.md`; journal-side agents discover the full layout and CLI reference via `.claude/skills/journal/` or `.agents/skills/journal/`. 4 4 5 5 ## Key Concepts 6 6 ··· 63 63 - `docs/coding-standards.md` 64 64 - `docs/testing.md` 65 65 - `docs/environment.md` 66 - 67 - ## Known limitations 68 - 69 - - Per-journal `AGENTS.md` files are seeded once at journal init by `apps/sol/maint/003_seed_agents_md.py`. They are not automatically refreshed if `docs/JOURNAL.md` changes upstream. Re-seed manually by deleting the journal's `AGENTS.md` and restarting the supervisor.
-76
apps/sol/maint/003_seed_agents_md.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Seed per-journal AGENTS.md, CLAUDE.md, and GEMINI.md files.""" 5 - 6 - from __future__ import annotations 7 - 8 - import argparse 9 - import sys 10 - from pathlib import Path 11 - 12 - from think.utils import get_journal, setup_cli 13 - 14 - 15 - def _symlink_points_to_agents(path: Path) -> bool: 16 - return path.is_symlink() and path.readlink() == Path("AGENTS.md") 17 - 18 - 19 - def _repair_symlink(path: Path) -> int: 20 - if path.exists() and not path.is_symlink(): 21 - print(f"refusing to replace existing non-symlink {path.name}", file=sys.stderr) 22 - return 1 23 - if path.is_symlink(): 24 - path.unlink() 25 - action = "replaced" 26 - else: 27 - action = "created" 28 - path.symlink_to("AGENTS.md") 29 - print(f"{action} {path.name}") 30 - return 0 31 - 32 - 33 - # AGENTS.md is derived from docs/JOURNAL.md. Drift-detection: direct content 34 - # compare. Hand-edit policy: overwrite on mismatch with stderr warning 35 - # (prior content recoverable from git history). 36 - def main() -> int: 37 - parser = argparse.ArgumentParser(description="Seed journal AGENTS.md symlinks.") 38 - setup_cli(parser) 39 - 40 - journal = Path(get_journal()) 41 - repo_root = Path(__file__).resolve().parents[3] 42 - 43 - agents_path = journal / "AGENTS.md" 44 - claude_path = journal / "CLAUDE.md" 45 - gemini_path = journal / "GEMINI.md" 46 - 47 - try: 48 - journal_md = (repo_root / "docs" / "JOURNAL.md").read_text(encoding="utf-8") 49 - 50 - if not agents_path.exists(): 51 - agents_path.write_text(journal_md, encoding="utf-8") 52 - print("created AGENTS.md") 53 - elif agents_path.read_text(encoding="utf-8") != journal_md: 54 - agents_path.write_text(journal_md, encoding="utf-8") 55 - print( 56 - "AGENTS.md content differed from docs/JOURNAL.md; refreshing " 57 - "(previous content preserved in git history)", 58 - file=sys.stderr, 59 - ) 60 - print("refreshed AGENTS.md") 61 - 62 - for path in (claude_path, gemini_path): 63 - if _symlink_points_to_agents(path): 64 - continue 65 - status = _repair_symlink(path) 66 - if status != 0: 67 - return status 68 - except OSError as exc: 69 - print(f"failed to seed journal agent files: {exc}", file=sys.stderr) 70 - return 1 71 - 72 - return 0 73 - 74 - 75 - if __name__ == "__main__": 76 - raise SystemExit(main())
-2
scripts/gate_agents_rename.py
··· 61 61 return True 62 62 if path == Path("scripts/gate_agents_rename.py"): 63 63 return True 64 - if path_str.startswith(".agents/skills/"): 65 - return True 66 64 if ALLOWLIST_RE.match(path_str): 67 65 return True 68 66 return False
+1 -1
tests/test_generate_talents.py
··· 12 12 assert content.startswith("# solstone Developer Guide") 13 13 assert "generated from sol/identity.md" not in content 14 14 assert "docs/project-structure.md" in content 15 - assert "003_seed_agents_md.py" in content 15 + assert "journal/AGENTS.md" in content 16 16 17 17 18 18 def test_root_agent_symlinks_point_to_agents():
-83
tests/test_journal_seeding_maint.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - import json 5 - import os 6 - import subprocess 7 - import sys 8 - from pathlib import Path 9 - 10 - import pytest 11 - 12 - 13 - @pytest.fixture 14 - def journal_path(tmp_path, monkeypatch): 15 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 16 - config_dir = tmp_path / "config" 17 - config_dir.mkdir() 18 - (config_dir / "journal.json").write_text(json.dumps({})) 19 - return tmp_path 20 - 21 - 22 - def _run_main(journal_path): 23 - env = os.environ.copy() 24 - env["_SOLSTONE_JOURNAL_OVERRIDE"] = str(journal_path) 25 - return subprocess.run( 26 - [sys.executable, "-m", "apps.sol.maint.003_seed_agents_md"], 27 - capture_output=True, 28 - text=True, 29 - check=False, 30 - env=env, 31 - ) 32 - 33 - 34 - def test_seed_agents_md_creates_all_files(journal_path): 35 - docs_text = Path("docs/JOURNAL.md").read_text(encoding="utf-8") 36 - 37 - result = _run_main(journal_path) 38 - 39 - assert result.returncode == 0 40 - agents_path = journal_path / "AGENTS.md" 41 - claude_path = journal_path / "CLAUDE.md" 42 - gemini_path = journal_path / "GEMINI.md" 43 - assert agents_path.read_text(encoding="utf-8") == docs_text 44 - assert claude_path.is_symlink() 45 - assert claude_path.readlink() == Path("AGENTS.md") 46 - assert gemini_path.is_symlink() 47 - assert gemini_path.readlink() == Path("AGENTS.md") 48 - 49 - 50 - def test_seed_agents_md_is_noop_when_already_seeded(journal_path): 51 - docs_text = Path("docs/JOURNAL.md").read_text(encoding="utf-8") 52 - agents_path = journal_path / "AGENTS.md" 53 - agents_path.write_text(docs_text, encoding="utf-8") 54 - (journal_path / "CLAUDE.md").symlink_to("AGENTS.md") 55 - (journal_path / "GEMINI.md").symlink_to("AGENTS.md") 56 - before = { 57 - "agents": agents_path.stat().st_mtime_ns, 58 - "claude": (journal_path / "CLAUDE.md").lstat().st_mtime_ns, 59 - "gemini": (journal_path / "GEMINI.md").lstat().st_mtime_ns, 60 - } 61 - 62 - result = _run_main(journal_path) 63 - 64 - after = { 65 - "agents": agents_path.stat().st_mtime_ns, 66 - "claude": (journal_path / "CLAUDE.md").lstat().st_mtime_ns, 67 - "gemini": (journal_path / "GEMINI.md").lstat().st_mtime_ns, 68 - } 69 - assert result.returncode == 0 70 - assert before == after 71 - 72 - 73 - def test_seed_agents_md_refreshes_on_drift(journal_path): 74 - agents_path = journal_path / "AGENTS.md" 75 - agents_path.write_text("stale content", encoding="utf-8") 76 - (journal_path / "CLAUDE.md").symlink_to("AGENTS.md") 77 - (journal_path / "GEMINI.md").symlink_to("AGENTS.md") 78 - docs_text = Path("docs/JOURNAL.md").read_text(encoding="utf-8") 79 - 80 - result = _run_main(journal_path) 81 - 82 - assert result.returncode == 0 83 - assert agents_path.read_text(encoding="utf-8") == docs_text
+109
tests/test_journal_skill.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + from __future__ import annotations 5 + 6 + import shutil 7 + import subprocess 8 + from pathlib import Path 9 + 10 + 11 + def _repo_root() -> Path: 12 + return Path(__file__).resolve().parent.parent 13 + 14 + 15 + def _assert_inside_repo(path: Path, repo_root: Path) -> None: 16 + resolved = path.resolve() 17 + assert resolved.is_relative_to(repo_root) 18 + 19 + 20 + def _tracked_symlinks(*roots: str) -> list[Path]: 21 + repo_root = _repo_root() 22 + result = subprocess.run( 23 + ["git", "ls-files", *roots], 24 + cwd=repo_root, 25 + check=True, 26 + capture_output=True, 27 + text=True, 28 + ) 29 + return [ 30 + repo_root / line 31 + for line in result.stdout.splitlines() 32 + if line and (repo_root / line).is_symlink() 33 + ] 34 + 35 + 36 + def test_journal_skill_references_exist_and_linked(): 37 + repo_root = _repo_root() 38 + skill_path = repo_root / "talent" / "journal" / "SKILL.md" 39 + skill_text = skill_path.read_text(encoding="utf-8") 40 + references = [ 41 + "references/cli.md", 42 + "references/config.md", 43 + "references/facets.md", 44 + "references/captures.md", 45 + "references/logs.md", 46 + "references/storage.md", 47 + ] 48 + 49 + for rel_path in references: 50 + ref_path = skill_path.parent / rel_path 51 + assert ref_path.exists() 52 + assert ref_path.read_text(encoding="utf-8").strip() 53 + assert rel_path in skill_text 54 + 55 + 56 + def test_journal_template_symlinks_resolve_inside_repo(): 57 + repo_root = _repo_root() 58 + for path in _tracked_symlinks("journal", "tests/fixtures/journal"): 59 + _assert_inside_repo(path, repo_root) 60 + 61 + 62 + def test_make_skills_idempotent(tmp_path): 63 + repo_root = _repo_root() 64 + temp_root = tmp_path / "repo" 65 + temp_root.mkdir() 66 + 67 + shutil.copy2(repo_root / "Makefile", temp_root / "Makefile") 68 + shutil.copytree(repo_root / "talent", temp_root / "talent", symlinks=True) 69 + shutil.copytree(repo_root / "apps", temp_root / "apps", symlinks=True) 70 + shutil.copytree(repo_root / "journal", temp_root / "journal", symlinks=True) 71 + 72 + subprocess.run( 73 + ["make", "skills"], 74 + cwd=temp_root, 75 + check=True, 76 + capture_output=True, 77 + text=True, 78 + ) 79 + 80 + def link_state(root: Path) -> dict[str, tuple[str, int]]: 81 + return { 82 + path.relative_to(root).as_posix(): ( 83 + path.readlink().as_posix(), 84 + path.lstat().st_mtime_ns, 85 + ) 86 + for path in sorted(root.rglob("*")) 87 + if path.is_symlink() 88 + } 89 + 90 + first = link_state(temp_root / "journal") 91 + 92 + subprocess.run( 93 + ["make", "skills"], 94 + cwd=temp_root, 95 + check=True, 96 + capture_output=True, 97 + text=True, 98 + ) 99 + 100 + second = link_state(temp_root / "journal") 101 + assert first == second 102 + 103 + 104 + def test_skill_discovery_from_journal_cwd(): 105 + repo_root = _repo_root() 106 + skill_path = repo_root / "journal" / ".claude" / "skills" / "journal" / "SKILL.md" 107 + 108 + assert skill_path.is_file() 109 + assert skill_path.read_text(encoding="utf-8").startswith("---")