{"id":50046758,"url":"https://github.com/patrick204nqh/textus","last_synced_at":"2026-05-25T10:00:56.749Z","repository":{"id":359006045,"uuid":"1242538999","full_name":"patrick204nqh/textus","owner":"patrick204nqh","description":"Context store for codebases shared by humans and AI agents — dotted keys, schemas, role-gated writes, byte-copy publish, audit log. Reference Ruby implementation of the textus/1 protocol.","archived":false,"fork":false,"pushed_at":"2026-05-23T07:56:16.000Z","size":604,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T08:03:15.678Z","etag":null,"topics":["agents","ai-agents","audit-log","claude","claude-code","claude-plugin","cli","context","knowledge-base","llm","markdown","memory","protocol","ruby","ruby-gem","yaml"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/textus","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patrick204nqh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-18T14:14:38.000Z","updated_at":"2026-05-23T07:55:21.000Z","dependencies_parsed_at":"2026-05-22T07:01:27.191Z","dependency_job_id":"8a5563f8-941c-47a3-ae46-47e6dc65ec57","html_url":"https://github.com/patrick204nqh/textus","commit_stats":null,"previous_names":["patrick204nqh/textus"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/patrick204nqh/textus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick204nqh%2Ftextus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick204nqh%2Ftextus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick204nqh%2Ftextus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick204nqh%2Ftextus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patrick204nqh","download_url":"https://codeload.github.com/patrick204nqh/textus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick204nqh%2Ftextus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33427584,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai-agents","audit-log","claude","claude-code","claude-plugin","cli","context","knowledge-base","llm","markdown","memory","protocol","ruby","ruby-gem","yaml"],"created_at":"2026-05-21T06:04:55.412Z","updated_at":"2026-05-24T09:01:29.780Z","avatar_url":"https://github.com/patrick204nqh.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# textus\n\n[![CI](https://github.com/patrick204nqh/textus/actions/workflows/ci.yml/badge.svg)](https://github.com/patrick204nqh/textus/actions/workflows/ci.yml)\n[![Gem Version](https://img.shields.io/gem/v/textus.svg)](https://rubygems.org/gems/textus)\n[![Ruby](https://img.shields.io/badge/ruby-%E2%89%A53.3-CC342D.svg)](https://www.ruby-lang.org/)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA context store for codebases that humans and AI agents both have to read and write. Dotted keys, schema-validated entries, role-gated writes, byte-copy publish, an audit log of every change. Built so an agent landing in your repo can run one command (`textus intro`) and know what to read, what to write, and what's off-limits.\n\nReference implementation in Ruby. Wire format `textus/1`. SPEC: [`SPEC.md`](SPEC.md). Implementation notes: [`docs/`](docs/).\n\n## Versioning\n\nTwo versions, deliberately independent:\n\n- **Protocol wire string:** `textus/1`. Stable; breaking changes require `textus/2`.\n- **Gem version:** semver, currently `0.2.0`. Gem `0.x.y` and `1.x` both speak `textus/1`.\n\nEnvelope payloads carry the `protocol` field. The gem version is irrelevant to the wire format.\n\n## Install\n\n```sh\ngem install textus\n```\n\nOr from this repo:\n\n```sh\nbundle install\nbundle exec exe/textus --help\n```\n\n## Quick start\n\n```sh\ntextus init\n```\n\nYou get `.textus/` with all five zone directories, baseline schemas, an empty audit log, and a starter manifest:\n\n```\n.textus/\n  manifest.yaml       # zone declarations + key-to-path mapping\n  audit.log           # append-only NDJSON, every write\n  schemas/            # YAML field shapes per entry family\n  templates/          # mustache templates for derived entries\n  extensions/         # one .rb per action / reducer / hook / doctor_check\n  sentinels/          # publish bookkeeping\n  zones/\n    canon/            # human-only — identity, voice, decisions\n    working/          # human / ai / script — day-to-day catalog\n    intake/           # script — declared external inputs (actions)\n    pending/          # ai + human — proposals awaiting accept\n    derived/          # build only — computed outputs\n```\n\nManifest `path:` fields are relative to `.textus/zones/`. So `working.network.org.jane` lives at `.textus/zones/working/network/org/jane.md`.\n\nRead and write:\n\n```sh\ntextus get working.network.org.jane --format=json\ntextus list --zone=working --format=json\necho '{\"frontmatter\":{\"name\":\"bob\",\"org\":\"acme\"},\"body\":\"hi\\n\"}' \\\n  | textus put working.network.org.bob --as=human --stdin --format=json\ntextus stale --zone=derived --format=json\n```\n\nFor the full shape — Claude plugin with agents, skills, commands, pending walkthrough, intake action — see [`examples/claude-plugin/`](examples/claude-plugin/).\n\n## What 0.2 ships\n\n- **Per-entry formats.** `format: markdown | json | yaml | text` on a manifest entry. `cat .textus/zones/derived/marketplace.json | jq .` works without going through textus — the in-store file *is* the consumer-shaped artifact. Structured outputs carry `_meta` at the top level (`generated_at`, `from`, `template`, `reducer`).\n- **Per-leaf publishing.** Nested entries declare `publish_each: \"skills/{basename}/SKILL.md\"`. Every leaf byte-copies to its consumer location on `textus build`. No more hand-mirrored `agents/` / `skills/` / `commands/` directories.\n- **Stable identity (`uid:`).** 16-char hex, auto-minted on first `put`, preserved across writes and moves. `textus key mv old.key new.key` renames in place — uid survives, audit row records `from_key`, `to_key`, `uid`. Reorganising a tree no longer breaks references.\n- **Strict key grammar.** `/^[a-z0-9][a-z0-9-]*$/`, max 8 segments × 64 chars. `textus key migrate --dry-run|--write` rewrites existing stores with illegal segments deterministically.\n- **`textus intro`.** One-shot store orientation: zones with writers + purposes, entry families with schemas and publish targets, loaded extensions, write flows per role, the full CLI verb table. The boot signal for any agent — one tool call and it knows your store.\n- **`textus doctor`.** Health check across 8 categories: missing schemas/templates, broken extensions, illegal nested keys, sentinel drift, audit log readability. Returns `ok: true` only when nothing is wrong; warnings and info don't flip the bit.\n- **Actionable hints on every error.** `UnknownKey` carries ranked \"did you mean\" suggestions. `WriteForbidden` names the role that *would* be allowed. `BadFrontmatter` tells you exactly what to rename. Printed to stderr alongside the JSON envelope on stdout.\n\nSymlink-mode publish was removed; publish is `FileUtils.cp` + sentinel. Sentinels for published files live under `.textus/sentinels/\u003ctarget_rel\u003e.textus-managed.json` so consumer directories stay clean. Legacy sibling sentinels auto-migrate on next publish.\n\n## CLI verbs\n\nAll verbs accept `--format=json` and return the envelope defined in SPEC §8. Write verbs require `--as=\u003crole\u003e` (role resolution: `--as` → `TEXTUS_ROLE` env → `.textus/role` file → default `human`).\n\n**Read:**\n\n| Verb | Purpose |\n|---|---|\n| `intro` | Store orientation: zones, entries, extensions, write flows, CLI map |\n| `list [--prefix=K] [--zone=Z]` | Enumerate keys |\n| `where K` | Resolve a key to its filesystem path |\n| `get K` | Full envelope (frontmatter, body, uid, etag, format) |\n| `schema show K` | Schema bound to an entry |\n| `stale [--prefix=K] [--zone=Z]` | List stale derived/intake entries |\n| `deps K` / `rdeps K` | Forward / reverse projection dependencies |\n| `published` | List `publish_to:` targets and their backing keys |\n| `doctor --check=schema_violations` | Validate every entry against its schema |\n| `extension list [--kind=K]` | Registered actions, reducers, hooks, doctor_checks |\n\n**Write:**\n\n| Verb | Role |\n|---|---|\n| `put K --stdin --as=R [--action=NAME]` | per zone |\n| `extension run NAME [--key=val] [--as=R]` | per zone written (invoke a registered action) |\n| `delete K --if-etag=E --as=R` | per zone |\n| `refresh K --as=script` | per zone (typically `script`) |\n| `key mv old new --as=R [--dry-run]` | per zone (same-zone moves; uid preserved) |\n| `build [--prefix=K] [--dry-run]` | `build` |\n| `accept K --as=human` | `human` only |\n\n**Health \u0026 maintenance:**\n\n| Verb | Purpose |\n|---|---|\n| `doctor` | 8 health checks; `ok: true` when clean |\n| `key migrate [--dry-run]` | Rename files whose basenames violate the strict key grammar |\n\n**Scaffolding (human-only):**\n\n| Verb | Purpose |\n|---|---|\n| `init` | Scaffold a fresh `.textus/` |\n| `schema init NAME` | Stub a schema |\n| `schema diff NAME` | Compare a schema against entries that claim it |\n| `schema migrate NAME [--rename=OLD:NEW]` | Rewrite frontmatter keys across affected entries |\n\n**Deprecated (removed in 0.6):** `mv`, `uid`, `migrate-keys`, `schema-init`, `schema-diff`, `schema-migrate`, `extensions`, `action`.\n\n## Zones and roles\n\n| Zone | `writable_by` | Purpose |\n|---|---|---|\n| `canon` | `[human]` | Identity, voice, decisions — slow-changing |\n| `working` | `[human, ai, script]` | Active project state |\n| `intake` | `[script]` | Declared external inputs (actions) |\n| `pending` | `[ai, human]` | AI proposals; humans run `textus accept` to apply |\n| `derived` | `[build]` | Computed outputs from `textus build` |\n\nMismatches return `write_forbidden` with a hint naming the role that *would* be allowed. Every write records the resolved role in `.textus/audit.log`.\n\n## Compute and publish\n\nDerived entries declare a `projection:` (`select`, `pluck`, `sort_by`, `limit`, optional `reducer`) and either a template under `.textus/templates/` (markdown/text) or a templateless path that lets a reducer shape the output directly (json/yaml). Projections cap at 1000 rows; the vendored Mustache subset caps at depth 8. No partials, no lambdas, no HTML escaping.\n\n`publish_to: [path]` byte-copies a single derived file to one target. `publish_each: \"template/{basename}.md\"` on a nested entry byte-copies every leaf to its templated target — substitutes `{leaf}`, `{basename}`, `{key}`, `{ext}`. Sentinels for every published file live under `.textus/sentinels/`. See SPEC §5.2, §5.3, §5.12.\n\n## Extensions\n\nFour DSL verbs, registered in `.textus/extensions/*.rb`. Each `Store` gets its own registry — no global state.\n\n- **`Textus.action(:name) do |config:, store:, args:|`** — runs in three invocation modes (intake refresh, `textus action` verb, `put --action`). Returns `{frontmatter:, body:}`, `{content:}`, or `{body:}` when its return is consumed (intake and put-fetch); writes via `store.put` for side-effectful work (verb mode). The store normalizes all three return shapes. Configured via `source.action` in the manifest for intake. Five built-ins ship: `json`, `csv`, `markdown-links`, `ical-events`, `rss`.\n- **`Textus.reducer(:name) do |rows:, config:|`** — shapes rows in a derived projection. Pure function. Configured via `projection.reducer`. May return an Array (templated builds) or a Hash (templateless json/yaml).\n- **`Textus.hook(:event, :name) do |kwargs|`** — fires on `:put`, `:delete`, `:refresh`, `:build`, or `:accept`. In-process; 2 s timeout per hook; failures land in the audit log as `event_error` rows.\n- **`Textus.doctor_check(:name) do |store:|`** — contributes whole-tree validators to `textus doctor`. Returns an array of issue hashes `{code, level, subject, message, fix}` that merge into the doctor report. Timeouts and exceptions surface as `doctor_check.*` issues; they do not abort the doctor run.\n\nSchemas (`.textus/schemas/\u003cname\u003e.yaml`) declare field shapes, per-field `maintained_by:` ownership, and an `evolution:` block (`added_in`, `deprecated_at`, `migrate_from`). Full contract in SPEC §5.8 and §5.11.\n\n## Examples\n\n[`examples/claude-plugin/`](examples/claude-plugin/) — a Claude Code plugin (`voice-tools`) whose entire content surface — agents, skills, commands, `CLAUDE.md`, `plugin.json`, `marketplace.json` — is textus-managed. Demonstrates per-entry formats, `publish_each`, intake actions, in-process reducers and hooks, the AI-propose / human-accept loop, and the `inject_intro:` flag that puts an orientation preamble at the top of `CLAUDE.md`.\n\n## Tests\n\n```sh\nbundle exec rspec\n```\n\n240 examples; includes conformance fixtures A–I from SPEC §12.\n\n## Code quality\n\n```sh\nbundle exec rubocop      # lint\nbundle exec rubocop -A   # lint + autocorrect\n```\n\nLefthook hooks (`brew bundle install` then `lefthook install`) run rubocop on `pre-commit` and `rspec + rubocop` on `pre-push`. Bypass with `LEFTHOOK=0 git commit ...` when needed. CI runs `rspec` (Ruby 3.3 / 3.4) and `rubocop` via GitHub Actions.\n\n## License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrick204nqh%2Ftextus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatrick204nqh%2Ftextus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrick204nqh%2Ftextus/lists"}