textus 0.35.1 → 0.39.0
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/CHANGELOG.md +135 -2
- data/README.md +34 -13
- data/SPEC.md +10 -4
- data/lib/textus/boot.rb +41 -21
- data/lib/textus/cli/verb/mcp_serve.rb +8 -3
- data/lib/textus/cli/verb/propose.rb +28 -0
- data/lib/textus/cli/verb/pulse.rb +12 -3
- data/lib/textus/cli/verb/schema.rb +1 -1
- data/lib/textus/cli/verb.rb +3 -2
- data/lib/textus/contract.rb +106 -0
- data/lib/textus/cursor_store.rb +24 -0
- data/lib/textus/dispatcher.rb +3 -1
- data/lib/textus/doctor/check/audit_log.rb +1 -1
- data/lib/textus/doctor/check/fetch_locks.rb +2 -2
- data/lib/textus/doctor/check/illegal_keys.rb +10 -4
- data/lib/textus/domain/policy/evaluation.rb +3 -6
- data/lib/textus/init.rb +4 -0
- data/lib/textus/layout.rb +41 -0
- data/lib/textus/maintenance/key_delete_prefix.rb +9 -0
- data/lib/textus/maintenance/key_mv_prefix.rb +10 -0
- data/lib/textus/maintenance/migrate.rb +9 -0
- data/lib/textus/maintenance/rule_lint.rb +8 -0
- data/lib/textus/maintenance/zone_mv.rb +10 -0
- data/lib/textus/manifest/entry/base.rb +5 -0
- data/lib/textus/manifest/entry/ignore_matcher.rb +46 -0
- data/lib/textus/manifest/entry/nested.rb +9 -2
- data/lib/textus/manifest/entry/validators/ignore.rb +28 -0
- data/lib/textus/manifest/entry/validators.rb +1 -0
- data/lib/textus/manifest/resolver.rb +2 -0
- data/lib/textus/manifest/schema.rb +1 -1
- data/lib/textus/mcp/catalog.rb +72 -0
- data/lib/textus/mcp/server.rb +8 -5
- data/lib/textus/mcp/session.rb +3 -20
- data/lib/textus/mcp/tool_schemas.rb +6 -62
- data/lib/textus/mcp/tools.rb +4 -119
- data/lib/textus/ports/audit_log.rb +17 -15
- data/lib/textus/ports/build_lock.rb +1 -2
- data/lib/textus/ports/fetch/lock.rb +1 -1
- data/lib/textus/read/audit.rb +3 -3
- data/lib/textus/read/boot.rb +6 -0
- data/lib/textus/read/get.rb +8 -0
- data/lib/textus/read/list.rb +8 -0
- data/lib/textus/read/pulse.rb +7 -0
- data/lib/textus/read/rules.rb +24 -0
- data/lib/textus/read/schema_envelope.rb +7 -0
- data/lib/textus/role.rb +6 -2
- data/lib/textus/session.rb +24 -0
- data/lib/textus/store.rb +11 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus/write/accept.rb +1 -1
- data/lib/textus/write/delete.rb +1 -1
- data/lib/textus/write/fetch_all.rb +8 -0
- data/lib/textus/write/fetch_worker.rb +9 -1
- data/lib/textus/write/mv.rb +1 -1
- data/lib/textus/write/propose.rb +46 -0
- data/lib/textus/write/put.rb +13 -1
- data/lib/textus/write/reject.rb +1 -1
- data/lib/textus.rb +4 -0
- metadata +15 -5
- data/docs/conventions.md +0 -148
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.39.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -93,8 +93,9 @@ dependencies:
|
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '3.13'
|
|
96
|
-
description:
|
|
97
|
-
memory
|
|
96
|
+
description: A coordination space for humans, AI, and automation. Durable, multi-writer
|
|
97
|
+
project memory where each actor writes into its own lane, proposals cross a review
|
|
98
|
+
queue, and every change is audited.
|
|
98
99
|
email:
|
|
99
100
|
- patrick204nqh@gmail.com
|
|
100
101
|
executables:
|
|
@@ -106,7 +107,6 @@ files:
|
|
|
106
107
|
- CHANGELOG.md
|
|
107
108
|
- README.md
|
|
108
109
|
- SPEC.md
|
|
109
|
-
- docs/conventions.md
|
|
110
110
|
- exe/textus
|
|
111
111
|
- lib/textus.rb
|
|
112
112
|
- lib/textus/boot.rb
|
|
@@ -147,6 +147,7 @@ files:
|
|
|
147
147
|
- lib/textus/cli/verb/mcp_serve.rb
|
|
148
148
|
- lib/textus/cli/verb/migrate.rb
|
|
149
149
|
- lib/textus/cli/verb/mv.rb
|
|
150
|
+
- lib/textus/cli/verb/propose.rb
|
|
150
151
|
- lib/textus/cli/verb/published.rb
|
|
151
152
|
- lib/textus/cli/verb/pulse.rb
|
|
152
153
|
- lib/textus/cli/verb/put.rb
|
|
@@ -164,6 +165,8 @@ files:
|
|
|
164
165
|
- lib/textus/cli/verb/where.rb
|
|
165
166
|
- lib/textus/cli/verb/zone_mv.rb
|
|
166
167
|
- lib/textus/container.rb
|
|
168
|
+
- lib/textus/contract.rb
|
|
169
|
+
- lib/textus/cursor_store.rb
|
|
167
170
|
- lib/textus/dispatcher.rb
|
|
168
171
|
- lib/textus/doctor.rb
|
|
169
172
|
- lib/textus/doctor/check.rb
|
|
@@ -234,6 +237,7 @@ files:
|
|
|
234
237
|
- lib/textus/key/distance.rb
|
|
235
238
|
- lib/textus/key/grammar.rb
|
|
236
239
|
- lib/textus/key/path.rb
|
|
240
|
+
- lib/textus/layout.rb
|
|
237
241
|
- lib/textus/maintenance.rb
|
|
238
242
|
- lib/textus/maintenance/key_delete_prefix.rb
|
|
239
243
|
- lib/textus/maintenance/key_mv_prefix.rb
|
|
@@ -246,6 +250,7 @@ files:
|
|
|
246
250
|
- lib/textus/manifest/entry.rb
|
|
247
251
|
- lib/textus/manifest/entry/base.rb
|
|
248
252
|
- lib/textus/manifest/entry/derived.rb
|
|
253
|
+
- lib/textus/manifest/entry/ignore_matcher.rb
|
|
249
254
|
- lib/textus/manifest/entry/intake.rb
|
|
250
255
|
- lib/textus/manifest/entry/leaf.rb
|
|
251
256
|
- lib/textus/manifest/entry/nested.rb
|
|
@@ -253,6 +258,7 @@ files:
|
|
|
253
258
|
- lib/textus/manifest/entry/validators.rb
|
|
254
259
|
- lib/textus/manifest/entry/validators/events.rb
|
|
255
260
|
- lib/textus/manifest/entry/validators/format_matrix.rb
|
|
261
|
+
- lib/textus/manifest/entry/validators/ignore.rb
|
|
256
262
|
- lib/textus/manifest/entry/validators/index_filename.rb
|
|
257
263
|
- lib/textus/manifest/entry/validators/inject_boot.rb
|
|
258
264
|
- lib/textus/manifest/entry/validators/publish_each.rb
|
|
@@ -261,6 +267,7 @@ files:
|
|
|
261
267
|
- lib/textus/manifest/rules.rb
|
|
262
268
|
- lib/textus/manifest/schema.rb
|
|
263
269
|
- lib/textus/mcp.rb
|
|
270
|
+
- lib/textus/mcp/catalog.rb
|
|
264
271
|
- lib/textus/mcp/errors.rb
|
|
265
272
|
- lib/textus/mcp/server.rb
|
|
266
273
|
- lib/textus/mcp/session.rb
|
|
@@ -292,6 +299,7 @@ files:
|
|
|
292
299
|
- lib/textus/read/pulse.rb
|
|
293
300
|
- lib/textus/read/rdeps.rb
|
|
294
301
|
- lib/textus/read/retainable.rb
|
|
302
|
+
- lib/textus/read/rules.rb
|
|
295
303
|
- lib/textus/read/schema_envelope.rb
|
|
296
304
|
- lib/textus/read/stale.rb
|
|
297
305
|
- lib/textus/read/uid.rb
|
|
@@ -303,6 +311,7 @@ files:
|
|
|
303
311
|
- lib/textus/schema.rb
|
|
304
312
|
- lib/textus/schema/tools.rb
|
|
305
313
|
- lib/textus/schemas.rb
|
|
314
|
+
- lib/textus/session.rb
|
|
306
315
|
- lib/textus/store.rb
|
|
307
316
|
- lib/textus/uid.rb
|
|
308
317
|
- lib/textus/version.rb
|
|
@@ -314,6 +323,7 @@ files:
|
|
|
314
323
|
- lib/textus/write/intake_fetch.rb
|
|
315
324
|
- lib/textus/write/materializer.rb
|
|
316
325
|
- lib/textus/write/mv.rb
|
|
326
|
+
- lib/textus/write/propose.rb
|
|
317
327
|
- lib/textus/write/publish.rb
|
|
318
328
|
- lib/textus/write/put.rb
|
|
319
329
|
- lib/textus/write/reject.rb
|
|
@@ -344,5 +354,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
344
354
|
requirements: []
|
|
345
355
|
rubygems_version: 3.6.9
|
|
346
356
|
specification_version: 4
|
|
347
|
-
summary: Reference implementation of the textus/
|
|
357
|
+
summary: Reference implementation of the textus/3 protocol.
|
|
348
358
|
test_files: []
|
data/docs/conventions.md
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# Conventions
|
|
2
|
-
|
|
3
|
-
> **Reference** · for integrators · **read when** you're shaping a `.textus/` tree and want the idiomatic choices
|
|
4
|
-
> **SSoT for** idiomatic key naming, schema design, and automation integration · **reviewed** 2026-05 (v0.31)
|
|
5
|
-
|
|
6
|
-
Guidelines for shaping a `.textus/` tree, naming keys, organising schemas, and integrating with build automation. The spec ([`../SPEC.md`](../SPEC.md)) defines what's enforceable; this document captures what's *idiomatic*.
|
|
7
|
-
|
|
8
|
-
## Key naming
|
|
9
|
-
|
|
10
|
-
- **Segments are lowercase, kebab- or snake-case.** The grammar `^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$` is the hard limit. Prefer `acme-dashboard` over `acmedashboard` when there's a natural word break.
|
|
11
|
-
- **Lead with the zone in the key path.** `working.projects.acme.dashboard`, not `projects.acme.dashboard`. The zone prefix makes it obvious from the key alone whether a write will be accepted.
|
|
12
|
-
- **Mirror the directory structure.** If `working.projects.acme.dashboard` resolves to `working/projects/acme/dashboard.md`, do not invent shortcuts that diverge.
|
|
13
|
-
- **Don't pluralise the leaf.** `working.network.org.jane`, not `working.network.org.janes`. Pluralise the container, not the entry.
|
|
14
|
-
|
|
15
|
-
## Zone layout
|
|
16
|
-
|
|
17
|
-
Recommended top-level layout — the spec allows alternatives, but this is what tooling will default to:
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
.textus/
|
|
21
|
-
manifest.yaml
|
|
22
|
-
schemas/ # YAML schema definitions
|
|
23
|
-
zones/
|
|
24
|
-
knowledge/ # authored truth: identity, voice, decisions — author-holders write
|
|
25
|
-
notebook/ # agent's own durable lane (workspace) — keep-holders write
|
|
26
|
-
feeds/ # automation-fed external inputs (fetch)
|
|
27
|
-
proposals/ # AI proposals awaiting accept (propose)
|
|
28
|
-
artifacts/ # generated by build automation — never edit by hand
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Inside `knowledge/`, group by **domain** (identity, people, projects, decisions, runbooks), not by file type or date. `knowledge.identity.*` is the convention for slow-changing identity facts. Inside `artifacts/`, group by **producer** (`artifacts/catalogs/`, `artifacts/indexes/`) so it's clear which build job owns what.
|
|
32
|
-
|
|
33
|
-
## Schema design
|
|
34
|
-
|
|
35
|
-
- **One schema per entry type, not per directory.** `person.yaml`, `project.yaml`, `decision.yaml` — applied across multiple subtrees if the shape matches.
|
|
36
|
-
- **Required = "this entry is meaningless without it."** Everything else is `optional`. Resist the urge to mark organisational metadata (like `tags`) required.
|
|
37
|
-
- **Prefer `enum` over free-text** for low-cardinality fields (relationship type, status, severity). Agents are far better at picking from a list than at producing exact strings.
|
|
38
|
-
- **Cap string lengths** with `max:` where the field has a natural bound (names, summaries). Skip for prose body — bodies are not schema-validated, only frontmatter is.
|
|
39
|
-
|
|
40
|
-
## Owner strings
|
|
41
|
-
|
|
42
|
-
The `owner:` field in the manifest is **advisory metadata**, not an ACL. Use it to label *who's expected to write here*:
|
|
43
|
-
|
|
44
|
-
- `textus:network` — humans curate
|
|
45
|
-
- `agent:planner` — a specific named agent
|
|
46
|
-
- `build:catalog-skills` — a specific build job
|
|
47
|
-
|
|
48
|
-
Tooling around `git blame` or audit logs may filter on owner; the gem itself only echoes it back in envelopes.
|
|
49
|
-
|
|
50
|
-
## Derived entries
|
|
51
|
-
|
|
52
|
-
A derived entry declares a `compute:` block with a `kind:` discriminator. Two kinds:
|
|
53
|
-
|
|
54
|
-
**`compute: { kind: projection }`** — textus computes the entry on `textus build` from other store entries. Declarative; nothing shells out.
|
|
55
|
-
|
|
56
|
-
```yaml
|
|
57
|
-
- key: artifacts.catalogs.people
|
|
58
|
-
path: artifacts/catalogs/people.md
|
|
59
|
-
zone: artifacts
|
|
60
|
-
schema: null
|
|
61
|
-
owner: build:catalog-people
|
|
62
|
-
compute:
|
|
63
|
-
kind: projection
|
|
64
|
-
select: knowledge.network.org # prefix or list of prefixes
|
|
65
|
-
pluck: [name, relationship, org]
|
|
66
|
-
sort_by: name
|
|
67
|
-
template: people.mustache # under .textus/templates/
|
|
68
|
-
publish_to: [docs/people.md] # optional repo-relative byte-copy targets
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**`compute: { kind: external }`** — an external build tool (rake, just, a shell script) produces the file. textus never executes the `command:`; it only tracks `sources:` so `textus freshness` can compare source mtimes against the file's `_meta.generated.at`. The role running the build must hold `build` (default: `automation`).
|
|
72
|
-
|
|
73
|
-
```yaml
|
|
74
|
-
- key: artifacts.catalogs.skills
|
|
75
|
-
path: artifacts/catalogs/skills.md
|
|
76
|
-
zone: artifacts
|
|
77
|
-
owner: build:catalog-skills
|
|
78
|
-
compute:
|
|
79
|
-
kind: external
|
|
80
|
-
command: "rake catalog:skills" # informational; the automation invokes it
|
|
81
|
-
sources: [knowledge.projects, knowledge.network]
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The build automation is responsible for writing the `generated:` frontmatter block (`by`, `at`, `from`) when it produces the file. `generated.from` SHOULD match `compute.sources` — same list, recorded twice so a diff proves what was consumed.
|
|
85
|
-
|
|
86
|
-
Full contract for both shapes is in [`../SPEC.md` §5.2.1 and §5.2.2](../SPEC.md). Transforms (`compute.transform:`) and per-leaf publishing (`publish_each:`) are also covered there.
|
|
87
|
-
|
|
88
|
-
## Intake and freshness
|
|
89
|
-
|
|
90
|
-
External inputs land via `:resolve_intake` hooks, not shell commands. Each intake entry names a registered handler; fetch is on demand:
|
|
91
|
-
|
|
92
|
-
```sh
|
|
93
|
-
textus fetch feeds.notion.roadmap --as=automation
|
|
94
|
-
textus fetch stale --zone=feeds --as=automation # everything past its TTL
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Freshness budgets live in the top-level `rules:` block, matched by glob:
|
|
98
|
-
|
|
99
|
-
```yaml
|
|
100
|
-
rules:
|
|
101
|
-
- match: feeds.notion.**
|
|
102
|
-
fetch: { ttl: 6h, on_stale: warn } # warn | sync | timed_sync
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A typical scheduled-fetch integration shells the `fetch stale` sweep itself:
|
|
106
|
-
|
|
107
|
-
```sh
|
|
108
|
-
textus fetch stale --zone=intake --as=automation # in cron / CI
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
See [`./zones.md` §6](zones.md) for the full intake contract and [`./events.md`](events.md) for writing custom handlers.
|
|
112
|
-
|
|
113
|
-
### Read vs. fetch
|
|
114
|
-
|
|
115
|
-
There are two read operations, and the difference matters in custom code:
|
|
116
|
-
|
|
117
|
-
| Operation | Triggers fetch? | Use for |
|
|
118
|
-
|-----------|-----------------|---------|
|
|
119
|
-
| `ops.get` | No — pure read of on-disk envelope + freshness verdict | build / materialization paths, schema tooling, any context where a side-effecting read would surprise the caller |
|
|
120
|
-
| `ops.get_or_fetch` | Yes — composes `get` with the orchestrator per the rule's `on_stale` policy | interactive reads (`textus get`, dashboards) where the caller wants the freshest envelope obtainable |
|
|
121
|
-
|
|
122
|
-
Build always uses the pure path; injecting fetch into materialization caused the cascading-staleness incident behind issue #59. Pick `get_or_fetch` only when you genuinely want side effects on read.
|
|
123
|
-
|
|
124
|
-
## Body content
|
|
125
|
-
|
|
126
|
-
- **Bodies are Markdown.** Headings, lists, code fences — whatever a human or agent finds useful.
|
|
127
|
-
- **The schema does not validate the body.** If a field belongs in structured data, put it in frontmatter, not the body.
|
|
128
|
-
- **Keep entries short.** If a project entry hits 500 lines, it probably wants to be split into sub-entries (e.g. `working.projects.acme.dashboard` + `working.projects.acme.api`) rather than one mega-document.
|
|
129
|
-
|
|
130
|
-
## Concurrency
|
|
131
|
-
|
|
132
|
-
For multi-writer environments, **always pass `if_etag`** on `put`. The gem treats etag-less writes as last-writer-wins on purpose (single-writer scripts, fresh-file creation), but anything resembling a daemon or a long-running agent should round-trip the etag.
|
|
133
|
-
|
|
134
|
-
## Application layering
|
|
135
|
-
|
|
136
|
-
The application layer is organised around three shapes — `Manifest` as a composition record, a single `Container` capability record handed to every use case, and a split envelope reader/writer. See [ADR 0018](architecture/decisions/0018-manifest-carving.md), [ADR 0017](architecture/decisions/0017-envelope-io-split.md), [ADR 0022](architecture/decisions/0022-container-call-dispatcher.md), and [ADR 0023](architecture/decisions/0023-uniform-use-case-shape.md).
|
|
137
|
-
|
|
138
|
-
- **`Manifest` is a composition record** (`Data.define(:data, :resolver, :policy, :rules)`). Reach individual concerns through the field accessors: `manifest.data.entries`, `manifest.policy.permission_for(zone)`, `manifest.resolver.resolve(key)`, `manifest.rules.for(key)`.
|
|
139
|
-
- **Use cases are plain `(container:, call:)` classes.** Each is a single class under `lib/textus/{read,write,maintenance}/` with `def initialize(container:, call:)` and a `#call(...)` method; verbs are looked up in the static `Textus::Dispatcher::VERBS` table. `Container` is a `Data.define` record bundling the wired ports + manifest (`manifest`, `file_store`, `schemas`, `root`, `audit_log`, `events`, `rpc`, `authorizer`); `Call` is the immutable per-invocation value (`role`, `correlation_id`, `now`, `dry_run`). A use case that emits events derives its `Hooks::Context` from `(container, call)` — nothing is injected. Use cases pull only what they need into ivars; nobody passes the raw `Store` around the application layer. `store.as(role)` returns a `RoleScope` that forwards verbs to the dispatcher.
|
|
140
|
-
- **Write path is split**: `Envelope::IO::Reader` owns read/parse (existing-uid lookup, raw read, parse), and `Envelope::IO::Writer` owns put/delete/move + the audit-append invariant (every public method's final action is `@audit_log.append(...)`).
|
|
141
|
-
|
|
142
|
-
The user-facing CLI surface, the wire envelope shape, and the protocol version (`textus/3`) are unchanged.
|
|
143
|
-
|
|
144
|
-
## Pairing with other tools
|
|
145
|
-
|
|
146
|
-
- **MCP servers**: a thin server that exposes `textus get` and `textus put` as tools is the recommended way to give Claude/agents access. Don't bake MCP into this gem.
|
|
147
|
-
- **Vector stores**: index `body` content into a vector store if you want fuzzy retrieval. `frontmatter` stays in textus as the source of truth for deterministic facts.
|
|
148
|
-
- **CI**: run `textus freshness` (or `textus list` + schema validation) in CI to catch drift between derived entries and their sources.
|