From 552e8da8c7a13b58834c20cc90fc9f725eda2d20 Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 12:46:54 +0100 Subject: [PATCH 1/6] feat: add fleet taidev/taiarch knowledge stack alongside existing .terraphim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the in-repo .terraphim layout (Rust Engineer + kg//) with fleet-standard §8 roles without replacing them: - Terraphim AI Developer (taidev) + Architect (taiarch) - Domain KG concepts under kg-taidev/ and kg-taiarch/ - Committed thesauri + skills.toml (tsm names verified 2026-08-08) - bootstrap.py materializes {REPO} placeholders and refreshes fleet thesauri - memory/ repo-local agent notes Config stays portable: new roles use {REPO} haystacks/KG paths; existing Rust Engineer keeps relative paths. Default role unchanged (Rust Engineer). Refs fleet-standard §8 --- .terraphim/README.md | 19 ++ .terraphim/config.json | 111 +++++++- .terraphim/kg-taiarch/knowledge-graph.md | 5 + .terraphim/kg-taiarch/privacy.md | 5 + .terraphim/kg-taiarch/relevance.md | 5 + .terraphim/kg-taiarch/role.md | 5 + .terraphim/kg-taiarch/search-pipeline.md | 5 + .terraphim/kg-taidev/agent.md | 5 + .terraphim/kg-taidev/automata.md | 5 + .terraphim/kg-taidev/config.md | 5 + .terraphim/kg-taidev/haystack.md | 5 + .terraphim/kg-taidev/hooks.md | 5 + .terraphim/kg-taidev/mcp.md | 5 + .terraphim/kg-taidev/orchestrator.md | 5 + .terraphim/kg-taidev/persistence.md | 5 + .terraphim/kg-taidev/rolegraph.md | 5 + .terraphim/kg-taidev/session.md | 5 + .terraphim/scripts/bootstrap.py | 78 ++++++ .terraphim/skills.toml | 28 ++ .terraphim/thesaurus-taiarch.json | 129 +++++++++ .terraphim/thesaurus-taidev.json | 325 +++++++++++++++++++++++ memory/README.md | 10 + 22 files changed, 773 insertions(+), 2 deletions(-) create mode 100644 .terraphim/kg-taiarch/knowledge-graph.md create mode 100644 .terraphim/kg-taiarch/privacy.md create mode 100644 .terraphim/kg-taiarch/relevance.md create mode 100644 .terraphim/kg-taiarch/role.md create mode 100644 .terraphim/kg-taiarch/search-pipeline.md create mode 100644 .terraphim/kg-taidev/agent.md create mode 100644 .terraphim/kg-taidev/automata.md create mode 100644 .terraphim/kg-taidev/config.md create mode 100644 .terraphim/kg-taidev/haystack.md create mode 100644 .terraphim/kg-taidev/hooks.md create mode 100644 .terraphim/kg-taidev/mcp.md create mode 100644 .terraphim/kg-taidev/orchestrator.md create mode 100644 .terraphim/kg-taidev/persistence.md create mode 100644 .terraphim/kg-taidev/rolegraph.md create mode 100644 .terraphim/kg-taidev/session.md create mode 100755 .terraphim/scripts/bootstrap.py create mode 100644 .terraphim/skills.toml create mode 100644 .terraphim/thesaurus-taiarch.json create mode 100644 .terraphim/thesaurus-taidev.json create mode 100644 memory/README.md diff --git a/.terraphim/README.md b/.terraphim/README.md index b6b56f95e..cdd19384a 100644 --- a/.terraphim/README.md +++ b/.terraphim/README.md @@ -89,3 +89,22 @@ When adding new concepts: 2. Include synonyms using the `synonyms::` directive 3. Regenerate the thesaurus JSON 4. Test with `terraphim-grep --role {role} --answer "your query"` + + +## Fleet roles (added 2026-08-08) + +Additional Terraphim-AI domain roles (fleet standard §8): + +| Role | shortname | KG | +|------|-----------|-----| +| Terraphim AI Developer | `taidev` | `.terraphim/kg-taidev/` | +| Terraphim AI Architect | `taiarch` | `.terraphim/kg-taiarch/` | + +These coexist with the existing `Rust Engineer` / `kg//` layout. + +```bash +python3 .terraphim/scripts/bootstrap.py # materialize {REPO} + refresh thesauri +``` + +Skills manifest: `.terraphim/skills.toml` (`tsm install `). +Repo agent memory: `memory/` at repo root. diff --git a/.terraphim/config.json b/.terraphim/config.json index 39ad5cb06..558d11319 100644 --- a/.terraphim/config.json +++ b/.terraphim/config.json @@ -31,6 +31,113 @@ "llm_provider": "ollama", "ollama_model": "llama3.2:3b", "ollama_base_url": "http://127.0.0.1:11434" + }, + "Terraphim AI Developer": { + "shortname": "taidev", + "name": "Terraphim AI Developer", + "relevance_function": "terraphim-graph", + "terraphim_it": true, + "theme": "united", + "kg": { + "automata_path": null, + "knowledge_graph_local": { + "input_type": "markdown", + "path": "{REPO}/.terraphim/kg-taidev" + }, + "public": false, + "publish": false + }, + "haystacks": [ + { + "location": "{REPO}", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + }, + { + "location": "{REPO}/crates", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + }, + { + "location": "{REPO}/memory", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + } + ], + "llm_enabled": false, + "llm_provider": "ollama", + "ollama_model": "llama3.2:3b", + "ollama_base_url": "http://127.0.0.1:11434", + "llm_router_enabled": false, + "llm_context_window": 32768, + "extra": {} + }, + "Terraphim AI Architect": { + "shortname": "taiarch", + "name": "Terraphim AI Architect", + "relevance_function": "terraphim-graph", + "terraphim_it": true, + "theme": "united", + "kg": { + "automata_path": null, + "knowledge_graph_local": { + "input_type": "markdown", + "path": "{REPO}/.terraphim/kg-taiarch" + }, + "public": false, + "publish": false + }, + "haystacks": [ + { + "location": "{REPO}", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + }, + { + "location": "{REPO}/docs", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + }, + { + "location": "{REPO}/.docs", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + }, + { + "location": "{REPO}/memory", + "service": "Ripgrep", + "read_only": true, + "fetch_content": false, + "atomic_server_secret": null, + "extra_parameters": {} + } + ], + "llm_enabled": false, + "llm_provider": "ollama", + "ollama_model": "llama3.2:3b", + "ollama_base_url": "http://127.0.0.1:11434", + "llm_router_enabled": false, + "llm_context_window": 32768, + "extra": {} } - } -} + }, + "id": "TerraphimAI" +} \ No newline at end of file diff --git a/.terraphim/kg-taiarch/knowledge-graph.md b/.terraphim/kg-taiarch/knowledge-graph.md new file mode 100644 index 000000000..2067247dd --- /dev/null +++ b/.terraphim/kg-taiarch/knowledge-graph.md @@ -0,0 +1,5 @@ +# Knowledge Graph + +Local-first KG: logseq-style markdown concepts with `synonyms::` lines compile to per-role thesauri; drives term expansion, automata, and graph ranking. + +synonyms:: knowledge graph, kg, thesaurus, synonyms, logseq, concept, term expansion diff --git a/.terraphim/kg-taiarch/privacy.md b/.terraphim/kg-taiarch/privacy.md new file mode 100644 index 000000000..16d8924f5 --- /dev/null +++ b/.terraphim/kg-taiarch/privacy.md @@ -0,0 +1,5 @@ +# Local-First Privacy + +No telemetry, local persistence, optional remote haystacks are explicit per role. Secrets via op:// references, never committed. + +synonyms:: privacy, local-first, no telemetry, offline diff --git a/.terraphim/kg-taiarch/relevance.md b/.terraphim/kg-taiarch/relevance.md new file mode 100644 index 000000000..1844bb3fb --- /dev/null +++ b/.terraphim/kg-taiarch/relevance.md @@ -0,0 +1,5 @@ +# Relevance Functions + +Pluggable ranking: title-scorer, terraphim-graph (rolegraph), BM25 variants. Selected per role in config. + +synonyms:: relevance, ranking, title-scorer, bm25, scorer, rank diff --git a/.terraphim/kg-taiarch/role.md b/.terraphim/kg-taiarch/role.md new file mode 100644 index 000000000..12bbdaf27 --- /dev/null +++ b/.terraphim/kg-taiarch/role.md @@ -0,0 +1,5 @@ +# Roles + +A role bundles a KG, haystacks, relevance function, and theme. Users/agents switch roles to change knowledge domain. Roles defined in config JSON. + +synonyms:: role, roles, role config, default_role, selected_role, haystack set diff --git a/.terraphim/kg-taiarch/search-pipeline.md b/.terraphim/kg-taiarch/search-pipeline.md new file mode 100644 index 000000000..1654a88f8 --- /dev/null +++ b/.terraphim/kg-taiarch/search-pipeline.md @@ -0,0 +1,5 @@ +# Search Pipeline + +Query → thesaurus expansion (automata) → haystack fetch → merge → relevance rank → results. Async via tokio; middleware crate provides the axum service. + +synonyms:: pipeline, search flow, query expansion, middleware, axum, async search diff --git a/.terraphim/kg-taidev/agent.md b/.terraphim/kg-taidev/agent.md new file mode 100644 index 000000000..0753e26ec --- /dev/null +++ b/.terraphim/kg-taidev/agent.md @@ -0,0 +1,5 @@ +# Terraphim Agent + +Agent crates (`terraphim_agent` + `terraphim-agent` binary): REPL with /search /role /graph, hooks handler, guard, learning capture, robot mode JSON for AI integration. + +synonyms:: agent, repl, terraphim-agent, hook, guard, robot mode, learning capture, /search, /role, /graph diff --git a/.terraphim/kg-taidev/automata.md b/.terraphim/kg-taidev/automata.md new file mode 100644 index 000000000..c8e2dc10e --- /dev/null +++ b/.terraphim/kg-taidev/automata.md @@ -0,0 +1,5 @@ +# Automata + +Core text automaton crate (`terraphim_automata`): Aho-Corasick based matching over the role thesaurus, autocomplete, and term linking. Foundation of KG-driven search and hooks text replacement. + +synonyms:: automata, aho-corasick, automaton, thesaurus, autocomplete, matcher, linkify, term linking, AutomataPath, TerraphimAutomata diff --git a/.terraphim/kg-taidev/config.md b/.terraphim/kg-taidev/config.md new file mode 100644 index 000000000..7a8c20bff --- /dev/null +++ b/.terraphim/kg-taidev/config.md @@ -0,0 +1,5 @@ +# Config & Secrets + +`terraphim_config` + `terraphim_onepassword_cli` (in terraphim-config-persistence): role config loading, SecretLoader resolving op://vault/item/field via 1Password CLI with EnvironmentLoader fallback. + +synonyms:: config, settings, secret, 1password, op://, SecretLoader, EnvironmentLoader, vault diff --git a/.terraphim/kg-taidev/haystack.md b/.terraphim/kg-taidev/haystack.md new file mode 100644 index 000000000..51e449279 --- /dev/null +++ b/.terraphim/kg-taidev/haystack.md @@ -0,0 +1,5 @@ +# Haystacks + +Search source crates (`haystack_*`): Ripgrep local files, GrepApp GitHub code search, QueryRs Rust docs, Atomic server, JMAP mail, Atlassian, Discourse. Each role configures its own haystacks. + +synonyms:: haystack, ripgrep, grepapp, queryrs, atomic, jmap, atlassian, discourse, data source, search source diff --git a/.terraphim/kg-taidev/hooks.md b/.terraphim/kg-taidev/hooks.md new file mode 100644 index 000000000..1f41fdfee --- /dev/null +++ b/.terraphim/kg-taidev/hooks.md @@ -0,0 +1,5 @@ +# Hooks + +`terraphim_hooks`: KG-driven text replacement and validation hooks (pre-commit, Claude Code integration). + +synonyms:: hooks, text replacement, validate, pre-commit, replace diff --git a/.terraphim/kg-taidev/mcp.md b/.terraphim/kg-taidev/mcp.md new file mode 100644 index 000000000..591f31275 --- /dev/null +++ b/.terraphim/kg-taidev/mcp.md @@ -0,0 +1,5 @@ +# MCP Server + +`terraphim_mcp_server`: exposes Terraphim search as MCP tools for Claude Code / agents. + +synonyms:: mcp, mcp server, model context protocol, mcp tools, search tool diff --git a/.terraphim/kg-taidev/orchestrator.md b/.terraphim/kg-taidev/orchestrator.md new file mode 100644 index 000000000..b52d5eb21 --- /dev/null +++ b/.terraphim/kg-taidev/orchestrator.md @@ -0,0 +1,5 @@ +# Multi-Agent Orchestration + +Agent fleet crates (`terraphim_orchestrator`, `terraphim_multi_agent`, `terraphim_agent_supervisor`, `terraphim_agent_registry`, `terraphim_agent_messaging`, `terraphim_agent_evolution`): spawn, supervise, message and evolve agent fleets. + +synonyms:: orchestrator, multi-agent, supervisor, registry, messaging, evolution, spawner, fleet, swarm diff --git a/.terraphim/kg-taidev/persistence.md b/.terraphim/kg-taidev/persistence.md new file mode 100644 index 000000000..144c6cbfd --- /dev/null +++ b/.terraphim/kg-taidev/persistence.md @@ -0,0 +1,5 @@ +# Persistence + +Storage layer (`terraphim_persistence`): DeviceStorage over OpenDAL with memory/dashmap/redb/sqlite/s3 profiles, multi-profile cache write-back, settings persistence. + +synonyms:: persistence, storage, opendal, redb, sqlite, dashmap, DeviceStorage, settings, cache, write-back, memory backend, s3 backend diff --git a/.terraphim/kg-taidev/rolegraph.md b/.terraphim/kg-taidev/rolegraph.md new file mode 100644 index 000000000..95224db17 --- /dev/null +++ b/.terraphim/kg-taidev/rolegraph.md @@ -0,0 +1,5 @@ +# Rolegraph + +Graph ranking crate (`terraphim_rolegraph`): builds the co-occurrence graph over documents for a role and ranks results (terraphim-graph relevance function). + +synonyms:: rolegraph, role graph, co-occurrence, graph rank, terraphim-graph, relevance, RoleGraph, graph ranking diff --git a/.terraphim/kg-taidev/session.md b/.terraphim/kg-taidev/session.md new file mode 100644 index 000000000..b06bcf022 --- /dev/null +++ b/.terraphim/kg-taidev/session.md @@ -0,0 +1,5 @@ +# Sessions + +`terraphim_sessions` + `terraphim-session-analyzer`: agent session capture, analysis, resume. + +synonyms:: session, sessions, session analyzer, resume, transcript diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py new file mode 100755 index 000000000..5d6cb06df --- /dev/null +++ b/.terraphim/scripts/bootstrap.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Bootstrap .terraphim for this clone: materialize {REPO} placeholders, +rebase foreign absolute paths, keep relative paths, regenerate thesauri +from kg-/ markdown (and skip roles whose kg dir is missing). + +Idempotent. Run from anywhere inside the repo after cloning, and after +adding/changing concept files. +""" +import os, json, glob, subprocess, sys + +REPO = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip() +TD = os.path.join(REPO, ".terraphim") +CFG = os.path.join(TD, "config.json") + +def compile_thesaurus(kg_dir, out_path, role_name): + if not os.path.isdir(kg_dir): + print(f" skip thesaurus (missing {os.path.relpath(kg_dir, REPO)})") + return + data, cid = {}, 100 + for f in sorted(glob.glob(os.path.join(kg_dir, "*.md"))): + nterm = os.path.splitext(os.path.basename(f))[0] + txt = open(f, encoding="utf-8").read() + syn = next((l.split("::", 1)[1] for l in txt.splitlines() + if l.strip().lower().startswith("synonyms::")), "") + for t in dict.fromkeys([nterm] + [s.strip().lower() for s in syn.split(",") if s.strip()]): + data.setdefault(t.lower(), {"id": cid, "nterm": nterm}) + cid += 1 + json.dump({"name": role_name, "data": data}, open(out_path, "w"), indent=2) + print(f" wrote {os.path.relpath(out_path, REPO)} ({len(data)} terms)") + +def materialize_loc(loc: str) -> str: + if loc == "{REPO}" or loc.startswith("{REPO}/"): + return REPO + loc[len("{REPO}"):] + if loc.startswith("/") and REPO not in loc: + for suffix in ("/rust/crates", "/crates", "/memory", "/docs", "/.docs", "/.terraphim"): + if loc.endswith(suffix): + return REPO + suffix + return loc # unknown absolute — leave + # relative or already this-repo absolute + return loc + +cfg = json.load(open(CFG)) +for role in cfg.get("roles", {}).values(): + short = role.get("shortname") or role.get("name", "").lower().replace(" ", "-") + kg = role.get("kg", {}).get("knowledge_graph_local") + if kg and "path" in kg: + p = kg["path"] + if p == "{REPO}" or p.startswith("{REPO}/"): + kg["path"] = REPO + p[len("{REPO}"):] + elif p.startswith(".terraphim/") or p.startswith("./"): + kg["path"] = os.path.join(REPO, p[2:] if p.startswith("./") else p) + elif not p.startswith("/"): + # bare relative like "kg/foo" under .terraphim or repo + cand = os.path.join(TD, p) if not p.startswith("kg") else os.path.join(TD, p) + # prefer existing kg- fleet layout when shortname set + fleet = os.path.join(TD, f"kg-{short}") + if os.path.isdir(fleet): + kg["path"] = fleet + elif os.path.isdir(os.path.join(REPO, p)): + kg["path"] = os.path.join(REPO, p) + elif os.path.isdir(os.path.join(TD, p)): + kg["path"] = os.path.join(TD, p) + else: + kg["path"] = os.path.join(REPO, p) if not p.startswith(".") else os.path.join(REPO, p) + elif p.startswith("/") and REPO not in p: + fleet = os.path.join(TD, f"kg-{short}") + if os.path.isdir(fleet): + kg["path"] = fleet + for h in role.get("haystacks", []): + h["location"] = materialize_loc(h.get("location", "")) + # only compile fleet-style kg- dirs (do not clobber legacy kg/ thesauri generators) + fleet_kg = os.path.join(TD, f"kg-{short}") + if os.path.isdir(fleet_kg): + compile_thesaurus(fleet_kg, os.path.join(TD, f"thesaurus-{short}.json"), role.get("name", short)) + +json.dump(cfg, open(CFG, "w"), indent=2) +print(f" config.json paths rebased to {REPO}") +print("done.") diff --git a/.terraphim/skills.toml b/.terraphim/skills.toml new file mode 100644 index 000000000..0d7ffd6e7 --- /dev/null +++ b/.terraphim/skills.toml @@ -0,0 +1,28 @@ +# Terraphim skills manifest for agents working in this repository. +# Install with: tsm install (all marketplace skills currently premium — `tsm login` required) +# Registry: https://terraphim-skills.md +# Names verified against `tsm list` 2026-08-08. + +[required] +skills = [ + "disciplined-research", + "disciplined-specification", + "disciplined-design", + "disciplined-implementation", + "disciplined-verification", + "disciplined-validation", + "code-review", + "debugging", + "handover", + "learning-capture", + "git-safety-guard", +] + +[recommended] +skills = [ + "architecture", + "documentation", + "kg-rlm-ingest", + "gitea-pagerank-workflow", + "deterministic-rlm-review", +] diff --git a/.terraphim/thesaurus-taiarch.json b/.terraphim/thesaurus-taiarch.json new file mode 100644 index 000000000..4ac759764 --- /dev/null +++ b/.terraphim/thesaurus-taiarch.json @@ -0,0 +1,129 @@ +{ + "name": "Terraphim AI Architect", + "data": { + "knowledge-graph": { + "id": 100, + "nterm": "knowledge-graph" + }, + "knowledge graph": { + "id": 100, + "nterm": "knowledge-graph" + }, + "kg": { + "id": 100, + "nterm": "knowledge-graph" + }, + "thesaurus": { + "id": 100, + "nterm": "knowledge-graph" + }, + "synonyms": { + "id": 100, + "nterm": "knowledge-graph" + }, + "logseq": { + "id": 100, + "nterm": "knowledge-graph" + }, + "concept": { + "id": 100, + "nterm": "knowledge-graph" + }, + "term expansion": { + "id": 100, + "nterm": "knowledge-graph" + }, + "privacy": { + "id": 101, + "nterm": "privacy" + }, + "local-first": { + "id": 101, + "nterm": "privacy" + }, + "no telemetry": { + "id": 101, + "nterm": "privacy" + }, + "offline": { + "id": 101, + "nterm": "privacy" + }, + "relevance": { + "id": 102, + "nterm": "relevance" + }, + "ranking": { + "id": 102, + "nterm": "relevance" + }, + "title-scorer": { + "id": 102, + "nterm": "relevance" + }, + "bm25": { + "id": 102, + "nterm": "relevance" + }, + "scorer": { + "id": 102, + "nterm": "relevance" + }, + "rank": { + "id": 102, + "nterm": "relevance" + }, + "role": { + "id": 103, + "nterm": "role" + }, + "roles": { + "id": 103, + "nterm": "role" + }, + "role config": { + "id": 103, + "nterm": "role" + }, + "default_role": { + "id": 103, + "nterm": "role" + }, + "selected_role": { + "id": 103, + "nterm": "role" + }, + "haystack set": { + "id": 103, + "nterm": "role" + }, + "search-pipeline": { + "id": 104, + "nterm": "search-pipeline" + }, + "pipeline": { + "id": 104, + "nterm": "search-pipeline" + }, + "search flow": { + "id": 104, + "nterm": "search-pipeline" + }, + "query expansion": { + "id": 104, + "nterm": "search-pipeline" + }, + "middleware": { + "id": 104, + "nterm": "search-pipeline" + }, + "axum": { + "id": 104, + "nterm": "search-pipeline" + }, + "async search": { + "id": 104, + "nterm": "search-pipeline" + } + } +} \ No newline at end of file diff --git a/.terraphim/thesaurus-taidev.json b/.terraphim/thesaurus-taidev.json new file mode 100644 index 000000000..e8ca6ed1b --- /dev/null +++ b/.terraphim/thesaurus-taidev.json @@ -0,0 +1,325 @@ +{ + "name": "Terraphim AI Developer", + "data": { + "agent": { + "id": 100, + "nterm": "agent" + }, + "repl": { + "id": 100, + "nterm": "agent" + }, + "terraphim-agent": { + "id": 100, + "nterm": "agent" + }, + "hook": { + "id": 100, + "nterm": "agent" + }, + "guard": { + "id": 100, + "nterm": "agent" + }, + "robot mode": { + "id": 100, + "nterm": "agent" + }, + "learning capture": { + "id": 100, + "nterm": "agent" + }, + "/search": { + "id": 100, + "nterm": "agent" + }, + "/role": { + "id": 100, + "nterm": "agent" + }, + "/graph": { + "id": 100, + "nterm": "agent" + }, + "automata": { + "id": 101, + "nterm": "automata" + }, + "aho-corasick": { + "id": 101, + "nterm": "automata" + }, + "automaton": { + "id": 101, + "nterm": "automata" + }, + "thesaurus": { + "id": 101, + "nterm": "automata" + }, + "autocomplete": { + "id": 101, + "nterm": "automata" + }, + "matcher": { + "id": 101, + "nterm": "automata" + }, + "linkify": { + "id": 101, + "nterm": "automata" + }, + "term linking": { + "id": 101, + "nterm": "automata" + }, + "automatapath": { + "id": 101, + "nterm": "automata" + }, + "terraphimautomata": { + "id": 101, + "nterm": "automata" + }, + "config": { + "id": 102, + "nterm": "config" + }, + "settings": { + "id": 102, + "nterm": "config" + }, + "secret": { + "id": 102, + "nterm": "config" + }, + "1password": { + "id": 102, + "nterm": "config" + }, + "op://": { + "id": 102, + "nterm": "config" + }, + "secretloader": { + "id": 102, + "nterm": "config" + }, + "environmentloader": { + "id": 102, + "nterm": "config" + }, + "vault": { + "id": 102, + "nterm": "config" + }, + "haystack": { + "id": 103, + "nterm": "haystack" + }, + "ripgrep": { + "id": 103, + "nterm": "haystack" + }, + "grepapp": { + "id": 103, + "nterm": "haystack" + }, + "queryrs": { + "id": 103, + "nterm": "haystack" + }, + "atomic": { + "id": 103, + "nterm": "haystack" + }, + "jmap": { + "id": 103, + "nterm": "haystack" + }, + "atlassian": { + "id": 103, + "nterm": "haystack" + }, + "discourse": { + "id": 103, + "nterm": "haystack" + }, + "data source": { + "id": 103, + "nterm": "haystack" + }, + "search source": { + "id": 103, + "nterm": "haystack" + }, + "hooks": { + "id": 104, + "nterm": "hooks" + }, + "text replacement": { + "id": 104, + "nterm": "hooks" + }, + "validate": { + "id": 104, + "nterm": "hooks" + }, + "pre-commit": { + "id": 104, + "nterm": "hooks" + }, + "replace": { + "id": 104, + "nterm": "hooks" + }, + "mcp": { + "id": 105, + "nterm": "mcp" + }, + "mcp server": { + "id": 105, + "nterm": "mcp" + }, + "model context protocol": { + "id": 105, + "nterm": "mcp" + }, + "mcp tools": { + "id": 105, + "nterm": "mcp" + }, + "search tool": { + "id": 105, + "nterm": "mcp" + }, + "orchestrator": { + "id": 106, + "nterm": "orchestrator" + }, + "multi-agent": { + "id": 106, + "nterm": "orchestrator" + }, + "supervisor": { + "id": 106, + "nterm": "orchestrator" + }, + "registry": { + "id": 106, + "nterm": "orchestrator" + }, + "messaging": { + "id": 106, + "nterm": "orchestrator" + }, + "evolution": { + "id": 106, + "nterm": "orchestrator" + }, + "spawner": { + "id": 106, + "nterm": "orchestrator" + }, + "fleet": { + "id": 106, + "nterm": "orchestrator" + }, + "swarm": { + "id": 106, + "nterm": "orchestrator" + }, + "persistence": { + "id": 107, + "nterm": "persistence" + }, + "storage": { + "id": 107, + "nterm": "persistence" + }, + "opendal": { + "id": 107, + "nterm": "persistence" + }, + "redb": { + "id": 107, + "nterm": "persistence" + }, + "sqlite": { + "id": 107, + "nterm": "persistence" + }, + "dashmap": { + "id": 107, + "nterm": "persistence" + }, + "devicestorage": { + "id": 107, + "nterm": "persistence" + }, + "cache": { + "id": 107, + "nterm": "persistence" + }, + "write-back": { + "id": 107, + "nterm": "persistence" + }, + "memory backend": { + "id": 107, + "nterm": "persistence" + }, + "s3 backend": { + "id": 107, + "nterm": "persistence" + }, + "rolegraph": { + "id": 108, + "nterm": "rolegraph" + }, + "role graph": { + "id": 108, + "nterm": "rolegraph" + }, + "co-occurrence": { + "id": 108, + "nterm": "rolegraph" + }, + "graph rank": { + "id": 108, + "nterm": "rolegraph" + }, + "terraphim-graph": { + "id": 108, + "nterm": "rolegraph" + }, + "relevance": { + "id": 108, + "nterm": "rolegraph" + }, + "graph ranking": { + "id": 108, + "nterm": "rolegraph" + }, + "session": { + "id": 109, + "nterm": "session" + }, + "sessions": { + "id": 109, + "nterm": "session" + }, + "session analyzer": { + "id": 109, + "nterm": "session" + }, + "resume": { + "id": 109, + "nterm": "session" + }, + "transcript": { + "id": 109, + "nterm": "session" + } + } +} \ No newline at end of file diff --git a/memory/README.md b/memory/README.md new file mode 100644 index 000000000..f4d93213b --- /dev/null +++ b/memory/README.md @@ -0,0 +1,10 @@ +# Repo-local agent memory + +Durable, repo-scoped memory for agents working in this repository (fleet +standard §8). Root `memory/` is a read-only haystack in `.terraphim/config.json`, +so everything here is searchable via terraphim-agent / terraphim-grep. + +- `YYYY-MM-DD.md` — daily working notes (decisions, evidence, pointers) +- `regressions.md` — failures turned into guardrails: `| date | what broke | rule | status |` +- Long-term curated notes live in the global Terraphim KG, not here — promote + distilled learnings, keep raw logs local. From 14d4a971d93d48610f8358ce7957009b4c76820c Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 12:52:05 +0100 Subject: [PATCH 2/6] fix: fleet roles use repo-relative paths (match migration P2 fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit taidev/taiarch haystacks and KG paths are repo-relative; bootstrap only regenerates thesauri and normalizes absolutes back to relative — never writes machine paths into tracked config.json. Refs fleet-standard §8 --- .terraphim/README.md | 6 ++ .terraphim/config.json | 20 ++--- .terraphim/scripts/bootstrap.py | 130 +++++++++++++++++++------------- 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/.terraphim/README.md b/.terraphim/README.md index cdd19384a..28d4b3b05 100644 --- a/.terraphim/README.md +++ b/.terraphim/README.md @@ -108,3 +108,9 @@ python3 .terraphim/scripts/bootstrap.py # materialize {REPO} + refresh thesaur Skills manifest: `.terraphim/skills.toml` (`tsm install `). Repo agent memory: `memory/` at repo root. + +## Path portability + +Fleet roles use **repo-relative** haystack and KG paths. `scripts/bootstrap.py` +regenerates thesauri and normalizes accidental absolute/`{REPO}` paths back to +relative form — it never writes machine-absolute paths into tracked config. diff --git a/.terraphim/config.json b/.terraphim/config.json index 558d11319..5ee766165 100644 --- a/.terraphim/config.json +++ b/.terraphim/config.json @@ -12,7 +12,7 @@ "kg": { "knowledge_graph_local": { "input_type": "markdown", - "path": ".terraphim/kg/rust-engineer" + "path": ".terraphim/kg-rust-engineer" }, "public": true, "publish": true @@ -42,14 +42,14 @@ "automata_path": null, "knowledge_graph_local": { "input_type": "markdown", - "path": "{REPO}/.terraphim/kg-taidev" + "path": ".terraphim/kg-taidev" }, "public": false, "publish": false }, "haystacks": [ { - "location": "{REPO}", + "location": ".", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -57,7 +57,7 @@ "extra_parameters": {} }, { - "location": "{REPO}/crates", + "location": "crates", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -65,7 +65,7 @@ "extra_parameters": {} }, { - "location": "{REPO}/memory", + "location": "memory", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -91,14 +91,14 @@ "automata_path": null, "knowledge_graph_local": { "input_type": "markdown", - "path": "{REPO}/.terraphim/kg-taiarch" + "path": ".terraphim/kg-taiarch" }, "public": false, "publish": false }, "haystacks": [ { - "location": "{REPO}", + "location": ".", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -106,7 +106,7 @@ "extra_parameters": {} }, { - "location": "{REPO}/docs", + "location": "docs", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -114,7 +114,7 @@ "extra_parameters": {} }, { - "location": "{REPO}/.docs", + "location": ".docs", "service": "Ripgrep", "read_only": true, "fetch_content": false, @@ -122,7 +122,7 @@ "extra_parameters": {} }, { - "location": "{REPO}/memory", + "location": "memory", "service": "Ripgrep", "read_only": true, "fetch_content": false, diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py index 5d6cb06df..f01df1546 100755 --- a/.terraphim/scripts/bootstrap.py +++ b/.terraphim/scripts/bootstrap.py @@ -1,78 +1,106 @@ #!/usr/bin/env python3 -"""Bootstrap .terraphim for this clone: materialize {REPO} placeholders, -rebase foreign absolute paths, keep relative paths, regenerate thesauri -from kg-/ markdown (and skip roles whose kg dir is missing). +"""Bootstrap .terraphim for this clone. -Idempotent. Run from anywhere inside the repo after cloning, and after -adding/changing concept files. +- Regenerates thesaurus-.json from kg-/*.md +- Normalizes role KG paths to repo-relative `.terraphim/kg-` form +- Keeps haystack locations repo-relative (`.`, `crates`, …) +- NEVER writes machine-absolute paths into the tracked config.json + (fleet standard §8 portability) + +Idempotent. Run after clone and after editing concept files. """ -import os, json, glob, subprocess, sys +from __future__ import annotations + +import glob +import json +import os +import subprocess +import sys -REPO = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip() +REPO = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True +).strip() TD = os.path.join(REPO, ".terraphim") CFG = os.path.join(TD, "config.json") -def compile_thesaurus(kg_dir, out_path, role_name): + +def compile_thesaurus(kg_dir: str, out_path: str, role_name: str) -> None: if not os.path.isdir(kg_dir): print(f" skip thesaurus (missing {os.path.relpath(kg_dir, REPO)})") return - data, cid = {}, 100 + data: dict = {} + cid = 100 for f in sorted(glob.glob(os.path.join(kg_dir, "*.md"))): nterm = os.path.splitext(os.path.basename(f))[0] txt = open(f, encoding="utf-8").read() - syn = next((l.split("::", 1)[1] for l in txt.splitlines() - if l.strip().lower().startswith("synonyms::")), "") - for t in dict.fromkeys([nterm] + [s.strip().lower() for s in syn.split(",") if s.strip()]): + syn = next( + ( + l.split("::", 1)[1] + for l in txt.splitlines() + if l.strip().lower().startswith("synonyms::") + ), + "", + ) + for t in dict.fromkeys( + [nterm] + [s.strip().lower() for s in syn.split(",") if s.strip()] + ): data.setdefault(t.lower(), {"id": cid, "nterm": nterm}) cid += 1 json.dump({"name": role_name, "data": data}, open(out_path, "w"), indent=2) print(f" wrote {os.path.relpath(out_path, REPO)} ({len(data)} terms)") -def materialize_loc(loc: str) -> str: - if loc == "{REPO}" or loc.startswith("{REPO}/"): - return REPO + loc[len("{REPO}"):] - if loc.startswith("/") and REPO not in loc: - for suffix in ("/rust/crates", "/crates", "/memory", "/docs", "/.docs", "/.terraphim"): + +def to_repo_relative(loc: str) -> str: + """Map absolute/{REPO}/foreign paths back to repo-relative form.""" + if loc in ("{REPO}", ".", ""): + return "." + if loc.startswith("{REPO}/"): + return loc[len("{REPO}/") :] or "." + if loc == REPO or loc.rstrip("/") == REPO: + return "." + if loc.startswith(REPO + os.sep): + return loc[len(REPO) + 1 :] or "." + # foreign absolute: try known suffixes, else basename==repo basename → root + if loc.startswith("/"): + for suffix in ( + "/rust/crates", + "/crates", + "/memory", + "/docs", + "/.docs", + "/.terraphim", + ): if loc.endswith(suffix): - return REPO + suffix - return loc # unknown absolute — leave - # relative or already this-repo absolute - return loc + return suffix.lstrip("/") + if os.path.basename(loc.rstrip("/")) == os.path.basename(REPO): + return "." + return loc # already relative or unknown — leave + cfg = json.load(open(CFG)) +changed = False for role in cfg.get("roles", {}).values(): - short = role.get("shortname") or role.get("name", "").lower().replace(" ", "-") + short = role.get("shortname") or role.get("name", "role").lower().replace(" ", "-") kg = role.get("kg", {}).get("knowledge_graph_local") if kg and "path" in kg: - p = kg["path"] - if p == "{REPO}" or p.startswith("{REPO}/"): - kg["path"] = REPO + p[len("{REPO}"):] - elif p.startswith(".terraphim/") or p.startswith("./"): - kg["path"] = os.path.join(REPO, p[2:] if p.startswith("./") else p) - elif not p.startswith("/"): - # bare relative like "kg/foo" under .terraphim or repo - cand = os.path.join(TD, p) if not p.startswith("kg") else os.path.join(TD, p) - # prefer existing kg- fleet layout when shortname set - fleet = os.path.join(TD, f"kg-{short}") - if os.path.isdir(fleet): - kg["path"] = fleet - elif os.path.isdir(os.path.join(REPO, p)): - kg["path"] = os.path.join(REPO, p) - elif os.path.isdir(os.path.join(TD, p)): - kg["path"] = os.path.join(TD, p) - else: - kg["path"] = os.path.join(REPO, p) if not p.startswith(".") else os.path.join(REPO, p) - elif p.startswith("/") and REPO not in p: - fleet = os.path.join(TD, f"kg-{short}") - if os.path.isdir(fleet): - kg["path"] = fleet + rel = f".terraphim/kg-{short}" + if kg["path"] != rel: + kg["path"] = rel + changed = True for h in role.get("haystacks", []): - h["location"] = materialize_loc(h.get("location", "")) - # only compile fleet-style kg- dirs (do not clobber legacy kg/ thesauri generators) - fleet_kg = os.path.join(TD, f"kg-{short}") - if os.path.isdir(fleet_kg): - compile_thesaurus(fleet_kg, os.path.join(TD, f"thesaurus-{short}.json"), role.get("name", short)) + new = to_repo_relative(h.get("location", ".")) + if h.get("location") != new: + h["location"] = new + changed = True + compile_thesaurus( + os.path.join(TD, f"kg-{short}"), + os.path.join(TD, f"thesaurus-{short}.json"), + role.get("name", short), + ) -json.dump(cfg, open(CFG, "w"), indent=2) -print(f" config.json paths rebased to {REPO}") +if changed: + json.dump(cfg, open(CFG, "w"), indent=2) + print(" config.json normalized to repo-relative paths (portable)") +else: + print(" config.json already portable (no path rewrite)") print("done.") From 222f068220e285bc48f9f789e21cf2863afe9939 Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 12:52:37 +0100 Subject: [PATCH 3/6] fix: bootstrap preserves legacy kg// paths (do not clobber Rust Engineer) --- .terraphim/config.json | 2 +- .terraphim/scripts/bootstrap.py | 41 ++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.terraphim/config.json b/.terraphim/config.json index 5ee766165..53c64ea60 100644 --- a/.terraphim/config.json +++ b/.terraphim/config.json @@ -12,7 +12,7 @@ "kg": { "knowledge_graph_local": { "input_type": "markdown", - "path": ".terraphim/kg-rust-engineer" + "path": ".terraphim/kg/rust-engineer" }, "public": true, "publish": true diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py index f01df1546..f59fdfb8f 100755 --- a/.terraphim/scripts/bootstrap.py +++ b/.terraphim/scripts/bootstrap.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 """Bootstrap .terraphim for this clone. -- Regenerates thesaurus-.json from kg-/*.md -- Normalizes role KG paths to repo-relative `.terraphim/kg-` form -- Keeps haystack locations repo-relative (`.`, `crates`, …) -- NEVER writes machine-absolute paths into the tracked config.json - (fleet standard §8 portability) +- Regenerates thesaurus-.json from kg-/*.md when that + fleet-style directory exists +- Normalizes accidental absolute/{REPO} haystack paths back to repo-relative +- Does NOT rewrite legacy kg// paths (e.g. .terraphim/kg/rust-engineer) +- NEVER writes machine-absolute paths into tracked config.json Idempotent. Run after clone and after editing concept files. """ @@ -15,7 +15,6 @@ import json import os import subprocess -import sys REPO = subprocess.check_output( ["git", "rev-parse", "--show-toplevel"], text=True @@ -51,7 +50,6 @@ def compile_thesaurus(kg_dir: str, out_path: str, role_name: str) -> None: def to_repo_relative(loc: str) -> str: - """Map absolute/{REPO}/foreign paths back to repo-relative form.""" if loc in ("{REPO}", ".", ""): return "." if loc.startswith("{REPO}/"): @@ -60,7 +58,6 @@ def to_repo_relative(loc: str) -> str: return "." if loc.startswith(REPO + os.sep): return loc[len(REPO) + 1 :] or "." - # foreign absolute: try known suffixes, else basename==repo basename → root if loc.startswith("/"): for suffix in ( "/rust/crates", @@ -74,33 +71,45 @@ def to_repo_relative(loc: str) -> str: return suffix.lstrip("/") if os.path.basename(loc.rstrip("/")) == os.path.basename(REPO): return "." - return loc # already relative or unknown — leave + return loc cfg = json.load(open(CFG)) changed = False for role in cfg.get("roles", {}).values(): short = role.get("shortname") or role.get("name", "role").lower().replace(" ", "-") + fleet_kg = os.path.join(TD, f"kg-{short}") kg = role.get("kg", {}).get("knowledge_graph_local") - if kg and "path" in kg: + # Only force fleet path when fleet kg- dir exists; leave legacy kg/ + if kg and "path" in kg and os.path.isdir(fleet_kg): rel = f".terraphim/kg-{short}" if kg["path"] != rel: kg["path"] = rel changed = True + elif kg and "path" in kg: + # normalize absolute/{REPO} on legacy paths without changing layout + p = kg["path"] + if p.startswith("{REPO}/"): + kg["path"] = p[len("{REPO}/") :] + changed = True + elif p.startswith(REPO + os.sep): + kg["path"] = p[len(REPO) + 1 :] + changed = True for h in role.get("haystacks", []): new = to_repo_relative(h.get("location", ".")) if h.get("location") != new: h["location"] = new changed = True - compile_thesaurus( - os.path.join(TD, f"kg-{short}"), - os.path.join(TD, f"thesaurus-{short}.json"), - role.get("name", short), - ) + if os.path.isdir(fleet_kg): + compile_thesaurus( + fleet_kg, + os.path.join(TD, f"thesaurus-{short}.json"), + role.get("name", short), + ) if changed: json.dump(cfg, open(CFG, "w"), indent=2) - print(" config.json normalized to repo-relative paths (portable)") + print(" config.json normalized (portable relative paths)") else: print(" config.json already portable (no path rewrite)") print("done.") From a688b00dd1dae4290685d62c3ca8a194aa2208b4 Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 13:00:03 +0100 Subject: [PATCH 4/6] fix: drop invalid Config id; warn synonym collisions; trailing newlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Structural review P1: top-level id "TerraphimAI" is not a ConfigId variant (Server/Desktop/Embedded) and breaks strict Config::load_from_json_file. ProjectConfig ignores it; remove rather than invent a value. P2: synonym first-wins now warns; JSON files end with newline. Preserve legacy kg/rust-engineer path. Refs fleet-standard §8 --- .terraphim/README.md | 2 +- .terraphim/config.json | 5 ++--- .terraphim/scripts/bootstrap.py | 24 +++++++++++++++--------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.terraphim/README.md b/.terraphim/README.md index 28d4b3b05..746c968c5 100644 --- a/.terraphim/README.md +++ b/.terraphim/README.md @@ -111,6 +111,6 @@ Repo agent memory: `memory/` at repo root. ## Path portability -Fleet roles use **repo-relative** haystack and KG paths. `scripts/bootstrap.py` +Fleet roles use **repo-relative** haystack and KG paths. `scripts/bootstrap.py` — regenerates thesauri and normalizes any absolute/`{REPO}` paths **back** to repo-relative form. Run once after cloning. regenerates thesauri and normalizes accidental absolute/`{REPO}` paths back to relative form — it never writes machine-absolute paths into tracked config. diff --git a/.terraphim/config.json b/.terraphim/config.json index 53c64ea60..d6eee56e3 100644 --- a/.terraphim/config.json +++ b/.terraphim/config.json @@ -138,6 +138,5 @@ "llm_context_window": 32768, "extra": {} } - }, - "id": "TerraphimAI" -} \ No newline at end of file + } +} diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py index f59fdfb8f..7369421ea 100755 --- a/.terraphim/scripts/bootstrap.py +++ b/.terraphim/scripts/bootstrap.py @@ -40,10 +40,17 @@ def compile_thesaurus(kg_dir: str, out_path: str, role_name: str) -> None: ), "", ) - for t in dict.fromkeys( + for term in dict.fromkeys( [nterm] + [s.strip().lower() for s in syn.split(",") if s.strip()] ): - data.setdefault(t.lower(), {"id": cid, "nterm": nterm}) + key = term.lower() + if key in data and data[key]["nterm"] != nterm: + print( + f" warn: synonym {key!r} already maps to {data[key]['nterm']!r}; " + f"ignoring mapping to {nterm!r} ({os.path.basename(f)})" + ) + continue + data.setdefault(key, {"id": cid, "nterm": nterm}) cid += 1 json.dump({"name": role_name, "data": data}, open(out_path, "w"), indent=2) print(f" wrote {os.path.relpath(out_path, REPO)} ({len(data)} terms)") @@ -87,13 +94,12 @@ def to_repo_relative(loc: str) -> str: kg["path"] = rel changed = True elif kg and "path" in kg: - # normalize absolute/{REPO} on legacy paths without changing layout - p = kg["path"] - if p.startswith("{REPO}/"): - kg["path"] = p[len("{REPO}/") :] - changed = True - elif p.startswith(REPO + os.sep): - kg["path"] = p[len(REPO) + 1 :] + # legacy kg// — heal via same relative normalizer (no layout change) + new_p = to_repo_relative(kg["path"]) + if new_p == ".": + new_p = kg["path"] # don't collapse a kg path to repo root + if kg["path"] != new_p: + kg["path"] = new_p changed = True for h in role.get("haystacks", []): new = to_repo_relative(h.get("location", ".")) From 9969458bf1fda740e3e02167634efd5db3c50b70 Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 13:05:13 +0100 Subject: [PATCH 5/6] fix: bootstrap only rewrites in-repo absolute paths; JSON trailing newlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R2 structural review: - P1: suffix heuristic rewrote foreign haystacks ending in /docs,/crates,etc. Now requires realpath containment under REPO; foreign paths left untouched. - P2: thesaurus json.dump now writes trailing newline (stays fixed on regen) - P2: clean Path portability README section (remove duplicate fragment) Refs fleet-standard §8 --- .terraphim/README.md | 8 +++++--- .terraphim/scripts/bootstrap.py | 29 +++++++++++++++++------------ .terraphim/thesaurus-taiarch.json | 2 +- .terraphim/thesaurus-taidev.json | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/.terraphim/README.md b/.terraphim/README.md index 746c968c5..847d88270 100644 --- a/.terraphim/README.md +++ b/.terraphim/README.md @@ -111,6 +111,8 @@ Repo agent memory: `memory/` at repo root. ## Path portability -Fleet roles use **repo-relative** haystack and KG paths. `scripts/bootstrap.py` — regenerates thesauri and normalizes any absolute/`{REPO}` paths **back** to repo-relative form. Run once after cloning. -regenerates thesauri and normalizes accidental absolute/`{REPO}` paths back to -relative form — it never writes machine-absolute paths into tracked config. +Fleet roles use **repo-relative** haystack and KG paths (`.`, `crates`, `memory`, …). +`scripts/bootstrap.py` regenerates thesauri and normalizes absolute/`{REPO}` paths +that resolve **inside this repo** back to relative form. Foreign absolute haystacks +are left untouched. The script never writes machine-absolute paths into tracked config. + diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py index 7369421ea..ca976eeea 100755 --- a/.terraphim/scripts/bootstrap.py +++ b/.terraphim/scripts/bootstrap.py @@ -52,7 +52,9 @@ def compile_thesaurus(kg_dir: str, out_path: str, role_name: str) -> None: continue data.setdefault(key, {"id": cid, "nterm": nterm}) cid += 1 - json.dump({"name": role_name, "data": data}, open(out_path, "w"), indent=2) + with open(out_path, "w", encoding="utf-8") as fh: + json.dump({"name": role_name, "data": data}, fh, indent=2) + fh.write("\n") print(f" wrote {os.path.relpath(out_path, REPO)} ({len(data)} terms)") @@ -66,18 +68,21 @@ def to_repo_relative(loc: str) -> str: if loc.startswith(REPO + os.sep): return loc[len(REPO) + 1 :] or "." if loc.startswith("/"): - for suffix in ( - "/rust/crates", - "/crates", - "/memory", - "/docs", - "/.docs", - "/.terraphim", - ): - if loc.endswith(suffix): - return suffix.lstrip("/") - if os.path.basename(loc.rstrip("/")) == os.path.basename(REPO): + # Only rewrite absolute paths that resolve INSIDE this repo. + # Never rewrite foreign paths that merely share a directory suffix + # (e.g. /mnt/shared/docs must not become "docs"). + try: + real_loc = os.path.realpath(loc) + real_repo = os.path.realpath(REPO) + except OSError: + return loc + if real_loc == real_repo: return "." + prefix = real_repo + os.sep + if real_loc.startswith(prefix): + return real_loc[len(prefix):] or "." + # foreign absolute path — leave untouched + return loc return loc diff --git a/.terraphim/thesaurus-taiarch.json b/.terraphim/thesaurus-taiarch.json index 4ac759764..07385320e 100644 --- a/.terraphim/thesaurus-taiarch.json +++ b/.terraphim/thesaurus-taiarch.json @@ -126,4 +126,4 @@ "nterm": "search-pipeline" } } -} \ No newline at end of file +} diff --git a/.terraphim/thesaurus-taidev.json b/.terraphim/thesaurus-taidev.json index e8ca6ed1b..a422da54d 100644 --- a/.terraphim/thesaurus-taidev.json +++ b/.terraphim/thesaurus-taidev.json @@ -322,4 +322,4 @@ "nterm": "session" } } -} \ No newline at end of file +} From 905f1d9ace4e3acdd756afc7e08efb99d6e16d06 Mon Sep 17 00:00:00 2001 From: AlexMikhalev Date: Sat, 8 Aug 2026 13:07:37 +0100 Subject: [PATCH 6/6] fix: trailing newline on config.json write-back (R3 P2) --- .terraphim/scripts/bootstrap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.terraphim/scripts/bootstrap.py b/.terraphim/scripts/bootstrap.py index ca976eeea..8796b0606 100755 --- a/.terraphim/scripts/bootstrap.py +++ b/.terraphim/scripts/bootstrap.py @@ -119,7 +119,9 @@ def to_repo_relative(loc: str) -> str: ) if changed: - json.dump(cfg, open(CFG, "w"), indent=2) + with open(CFG, "w", encoding="utf-8") as fh: + json.dump(cfg, fh, indent=2) + fh.write("\n") print(" config.json normalized (portable relative paths)") else: print(" config.json already portable (no path rewrite)")