textus 0.30.0 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +2 -241
- data/CHANGELOG.md +113 -0
- data/README.md +83 -62
- data/SPEC.md +352 -211
- data/docs/conventions.md +42 -37
- data/lib/textus/boot.rb +89 -74
- data/lib/textus/cli/group/{refresh.rb → fetch.rb} +4 -4
- data/lib/textus/cli/verb/build.rb +1 -1
- data/lib/textus/cli/verb/fetch.rb +14 -0
- data/lib/textus/cli/verb/{refresh_stale.rb → fetch_stale.rb} +3 -3
- data/lib/textus/cli/verb/get.rb +1 -1
- data/lib/textus/cli/verb/hooks.rb +1 -1
- data/lib/textus/cli/verb/put.rb +1 -1
- data/lib/textus/cli/verb/rule_list.rb +7 -7
- data/lib/textus/cli.rb +2 -2
- data/lib/textus/container.rb +1 -2
- data/lib/textus/dispatcher.rb +3 -3
- data/lib/textus/doctor/check/{refresh_locks.rb → fetch_locks.rb} +7 -7
- data/lib/textus/doctor/check/proposal_targets.rb +45 -0
- data/lib/textus/doctor/check/rule_ambiguity.rb +3 -3
- data/lib/textus/doctor.rb +2 -1
- data/lib/textus/domain/action.rb +3 -3
- data/lib/textus/domain/freshness/evaluator.rb +3 -3
- data/lib/textus/domain/freshness/policy.rb +2 -2
- data/lib/textus/domain/freshness.rb +7 -7
- data/lib/textus/domain/outcome.rb +2 -2
- data/lib/textus/domain/permission.rb +2 -10
- data/lib/textus/domain/policy/base_guards.rb +25 -0
- data/lib/textus/domain/policy/evaluation.rb +18 -0
- data/lib/textus/domain/policy/{refresh.rb → fetch.rb} +1 -1
- data/lib/textus/domain/policy/guard.rb +35 -0
- data/lib/textus/domain/policy/guard_factory.rb +40 -0
- data/lib/textus/domain/policy/predicates/author_held.rb +33 -0
- data/lib/textus/domain/policy/predicates/etag_match.rb +32 -0
- data/lib/textus/domain/policy/predicates/fresh_within.rb +58 -0
- data/lib/textus/domain/policy/predicates/registry.rb +39 -0
- data/lib/textus/domain/policy/predicates/schema_valid.rb +30 -19
- data/lib/textus/domain/policy/predicates/target_is_canon.rb +33 -0
- data/lib/textus/domain/policy/predicates/zone_writable_by.rb +39 -0
- data/lib/textus/domain/staleness/intake_check.rb +6 -6
- data/lib/textus/envelope.rb +2 -2
- data/lib/textus/errors.rb +25 -28
- data/lib/textus/hooks/event_bus.rb +4 -4
- data/lib/textus/init.rb +23 -18
- data/lib/textus/maintenance/zone_mv.rb +1 -1
- data/lib/textus/manifest/capabilities.rb +29 -0
- data/lib/textus/manifest/data.rb +14 -10
- data/lib/textus/manifest/policy.rb +37 -21
- data/lib/textus/manifest/rules.rb +16 -14
- data/lib/textus/manifest/schema.rb +48 -58
- data/lib/textus/manifest.rb +3 -3
- data/lib/textus/mcp/server.rb +1 -1
- data/lib/textus/mcp/tool_schemas.rb +3 -3
- data/lib/textus/mcp/tools.rb +7 -7
- data/lib/textus/ports/audit_subscriber.rb +1 -1
- data/lib/textus/ports/{refresh → fetch}/detached.rb +4 -4
- data/lib/textus/ports/{refresh → fetch}/lock.rb +1 -1
- data/lib/textus/projection.rb +1 -1
- data/lib/textus/read/freshness.rb +9 -9
- data/lib/textus/read/get.rb +8 -8
- data/lib/textus/read/{get_or_refresh.rb → get_or_fetch.rb} +11 -11
- data/lib/textus/read/policy_explain.rb +14 -10
- data/lib/textus/read/pulse.rb +5 -4
- data/lib/textus/read/validator.rb +1 -1
- data/lib/textus/schema/tools.rb +5 -5
- data/lib/textus/version.rb +1 -1
- data/lib/textus/write/accept.rb +19 -55
- data/lib/textus/write/delete.rb +14 -2
- data/lib/textus/write/{refresh_all.rb → fetch_all.rb} +6 -6
- data/lib/textus/write/{refresh_orchestrator.rb → fetch_orchestrator.rb} +14 -14
- data/lib/textus/write/{refresh_worker.rb → fetch_worker.rb} +21 -14
- data/lib/textus/write/mv.rb +15 -3
- data/lib/textus/write/put.rb +14 -2
- data/lib/textus/write/reject.rb +11 -5
- metadata +24 -18
- data/lib/textus/cli/verb/refresh.rb +0 -14
- data/lib/textus/domain/authorizer.rb +0 -37
- data/lib/textus/domain/policy/predicates/accept_authority_signed.rb +0 -33
- data/lib/textus/domain/policy/promote.rb +0 -26
- data/lib/textus/domain/policy/promotion.rb +0 -57
- data/lib/textus/manifest/role_kinds.rb +0 -21
- data/lib/textus/write/authority_gate.rb +0 -24
data/README.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/branding/wordmark-dark.png">
|
|
4
|
+
<img src="docs/assets/branding/wordmark.png" alt="textus" width="360">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/patrick204nqh/textus/actions/workflows/ci.yml"><img src="https://github.com/patrick204nqh/textus/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://rubygems.org/gems/textus"><img src="https://img.shields.io/gem/v/textus.svg" alt="Gem Version"></a>
|
|
11
|
+
<a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%E2%89%A53.3-CC342D.svg" alt="Ruby"></a>
|
|
12
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
13
|
+
</p>
|
|
7
14
|
|
|
8
|
-
**
|
|
15
|
+
**A coordination space for humans, AI, and automation.** Your agent forgets between sessions; your notes and `CLAUDE.md` get edited by whoever ran last; nobody can reconstruct who wrote what. textus is durable, multi-writer memory that stays current and survives the model, the session, and the vendor — you keep your space, agents keep theirs, automation keeps external data fresh, and every change crosses a review queue and an audit log.
|
|
9
16
|
|
|
10
|
-
*textus* is Latin for "the fabric a text is woven from" — same root as *context*, from *con-texere*, "to weave together."
|
|
17
|
+
*textus* is Latin for "the fabric a text is woven from" — same root as *context*, from *con-texere*, "to weave together."
|
|
11
18
|
|
|
12
19
|
## The idea
|
|
13
20
|
|
|
@@ -15,19 +22,31 @@ Three actors write to your repo today:
|
|
|
15
22
|
|
|
16
23
|
- **Humans** — you, your team. Authoritative on identity, decisions, voice.
|
|
17
24
|
- **Agents** — Claude, Cursor, custom assistants. Smart, fast, forgetful, and not always right.
|
|
18
|
-
- **
|
|
25
|
+
- **Automation** — cron jobs, fetchers, CI. Bring outside data in and compile published artifacts.
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
human(["human"]) -->|author| knowledge["knowledge<br/>(canon)"]
|
|
30
|
+
agent(["agent"]) -->|keep| notebook["notebook<br/>(workspace)"]
|
|
31
|
+
agent -->|propose| proposals["proposals<br/>(queue)"]
|
|
32
|
+
proposals -->|human accepts| knowledge
|
|
33
|
+
automation(["automation"]) -->|fetch| feeds["feeds<br/>(quarantine)"]
|
|
34
|
+
automation -->|build| artifacts["artifacts<br/>(derived)"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
*Each actor writes only into its own lane; low-trust input climbs to authoritative lanes only by passing a guarded transition (an agent's proposal needs a human `accept`).*
|
|
19
38
|
|
|
20
|
-
Without coordination, they overwrite each other and nothing remembers why. textus gives each actor a **lane**
|
|
39
|
+
Without coordination, they overwrite each other and nothing remembers why. textus gives each actor a **lane** — called a **zone** in the manifest and CLI, the term used everywhere technical from here on — routes everything they can't write directly through a **proposals queue**, and writes every successful change to an **append-only audit log**. The lanes are enforced at the protocol level, not by convention.
|
|
21
40
|
|
|
22
41
|
```
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
knowledge/ author only — who you are, what you decide, how you sound (knowledge.identity.* for identity facts)
|
|
43
|
+
notebook/ keep only — agent's own durable lane (agents keep theirs; bytes climb to knowledge only via propose→accept)
|
|
44
|
+
feeds/ fetch only — declared external inputs
|
|
45
|
+
proposals/ propose (agent + human) — proposals waiting on a human accept
|
|
46
|
+
artifacts/ build only — computed, published artifacts
|
|
28
47
|
```
|
|
29
48
|
|
|
30
|
-
An agent that tries to write directly into `
|
|
49
|
+
An agent that tries to write directly into `knowledge/` gets `write_forbidden`. It writes to `proposals/` (to change authoritative content) or its own `notebook/` (for working memory). You accept the good proposals; textus promotes them, records the move, and audits both halves. Stable per-entry `uid:` means a reorganization doesn't break references. A monotonic audit cursor (`textus pulse --since=N`) means the next session — possibly a different agent, possibly a different model — picks up exactly where the last one left off.
|
|
31
50
|
|
|
32
51
|
That's the load-bearing claim: **coordination is a protocol invariant, not a library convenience.**
|
|
33
52
|
|
|
@@ -36,19 +55,19 @@ That's the load-bearing claim: **coordination is a protocol invariant, not a lib
|
|
|
36
55
|
```sh
|
|
37
56
|
gem install textus
|
|
38
57
|
textus init # creates .textus/ with zones + schemas
|
|
39
|
-
# agent proposes a change to
|
|
40
|
-
printf '%s' '{"_meta":{"name":"oncall","proposal":{"target_key":"
|
|
41
|
-
| textus put
|
|
42
|
-
# you accept it — textus promotes to
|
|
43
|
-
textus accept
|
|
58
|
+
# agent proposes a change to proposals/
|
|
59
|
+
printf '%s' '{"_meta":{"name":"oncall","proposal":{"target_key":"knowledge.notes.oncall","action":"put"}},"body":"Patrick on call.\n"}' \
|
|
60
|
+
| textus put proposals.notes.oncall --as=agent --stdin
|
|
61
|
+
# you accept it — textus promotes to knowledge/ and audits the move
|
|
62
|
+
textus accept proposals.notes.oncall --as=human
|
|
44
63
|
```
|
|
45
64
|
|
|
46
|
-
Try the gate the other way (`textus put
|
|
65
|
+
Try the gate the other way (`textus put knowledge.notes.X --as=agent`) and you get `write_forbidden`, with the role that *would* be allowed named in the error. That refusal is the whole point.
|
|
47
66
|
|
|
48
67
|
## Try it
|
|
49
68
|
|
|
50
69
|
- **5-command worked demo** — single terminal scroll, no MCP, no schemas: [`examples/hello/`](examples/hello/)
|
|
51
|
-
- **Wire textus into Claude Code via MCP** — 4 steps, ~5 minutes: [`
|
|
70
|
+
- **Wire textus into Claude Code via MCP** — 4 steps, ~5 minutes: [`docs/agents-mcp.md`](docs/agents-mcp.md)
|
|
52
71
|
- **Use textus as your own project's context store**: [`examples/project/`](examples/project/)
|
|
53
72
|
- **Use textus to author a Claude plugin** (textus is the source-of-truth, build publishes to `agents/`, `skills/`, `commands/`): [`examples/claude-plugin/`](examples/claude-plugin/)
|
|
54
73
|
|
|
@@ -57,7 +76,7 @@ Try the gate the other way (`textus put working.notes.X --as=agent`) and you get
|
|
|
57
76
|
This Ruby gem is the reference implementation of **`textus/3`** — a wire format and storage convention any language can speak. The protocol owns the envelope shape, the role/zone gate, the audit log format, and the key grammar. The gem version (semver, see badge) and the protocol version (`textus/3`) move independently; envelopes carry the `protocol` field so consumers can pin to the contract, not the implementation.
|
|
58
77
|
|
|
59
78
|
- Specification: [`SPEC.md`](SPEC.md)
|
|
60
|
-
- Architecture: [`
|
|
79
|
+
- Architecture: [`docs/architecture/README.md`](docs/architecture/README.md)
|
|
61
80
|
- Per-release notes: [`CHANGELOG.md`](CHANGELOG.md)
|
|
62
81
|
|
|
63
82
|
A second implementation in another language would share the same `.textus/` directory and the same audit log. That's deliberate.
|
|
@@ -75,40 +94,50 @@ bundle install
|
|
|
75
94
|
bundle exec exe/textus --help
|
|
76
95
|
```
|
|
77
96
|
|
|
78
|
-
##
|
|
97
|
+
## What `textus init` gives you
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
textus init
|
|
82
|
-
```
|
|
99
|
+
You get `.textus/` with all five zone directories, baseline schemas, an empty audit log, and a starter manifest. Roles declare capabilities; each zone declares a `kind:`, and write authority is derived from the role's capabilities crossed with the zone's kind:
|
|
83
100
|
|
|
84
|
-
|
|
101
|
+
```yaml
|
|
102
|
+
roles:
|
|
103
|
+
- { name: human, can: [author, propose] }
|
|
104
|
+
- { name: agent, can: [propose, keep] }
|
|
105
|
+
- { name: automation, can: [fetch, build] }
|
|
106
|
+
|
|
107
|
+
zones:
|
|
108
|
+
- { name: knowledge, kind: canon } # author — canonical truth
|
|
109
|
+
- { name: notebook, kind: workspace } # keep — agent's own durable lane
|
|
110
|
+
- { name: feeds, kind: quarantine } # fetch — declared external inputs
|
|
111
|
+
- { name: proposals, kind: queue } # propose — proposals awaiting accept
|
|
112
|
+
- { name: artifacts, kind: derived } # build — computed outputs
|
|
113
|
+
```
|
|
85
114
|
|
|
86
115
|
```
|
|
87
116
|
.textus/
|
|
88
|
-
manifest.yaml # zone
|
|
117
|
+
manifest.yaml # role capabilities + zone kinds + key-to-path mapping
|
|
89
118
|
audit.log # append-only NDJSON, every write
|
|
90
119
|
schemas/ # YAML field shapes per entry family
|
|
91
120
|
templates/ # mustache templates for derived entries
|
|
92
121
|
hooks/ # one .rb per hook
|
|
93
122
|
sentinels/ # publish bookkeeping
|
|
94
123
|
zones/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
knowledge/ # author — identity (knowledge.identity.*), voice, decisions, notes
|
|
125
|
+
notebook/ # keep — agent's own durable lane (agents keep theirs)
|
|
126
|
+
feeds/ # fetch — declared external inputs (actions)
|
|
127
|
+
proposals/ # propose (agent + human) — proposals awaiting accept
|
|
128
|
+
artifacts/ # build — computed outputs
|
|
100
129
|
```
|
|
101
130
|
|
|
102
|
-
Manifest `path:` fields are relative to `.textus/zones/`. So `
|
|
131
|
+
Manifest `path:` fields are relative to `.textus/zones/`. So `knowledge.notes.org.jane` lives at `.textus/zones/knowledge/notes/org/jane.md`.
|
|
103
132
|
|
|
104
133
|
Read and write:
|
|
105
134
|
|
|
106
135
|
```sh
|
|
107
|
-
textus get
|
|
108
|
-
textus list --zone=
|
|
136
|
+
textus get knowledge.notes.org.jane
|
|
137
|
+
textus list --zone=knowledge
|
|
109
138
|
printf '%s' '{"_meta":{"name":"bob","org":"acme"},"body":"hi\n"}' \
|
|
110
|
-
| textus put
|
|
111
|
-
textus freshness --zone=
|
|
139
|
+
| textus put knowledge.notes.bob --as=human --stdin
|
|
140
|
+
textus freshness --zone=artifacts # per-entry fresh/stale/never_fetched/no_policy
|
|
112
141
|
textus rule list # show every rule block
|
|
113
142
|
textus audit --limit=20 # query the audit log
|
|
114
143
|
```
|
|
@@ -119,26 +148,18 @@ For the full shape — Claude plugin with agents, skills, commands, pending walk
|
|
|
119
148
|
|
|
120
149
|
## What's shipped
|
|
121
150
|
|
|
122
|
-
- **Per-entry formats.** `format: markdown
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
125
|
-
- **
|
|
126
|
-
-
|
|
127
|
-
- **Strict key grammar.** `/^[a-z0-9][a-z0-9-]*$/`, max 8 segments × 64 chars. `textus doctor` flags any illegal segments with a rename hint; `textus key mv old.key new.key` renames in place (uid survives).
|
|
128
|
-
- **`textus boot`.** One-shot store orientation: zones with writers + purposes, entry families with schemas and publish targets, loaded hooks, write flows per role, the full CLI verb table, and an `agent_quickstart` block (read/write verbs, writable zones, propose zone, latest audit seq).
|
|
129
|
-
- **`textus pulse [--since=N]`.** Per-turn heartbeat for agents: changed entries since cursor N, stale keys, pending review proposals, and a doctor summary. Cursor is a monotonic seq stamped on every audit row; rotation keeps the last 5 files (configurable via `audit:` in the manifest) and raises `CursorExpired` when the requested cursor has fallen off disk.
|
|
130
|
-
- **`textus doctor`.** Health check across 15 checks — among them: missing schemas/templates, broken hooks, illegal nested keys, sentinel drift, audit log readability, unowned schema fields, schema violations, and missing manifest files. Returns `ok: true` only when nothing is wrong; warnings and info don't flip the bit.
|
|
131
|
-
- **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.
|
|
132
|
-
- **Compute.** Derived entries declare `compute: { kind: projection, ... }` (declarative rows + template) or `compute: { kind: external, ... }` (build runner produces the file; textus tracks sources for staleness). Inside projection computes, `transform:` names the row-shaping hook.
|
|
133
|
-
|
|
134
|
-
Symlink-mode publish was removed; publish is `FileUtils.cp` + sentinel. Sentinels for published files live under `.textus/sentinels/<target_rel>.textus-managed.json` so consumer directories stay clean. Legacy sibling sentinels auto-migrate on next publish.
|
|
151
|
+
- **Per-entry formats & publish.** `format: markdown|json|yaml|text` per entry; `publish_to:`/`publish_each:` byte-copy derived files to their consumer paths. ([SPEC §5.2–5.3](SPEC.md))
|
|
152
|
+
- **Stable identity.** Auto-minted `uid:` survives writes and `textus key mv`; reorganising never breaks references.
|
|
153
|
+
- **Capability × zone-kind gate.** Writes carry `--as=<role>`; a role may write a zone iff it holds the capability the zone's `kind:` requires (`canon`→`author`, `workspace`→`keep`, `quarantine`→`fetch`, `queue`→`propose`, `derived`→`build`). The wrong role gets `write_forbidden` naming the capability needed and the roles that hold it. ([SPEC §5](SPEC.md))
|
|
154
|
+
- **Agent loop.** `textus boot` orients a fresh session; `textus pulse --since=N` is the per-turn heartbeat (changed entries, stale keys, pending proposals). ([docs/agents-mcp.md](docs/agents-mcp.md))
|
|
155
|
+
- **`textus doctor`.** 15 health checks across schemas, hooks, keys, sentinels, and the audit log.
|
|
135
156
|
|
|
136
157
|
## CLI and zones
|
|
137
158
|
|
|
138
|
-
All verbs accept `--output=json` and return the envelope defined in [SPEC §8](SPEC.md). Write verbs require `--as=<role>` (role resolution: `--as` → `TEXTUS_ROLE` env → `.textus/role` file → default `human`).
|
|
159
|
+
All verbs accept `--output=json` and return the envelope defined in [SPEC §8](SPEC.md). Write verbs require `--as=<role>` (role resolution: `--as` → `TEXTUS_ROLE` env → `.textus/role` file → default `human`). Default roles: `human`, `agent`, `automation` (rename or add your own in the manifest's `roles:` block).
|
|
139
160
|
|
|
140
161
|
- Full verb table — read, write, health, scaffolding — is in [SPEC §9](SPEC.md).
|
|
141
|
-
- Zone semantics and the
|
|
162
|
+
- Zone semantics and the capability × zone-kind mapping live in [SPEC §5](SPEC.md), with a tutorial expansion in [`docs/zones.md`](docs/zones.md).
|
|
142
163
|
|
|
143
164
|
`textus boot` prints the same information for the current store: zones, entry families with schemas, registered hooks, write flows, and the verb catalog. Run it inside a store and you get the live picture; reach for the SPEC when you want the contract.
|
|
144
165
|
|
|
@@ -146,7 +167,7 @@ All verbs accept `--output=json` and return the envelope defined in [SPEC §8](S
|
|
|
146
167
|
|
|
147
168
|
Derived entries declare `compute: { kind: projection, select: ..., pluck: ..., sort_by: ..., limit: ..., transform: name }` and either a template under `.textus/templates/` (markdown/text) or a templateless path that lets a transform hook 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.
|
|
148
169
|
|
|
149
|
-
For externally-generated entries, declare `compute: { kind: external, sources: [...] }` — textus tracks the declared sources for staleness; the build
|
|
170
|
+
For externally-generated entries, declare `compute: { kind: external, sources: [...] }` — textus tracks the declared sources for staleness; the build automation produces the file.
|
|
150
171
|
|
|
151
172
|
`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.
|
|
152
173
|
|
|
@@ -157,8 +178,8 @@ textus exposes a hook DSL. Drop `.rb` files into `.textus/hooks/` (subdirectorie
|
|
|
157
178
|
- `:resolve_intake` — bring bytes in from elsewhere (returns `{_meta:, body:}`)
|
|
158
179
|
- `:transform_rows` — transform rows during projection (returns rows)
|
|
159
180
|
- `:validate` — custom doctor check (returns issues)
|
|
160
|
-
- `:entry_put`, `:entry_deleted`, `:
|
|
161
|
-
- `:
|
|
181
|
+
- `:entry_put`, `:entry_deleted`, `:entry_fetched`, `:build_completed`, `:proposal_accepted`, `:file_published`, `:entry_renamed`, `:proposal_rejected`, `:store_loaded` — react to lifecycle events
|
|
182
|
+
- `:fetch_started`, `:fetch_failed`, `:fetch_backgrounded` — background-fetch lifecycle
|
|
162
183
|
|
|
163
184
|
```ruby
|
|
164
185
|
# Inside .textus/hooks/local_file.rb
|
|
@@ -166,7 +187,7 @@ Textus.hook do |reg|
|
|
|
166
187
|
reg.on(:resolve_intake, :local_file) do |config:, args:, **|
|
|
167
188
|
path = config["path"] or raise "local-file requires intake.config.path"
|
|
168
189
|
{
|
|
169
|
-
_meta: { "
|
|
190
|
+
_meta: { "last_fetched_at" => Time.now.utc.iso8601, "source_path" => path },
|
|
170
191
|
body: File.read(File.expand_path(path)),
|
|
171
192
|
}
|
|
172
193
|
end
|
|
@@ -184,16 +205,16 @@ end
|
|
|
184
205
|
To keep a batch of stale intake entries current in one shot:
|
|
185
206
|
|
|
186
207
|
```sh
|
|
187
|
-
textus
|
|
188
|
-
# or just
|
|
189
|
-
textus
|
|
208
|
+
textus fetch stale --prefix=feeds --zone=feeds --as=automation
|
|
209
|
+
# or just fetch everything stale in the feeds zone:
|
|
210
|
+
textus fetch stale --zone=feeds --as=automation
|
|
190
211
|
```
|
|
191
212
|
|
|
192
213
|
See SPEC.md §5.10 for the full hook contract.
|
|
193
214
|
|
|
194
215
|
Schemas (`.textus/schemas/<name>.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.
|
|
195
216
|
|
|
196
|
-
See [`docs/
|
|
217
|
+
See [`docs/agents-mcp.md`](docs/agents-mcp.md) for the agent boot → pulse loop.
|
|
197
218
|
|
|
198
219
|
## Examples
|
|
199
220
|
|