textus 0.54.1 → 0.55.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/CHANGELOG.md +51 -1
- data/README.md +61 -27
- data/SPEC.md +167 -296
- data/docs/architecture/README.md +20 -8
- data/docs/reference/conventions.md +40 -56
- data/exe/textus +1 -1
- data/lib/textus/action/accept.rb +23 -21
- data/lib/textus/action/audit.rb +24 -61
- data/lib/textus/action/base.rb +9 -9
- data/lib/textus/action/blame.rb +18 -36
- data/lib/textus/action/boot.rb +2 -4
- data/lib/textus/action/data_mv.rb +20 -31
- data/lib/textus/action/deps.rb +3 -18
- data/lib/textus/action/doctor.rb +2 -9
- data/lib/textus/action/drain.rb +11 -19
- data/lib/textus/action/enqueue.rb +14 -30
- data/lib/textus/action/get.rb +12 -56
- data/lib/textus/action/ingest.rb +141 -77
- data/lib/textus/action/jobs.rb +6 -15
- data/lib/textus/action/key_delete.rb +6 -16
- data/lib/textus/action/key_delete_prefix.rb +8 -17
- data/lib/textus/action/key_mv.rb +54 -61
- data/lib/textus/action/key_mv_prefix.rb +13 -22
- data/lib/textus/action/list.rb +7 -21
- data/lib/textus/action/propose.rb +16 -26
- data/lib/textus/action/published.rb +3 -5
- data/lib/textus/action/pulse.rb +20 -27
- data/lib/textus/action/put.rb +15 -29
- data/lib/textus/action/rdeps.rb +3 -18
- data/lib/textus/action/reject.rb +12 -21
- data/lib/textus/action/rule_explain.rb +12 -22
- data/lib/textus/action/rule_lint.rb +10 -16
- data/lib/textus/action/rule_list.rb +5 -9
- data/lib/textus/action/schema_envelope.rb +3 -10
- data/lib/textus/action/uid.rb +3 -17
- data/lib/textus/action/where.rb +3 -18
- data/lib/textus/boot.rb +8 -16
- data/lib/textus/contract/arg.rb +10 -0
- data/lib/textus/contract/dsl.rb +88 -0
- data/lib/textus/contract/spec.rb +25 -0
- data/lib/textus/contract.rb +0 -162
- data/lib/textus/doctor/check/audit_log.rb +2 -2
- data/lib/textus/doctor/check/generator_drift.rb +2 -2
- data/lib/textus/doctor/check/orphaned_publish_targets.rb +3 -3
- data/lib/textus/doctor/check/protocol_version.rb +2 -2
- data/lib/textus/doctor/check/raw_asset_paths.rb +1 -1
- data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
- data/lib/textus/doctor/check/schema_violations.rb +2 -2
- data/lib/textus/doctor/check/schemas.rb +1 -1
- data/lib/textus/doctor/check/sentinels.rb +4 -4
- data/lib/textus/doctor/check/templates.rb +1 -1
- data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
- data/lib/textus/doctor/check.rb +4 -7
- data/lib/textus/doctor.rb +1 -1
- data/lib/textus/errors.rb +6 -0
- data/lib/textus/format/base.rb +0 -4
- data/lib/textus/format/json.rb +5 -6
- data/lib/textus/format/markdown.rb +5 -6
- data/lib/textus/format/shared.rb +17 -0
- data/lib/textus/format/text.rb +5 -4
- data/lib/textus/format/yaml.rb +30 -6
- data/lib/textus/format.rb +6 -0
- data/lib/textus/gate/auth.rb +2 -17
- data/lib/textus/gate/binder.rb +50 -0
- data/lib/textus/gate.rb +64 -88
- data/lib/textus/init.rb +2 -4
- data/lib/textus/jobs.rb +3 -9
- data/lib/textus/manifest/capabilities.rb +3 -3
- data/lib/textus/manifest/entry/base.rb +1 -1
- data/lib/textus/manifest/entry/publish/subtree_mirror.rb +2 -2
- data/lib/textus/manifest/entry/publish/to_paths.rb +2 -5
- data/lib/textus/manifest/schema/semantics/cross_field.rb +53 -0
- data/lib/textus/manifest/schema/semantics/invariants.rb +125 -0
- data/lib/textus/manifest/schema/semantics/migration.rb +83 -0
- data/lib/textus/manifest/schema/semantics.rb +11 -216
- data/lib/textus/meta.rb +54 -0
- data/lib/textus/{ports → port}/audit_log.rb +44 -4
- data/lib/textus/{ports → port}/build_lock.rb +2 -2
- data/lib/textus/{ports → port}/clock.rb +1 -1
- data/lib/textus/{ports → port}/publisher.rb +5 -5
- data/lib/textus/{ports → port}/sentinel_store.rb +3 -3
- data/lib/textus/{ports → port}/storage/file_stat.rb +1 -1
- data/lib/textus/{ports → port}/storage/file_store.rb +2 -2
- data/lib/textus/port/store.rb +93 -0
- data/lib/textus/{ports → port}/watcher_lock.rb +3 -3
- data/lib/textus/produce/engine.rb +1 -1
- data/lib/textus/schema/tools.rb +11 -7
- data/lib/textus/store/compositor.rb +34 -0
- data/lib/textus/store/container.rb +43 -0
- data/lib/textus/store/cursor.rb +26 -0
- data/lib/textus/store/envelope/reader.rb +43 -0
- data/lib/textus/store/envelope/writer.rb +195 -0
- data/lib/textus/store/geometry.rb +81 -0
- data/lib/textus/store/index/builder.rb +74 -0
- data/lib/textus/store/index/lookup.rb +60 -0
- data/lib/textus/store/jobs/base.rb +13 -0
- data/lib/textus/store/jobs/index.rb +15 -0
- data/lib/textus/store/jobs/materialize.rb +15 -0
- data/lib/textus/store/jobs/plan.rb +11 -0
- data/lib/textus/store/jobs/planner.rb +104 -0
- data/lib/textus/store/jobs/queue.rb +154 -0
- data/lib/textus/store/jobs/registry.rb +19 -0
- data/lib/textus/store/jobs/retention.rb +50 -0
- data/lib/textus/store/jobs/sweep.rb +21 -0
- data/lib/textus/store/jobs/worker.rb +64 -0
- data/lib/textus/store/session.rb +37 -0
- data/lib/textus/store.rb +21 -13
- data/lib/textus/{surfaces → surface}/cli/group/data.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/group/key.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/group/mcp.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/group/rule.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/group/schema.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/group.rb +2 -2
- data/lib/textus/{surfaces → surface}/cli/runner.rb +10 -63
- data/lib/textus/surface/cli/sources.rb +41 -0
- data/lib/textus/{surfaces → surface}/cli/verb/doctor.rb +4 -6
- data/lib/textus/{surfaces → surface}/cli/verb/get.rb +4 -4
- data/lib/textus/{surfaces → surface}/cli/verb/init.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/verb/mcp_serve.rb +3 -3
- data/lib/textus/{surfaces → surface}/cli/verb/put.rb +6 -11
- data/lib/textus/{surfaces → surface}/cli/verb/schema_diff.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/verb/schema_init.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/verb/schema_migrate.rb +1 -1
- data/lib/textus/{surfaces → surface}/cli/verb/watch.rb +2 -2
- data/lib/textus/{surfaces → surface}/cli/verb.rb +3 -8
- data/lib/textus/{surfaces → surface}/cli.rb +1 -1
- data/lib/textus/{surfaces → surface}/mcp/catalog.rb +9 -26
- data/lib/textus/{surfaces → surface}/mcp/errors.rb +1 -1
- data/lib/textus/{surfaces → surface}/mcp/server.rb +5 -5
- data/lib/textus/{surfaces → surface}/mcp.rb +2 -2
- data/lib/textus/surface/projector.rb +27 -0
- data/lib/textus/{surfaces → surface}/role_scope.rb +1 -1
- data/lib/textus/{surfaces → surface}/watcher.rb +8 -8
- data/lib/textus/value/call.rb +30 -0
- data/lib/textus/value/command.rb +16 -0
- data/lib/textus/value/envelope.rb +89 -0
- data/lib/textus/value/etag.rb +39 -0
- data/lib/textus/value/result.rb +26 -0
- data/lib/textus/value/role.rb +38 -0
- data/lib/textus/value/types.rb +13 -0
- data/lib/textus/{uid.rb → value/uid.rb} +9 -7
- data/lib/textus/version.rb +1 -1
- data/lib/textus/workflow/loader.rb +4 -4
- data/lib/textus/workflow/runner.rb +4 -18
- data/lib/textus.rb +9 -10
- metadata +100 -62
- data/lib/textus/action/write_verb.rb +0 -44
- data/lib/textus/call.rb +0 -28
- data/lib/textus/command.rb +0 -41
- data/lib/textus/container.rb +0 -26
- data/lib/textus/contract/around.rb +0 -29
- data/lib/textus/contract/binder.rb +0 -88
- data/lib/textus/contract/resources/build_lock.rb +0 -17
- data/lib/textus/contract/resources/cursor.rb +0 -26
- data/lib/textus/contract/sources.rb +0 -39
- data/lib/textus/contract/view.rb +0 -15
- data/lib/textus/cursor_store.rb +0 -24
- data/lib/textus/envelope/reader.rb +0 -46
- data/lib/textus/envelope/writer.rb +0 -209
- data/lib/textus/envelope.rb +0 -79
- data/lib/textus/etag.rb +0 -36
- data/lib/textus/jobs/base.rb +0 -23
- data/lib/textus/jobs/materialize.rb +0 -20
- data/lib/textus/jobs/plan.rb +0 -9
- data/lib/textus/jobs/planner.rb +0 -101
- data/lib/textus/jobs/retention.rb +0 -48
- data/lib/textus/jobs/sweep.rb +0 -27
- data/lib/textus/jobs/worker.rb +0 -67
- data/lib/textus/layout.rb +0 -83
- data/lib/textus/ports/job_store/job.rb +0 -65
- data/lib/textus/ports/job_store.rb +0 -123
- data/lib/textus/role.rb +0 -36
- data/lib/textus/session.rb +0 -35
- data/lib/textus/types.rb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27ac7fc2922b98c97c7873939781a13330e0765b7f0521449aa4871c414fc201
|
|
4
|
+
data.tar.gz: 055ed0684590d9ca8cf5e2ad49fc17ee05c70b8cbaa5f8b589b9bb9a3de99f0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 437568a688e1ff1b6f9786d1965633e9dfa69be5b6f7450ffeeaecb3c0be5a6729e94851f2066a26e2d313f8d23335d2ff12b83ad5e73718b27e784887c9ceb4
|
|
7
|
+
data.tar.gz: a8f4167af7c3ce315301996d3d3fedc13139f8eae4804b445ec4ed930da0d284ec96cab3467af1169ce8f6a64288d6f1d805bbfad60c0acb949f62e03ce1322d
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,56 @@ The **gem version** (`0.x.y`) is distinct from the **protocol version**
|
|
|
9
9
|
bump is a breaking change that requires a store migration; the gem version
|
|
10
10
|
tracks both additive improvements and breaking protocol bumps independently.
|
|
11
11
|
|
|
12
|
+
## 0.55.1 — 2026-06-22 — CI fix: converge_now purges done jobs
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **publish_tree prune spec** — `converge_now` now purges "done" jobs before reseeding. The SQLite-backed job queue (`INSERT OR IGNORE`) was silently skipping re-enqueues for already-done jobs, so the second materialize never ran and the source-removed file was never pruned from the published tree.
|
|
17
|
+
|
|
18
|
+
## 0.55.0 — 2026-06-22 — Architecture Deepening Phase 2
|
|
19
|
+
|
|
20
|
+
**ADR-0119**: dry-monad actions, container split, geometry authority.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **dry-monad Success/Failure actions** (PR #234): all 30 actions now return `Success(result)` or `Failure(code:, message:)` via `Dry::Monads`. `Value::Result.unwrap` converts Failure → `ActionError`. `be_success`/`be_failure` RSpec matchers replace `spec/unit/` tests.
|
|
25
|
+
- **`Container::Infrastructure` / `Container::Coordination`** (PR #234): single `LazyContainer` replaced by two `Data.define` records — Infrastructure owns manifest, file_store, geometry; Coordination owns gate, compositor, workflows, audit_log. Wired at boot via `wire_gate!`.
|
|
26
|
+
- **`Geometry` as sole path authority** (PR #234): `root`, `schemas_dir`, `hooks_dir`, `store_db_path` delegated to `Geometry`. Removed `root` from Infrastructure. High-value `File.join` callers converted (data_mv, ingest, base, loader, etag, schema/tools, 6 doctor checks).
|
|
27
|
+
- **`bin/db` helper**: sqlite3 wrapper for `.textus/.state/store.db` — `bin/db "SELECT * FROM jobs"` or just `bin/db` for interactive shell.
|
|
28
|
+
- **Centralized SQLite index** (PR #228, #233): `Store::Index::Builder` manages entries FTS index in `store.db`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Gate reads `Action::VERBS` directly** (PR #234): `Gate::VERB_ACTIONS` deleted. Gate dispatches via `Action::VERBS[cmd.verb]`.
|
|
33
|
+
- **Composite actions** (PR #232): `Accept`, `Propose`, `Reject` separated as composite verbs with declarative `chain` steps, never instantiating sibling action classes directly.
|
|
34
|
+
- **`knowledge.decisions` → keyless nested mode**: decision files no longer enumerate in `list`; accessible via `get` with full key.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **CLI `--args` JSON parsing** (PR #233): `Runner.coerce` now `JSON.parse(raw)` for `Hash`-typed args — fixes `textus enqueue --args='{"key":"..."}'` which previously passed the raw JSON string to `Queue::Job`.
|
|
39
|
+
- **adr-log workflow convention drift** (PR #233): `Get.new(key:).call(...)` → `Get.call(key: k, ...)` — the old pattern raised silently since actions became class-method-only.
|
|
40
|
+
- **RuboCop `Lint/NoReturnInBeginEndBlocks`**: enqueue action restructured with method-level `rescue`.
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
|
|
44
|
+
- `Gate::VERB_ACTIONS` constant (replaced by `Action::VERBS`)
|
|
45
|
+
- `LazyContainer` (replaced by `Container::Infrastructure`/`Coordination`)
|
|
46
|
+
- `spec/unit/` directory + `spec/support/spec_layout.rb` (378 examples removed)
|
|
47
|
+
- Orphaned `.textus/.state/jobs.sqlite3` database file
|
|
48
|
+
|
|
49
|
+
## 0.54.2 — 2026-06-18 — ingest dedup, .run → .state rename
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **Content-hash dedup in `Action::Ingest`**: re-ingesting the same URL or file bytes silently creates a supersede chain — new entry with full content, old stripped to tombstone with a `superseded_by` pointer.
|
|
54
|
+
- **`Ports::RawIndex`**: lightweight YAML-based O(1) index for raw lane content-hash/URL lookups, stored at `.textus/.state/indexes/raw.yaml`.
|
|
55
|
+
- **`content_hash` field**: every new raw entry stores a `sha256:` content hash for fast dedup lookups.
|
|
56
|
+
- **Asset file move on re-ingest**: asset files are moved (not copied) to the new date path on supersede.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **`.run` → `.state` rename**: the runtime artifact directory is renamed from `.textus/.run/` to `.textus/.state/`. The `state/` subdirectory becomes `cursors/`. Layout path methods follow automatically. Manual migration step: `mv .textus/.run .textus/.state`.
|
|
61
|
+
|
|
12
62
|
## 0.54.1 — 2026-06-17 — MCP SDK, dry ecosystem, ERB templates
|
|
13
63
|
|
|
14
64
|
### Added
|
|
@@ -406,7 +456,7 @@ No `textus/3` wire-format change. Manifest schema gains one optional, backward-c
|
|
|
406
456
|
segment-wise globstar — `**` spans zero or more path segments; within a
|
|
407
457
|
segment `*` is anchored and `{a,b}` alternates (stdlib `File.fnmatch`,
|
|
408
458
|
no new dependency). Documented in
|
|
409
|
-
[`docs/reference/zones.md`](docs/reference/
|
|
459
|
+
[`docs/reference/zones.md`](docs/reference/lanes.md#nested-entries).
|
|
410
460
|
|
|
411
461
|
### Internal
|
|
412
462
|
|
data/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<!-- Generated from .
|
|
1
|
+
<!-- Generated from knowledge.readme.* fragments by the artifacts.docs.readme workflow. Edit the fragments under .textus/data/knowledge/readme/ and run `textus drain`. -->
|
|
2
|
+
<!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
|
|
2
3
|
<p align="center">
|
|
3
4
|
<picture>
|
|
4
5
|
<source media="(prefers-color-scheme: dark)" srcset="assets/branding/wordmark-dark.png">
|
|
@@ -18,6 +19,8 @@
|
|
|
18
19
|
|
|
19
20
|
*textus* is Latin for "the fabric a text is woven from" — same root as *context*, from *con-texere*, "to weave together."
|
|
20
21
|
|
|
22
|
+
|
|
23
|
+
<!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
|
|
21
24
|
## The idea
|
|
22
25
|
|
|
23
26
|
Three actors write to your repo today:
|
|
@@ -39,6 +42,9 @@ flowchart LR
|
|
|
39
42
|
agent -->|keep| notebook["notebook<br/>(workspace)"]
|
|
40
43
|
agent -->|propose| proposals["proposals<br/>(queue)"]
|
|
41
44
|
automation -->|drain| artifacts["artifacts<br/>(machine)"]
|
|
45
|
+
human -->|ingest| raw["raw<br/>(intake)"]
|
|
46
|
+
agent -->|ingest| raw
|
|
47
|
+
automation -->|ingest| raw
|
|
42
48
|
|
|
43
49
|
proposals ==>|human accept| knowledge
|
|
44
50
|
knowledge -.->|projection source| artifacts
|
|
@@ -46,13 +52,15 @@ flowchart LR
|
|
|
46
52
|
classDef actor fill:#238636,stroke:#2ea043,color:#fff;
|
|
47
53
|
classDef gate fill:#9e6a03,stroke:#bb8009,color:#fff;
|
|
48
54
|
classDef anchor fill:#1f6feb,stroke:#388bfd,color:#fff;
|
|
55
|
+
classDef intake fill:#6e40c9,stroke:#8b5cf6,color:#fff;
|
|
49
56
|
class human,agent,automation actor;
|
|
50
57
|
class proposals gate;
|
|
51
58
|
class knowledge anchor;
|
|
59
|
+
class raw intake;
|
|
52
60
|
```
|
|
53
61
|
|
|
54
62
|
*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`).*
|
|
55
|
-
*Colour legend: **green** = writers · **amber** = the review gate (`proposals`) · **blue** = the trust anchor (`knowledge`).*
|
|
63
|
+
*Colour legend: **green** = writers · **amber** = the review gate (`proposals`) · **blue** = the trust anchor (`knowledge`) · **purple** = write-once intake (`raw`).*
|
|
56
64
|
|
|
57
65
|
The point of those lanes is to **build context you can trust**. Place each lane on two axes — how durable it is, and how much you can rely on it without review — and the value shows up as a climb: the high-trust corner (durable *and* authoritative = `knowledge`) is the one place nothing is *written* directly. It's *earned* by crossing the `accept` gate.
|
|
58
66
|
|
|
@@ -64,9 +72,10 @@ DURABLE │ notebook │ knowledge ★ the goal
|
|
|
64
72
|
(kept) │ agent's working truth │ canon — a human authors │
|
|
65
73
|
│ durable, but low-trust │ here · the context you ship │
|
|
66
74
|
├──────────────────────────┼───────────────────────────────┤
|
|
67
|
-
TRANSIENT │ artifacts
|
|
68
|
-
(staging) │ computed
|
|
69
|
-
│
|
|
75
|
+
TRANSIENT │ artifacts (outputs) │ proposals (queue) │
|
|
76
|
+
(staging) │ computed, machine-made │ a candidate, in review │
|
|
77
|
+
│ raw (inputs) │ ▲ climbs via human accept │
|
|
78
|
+
│ ingested, write-once │ │
|
|
70
79
|
└──────────────────────────┴───────────────────────────────┘
|
|
71
80
|
raw material ──── propose ────► a human accept lifts it to canon
|
|
72
81
|
```
|
|
@@ -84,6 +93,8 @@ An agent that tries to write directly into `knowledge/` gets `write_forbidden`.
|
|
|
84
93
|
|
|
85
94
|
That's the load-bearing claim: **coordination is a protocol invariant, not a library convenience.**
|
|
86
95
|
|
|
96
|
+
|
|
97
|
+
<!-- This is a fragment of the README. Edit here; the README is composed from fragments by a workflow. -->
|
|
87
98
|
## See it in four commands
|
|
88
99
|
|
|
89
100
|
```sh
|
|
@@ -135,7 +146,7 @@ bundle exec exe/textus --help
|
|
|
135
146
|
|
|
136
147
|
## What `textus init` gives you
|
|
137
148
|
|
|
138
|
-
You get `.textus/` with all
|
|
149
|
+
You get `.textus/` with all five lane directories under `data/`, baseline schemas, a starter manifest, and a gitignored `.state/` for disposable runtime state (the audit log, per-role cursors, produce locks). Roles declare capabilities; each lane declares a `kind:`, and write authority is derived from the role's capabilities crossed with the lane's kind:
|
|
139
150
|
|
|
140
151
|
```yaml
|
|
141
152
|
roles:
|
|
@@ -156,17 +167,18 @@ lanes:
|
|
|
156
167
|
schemas/ # YAML field shapes per entry family
|
|
157
168
|
templates/ # ERB templates for produced entries
|
|
158
169
|
workflows/ # Ruby workflow files (Textus.workflow DSL) for data acquisition
|
|
159
|
-
.gitignore # generated — ignores .
|
|
170
|
+
.gitignore # generated — ignores .state/ and any tracked:false entries
|
|
160
171
|
data/ # one dir per lane; kinds + capabilities are in the manifest above
|
|
161
172
|
knowledge/ # e.g. identity (knowledge.identity.*), voice, decisions, notes
|
|
162
173
|
notebook/
|
|
163
174
|
proposals/
|
|
164
175
|
artifacts/ # machine lane: computed outputs + external inputs
|
|
165
|
-
.
|
|
176
|
+
.state/ # disposable runtime state — gitignored, safe to delete (ADR 0038)
|
|
166
177
|
audit/audit.log # append-only NDJSON event ledger, every write (rotates at ~10 MB)
|
|
167
|
-
|
|
178
|
+
cursors/<role> # per-role pulse cursor — where `pulse --since` resumes
|
|
168
179
|
locks/ # per-key produce locks + the produce mutex
|
|
169
180
|
sentinels/ # publish bookkeeping (target sha) — regenerated on drain (ADR 0070)
|
|
181
|
+
indexes/raw.yaml # raw lane content-hash/URL index — regenerable cache
|
|
170
182
|
```
|
|
171
183
|
|
|
172
184
|
Manifest `path:` fields are relative to `.textus/data/`. So `knowledge.notes.org.jane` lives at `.textus/data/knowledge/notes/org/jane.md`.
|
|
@@ -195,14 +207,14 @@ For a worked store — knowledge entries, a staged proposal, schemas, ERB templa
|
|
|
195
207
|
- **Agent loop.** `textus boot` orients a fresh session; `textus pulse --since=N` is the per-turn heartbeat (changed entries, pending proposals, index etag for catalog drift detection). ([docs/how-to/agents-mcp.md](docs/how-to/agents-mcp.md))
|
|
196
208
|
- **MCP surface.** The official `mcp` Ruby SDK drives the stdio JSON-RPC server; protocol version auto-negotiated up to `2025-11-25`. Wire textus into Claude Code, Cursor, or any MCP host in one config block.
|
|
197
209
|
- **`textus doctor`.** Health checks across schemas, workflow registrations, keys, sentinels, and the audit log.
|
|
198
|
-
- **`raw` lane
|
|
210
|
+
- **`raw` lane and `ingest` verb.** Write-once intake lane for external URL bookmarks, files, and binary assets. Three source kinds (`url`/`file`/`asset`); daily key derivation; notebook stub per ingest. See "Intake and ingest" section below.
|
|
199
211
|
|
|
200
212
|
## CLI and lanes
|
|
201
213
|
|
|
202
214
|
Every command operates on one store, located in this order: `--root <path>` flag → **`TEXTUS_ROOT`** env → walk up from the working directory for a `.textus/` ([SPEC §3.1](SPEC.md)). Write verbs require `--as=<role>`, resolved as: `--as` flag → **`TEXTUS_ROLE`** env → `.textus/role` file → default `human` ([SPEC §5.1](SPEC.md)). Default roles: `human`, `agent`, `automation` (rename or add your own in the manifest's `roles:` block). All verbs accept `--output=json` and return the envelope defined in [SPEC §8](SPEC.md).
|
|
203
215
|
|
|
204
216
|
- Full verb table — read, write, health, scaffolding — is in [SPEC §9](SPEC.md).
|
|
205
|
-
- Lane semantics and the capability × lane-kind mapping live in [SPEC §5](SPEC.md), with the reference in [`docs/reference/
|
|
217
|
+
- Lane semantics and the capability × lane-kind mapping live in [SPEC §5](SPEC.md), with the reference in [`docs/reference/lanes.md`](docs/reference/lanes.md).
|
|
206
218
|
|
|
207
219
|
`textus boot` prints the same information for the current store: lanes, entry families with schemas, registered workflows, 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.
|
|
208
220
|
|
|
@@ -213,7 +225,7 @@ Produced entries (`kind: produced`) declare how they're acquired in one `source:
|
|
|
213
225
|
- **`source: { from: external, command: "...", sources: [...] }`** — *externally managed*: an out-of-band command or workflow writes the file; textus tracks staleness via declared `sources`.
|
|
214
226
|
- **`source: { from: external, command: "true", sources: [] }` + a workflow** — *workflow-driven*: a `Textus.workflow` block (in `.textus/workflows/`) acquires and shapes the data on `drain`.
|
|
215
227
|
|
|
216
|
-
Publishing is one typed `publish:` block (ADR 0052/0094). Each target is either `{ to: path, template?: name }` for a single file (optionally rendered through an ERB template) or `{ tree: "dir" }` to mirror a whole stored subtree. Sentinels for every published file live under `.textus/.
|
|
228
|
+
Publishing is one typed `publish:` block (ADR 0052/0094). Each target is either `{ to: path, template?: name }` for a single file (optionally rendered through an ERB template) or `{ tree: "dir" }` to mirror a whole stored subtree. Sentinels for every published file live under `.textus/.state/sentinels/` (git-ignored, regenerated on drain). See SPEC §5.2, §5.3, §5.12.
|
|
217
229
|
|
|
218
230
|
Templates live in `.textus/templates/` as ERB files (`.erb`). The template receives the entry's `content` hash as local variables via `ERB#result_with_hash`. If `inject_boot: true`, a `boot` variable is also available with the live orientation context.
|
|
219
231
|
|
|
@@ -222,7 +234,7 @@ Templates live in `.textus/templates/` as ERB files (`.erb`). The template recei
|
|
|
222
234
|
textus extends through **workflows** — a `Textus.workflow` block placed in `.textus/workflows/**/*.rb`. Each workflow matches a produced entry by key glob, then runs one or more named steps to acquire its data:
|
|
223
235
|
|
|
224
236
|
```ruby
|
|
225
|
-
# .textus/workflows/
|
|
237
|
+
# .textus/workflows/docs/my_report.rb
|
|
226
238
|
Textus.workflow "my_report" do
|
|
227
239
|
match "artifacts.my-report"
|
|
228
240
|
|
|
@@ -238,27 +250,47 @@ end
|
|
|
238
250
|
|
|
239
251
|
`drain` discovers all workflow files, matches them against produced entries, and runs the steps. The result is written back to the entry's data path; `publish:` then copies it to its consumer paths.
|
|
240
252
|
|
|
241
|
-
##
|
|
253
|
+
## Intake and ingest
|
|
242
254
|
|
|
243
|
-
|
|
255
|
+
The `raw` lane is the inbound counterpart to `artifacts`: where `drain` materialises
|
|
256
|
+
**outbound** computed outputs, `ingest` receives **inbound** external source material.
|
|
244
257
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
```
|
|
258
|
+
**The ingest principle:** prefer a reference over a copy. Store body or asset only when the
|
|
259
|
+
content itself is the value — human-authored notes, brainstorm outputs, context you want to
|
|
260
|
+
annotate. For everything else, the URL is enough. If the source is private or
|
|
261
|
+
access-restricted, set `access: private` in `source:` so downstream workflows can handle it
|
|
262
|
+
appropriately.
|
|
253
263
|
|
|
254
|
-
|
|
264
|
+
**Three source kinds:**
|
|
255
265
|
|
|
256
|
-
|
|
266
|
+
| Kind | Stores | Use when |
|
|
267
|
+
|------|--------|----------|
|
|
268
|
+
| `url` | URL reference only (`body: null`) | Bookmarking a page, skill, or doc for later annotation |
|
|
269
|
+
| `file` | File body text | Valuable human-authored content (brainstorm notes, meeting summaries) |
|
|
270
|
+
| `asset` | Binary at `assets/raw/` | Screenshots, PDFs — only when the asset itself is the artefact |
|
|
271
|
+
|
|
272
|
+
**Write-once** — the same slug on the same day cannot be overwritten. Delete and re-ingest to replace.
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
# bookmark a skill reference — URL only, body stays null
|
|
276
|
+
textus ingest url agentskills-io-brainstorming \
|
|
277
|
+
--url=https://agentskills.io/skills/brainstorming \
|
|
278
|
+
--label="brainstorming skill" \
|
|
279
|
+
--as=agent
|
|
280
|
+
|
|
281
|
+
# see what landed in the raw lane
|
|
282
|
+
textus list --lane=raw
|
|
283
|
+
|
|
284
|
+
# a notebook stub was created alongside — annotate it
|
|
285
|
+
textus get notebook.notes.raw
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Stale produced entries are re-materialised by `drain`, not by reads — `get` is a pure read (ADR 0089).
|
|
257
289
|
|
|
258
290
|
```sh
|
|
259
291
|
textus drain --as=automation # re-materialise every stale produced entry
|
|
260
|
-
textus drain artifacts.feeds --as=automation
|
|
261
|
-
textus get artifacts.feeds.
|
|
292
|
+
textus drain artifacts.feeds.skills --as=automation # scope to one prefix
|
|
293
|
+
textus get artifacts.feeds.skills # a pure read; carries a freshness verdict
|
|
262
294
|
```
|
|
263
295
|
|
|
264
296
|
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.
|
|
@@ -289,3 +321,5 @@ Lefthook hooks (`brew bundle install` then `lefthook install`) run rubocop on `p
|
|
|
289
321
|
## License
|
|
290
322
|
|
|
291
323
|
[MIT](LICENSE)
|
|
324
|
+
|
|
325
|
+
|