textus 0.10.4 → 0.12.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 +128 -3
- data/README.md +45 -86
- data/SPEC.md +266 -138
- data/docs/conventions.md +47 -15
- data/lib/textus/application/reads/freshness.rb +2 -2
- data/lib/textus/application/reads/get.rb +1 -1
- data/lib/textus/application/reads/policy_explain.rb +2 -2
- data/lib/textus/application/refresh/orchestrator.rb +1 -1
- data/lib/textus/application/refresh/worker.rb +5 -5
- data/lib/textus/application/writes/accept.rb +19 -1
- data/lib/textus/application/writes/build.rb +5 -5
- data/lib/textus/application/writes/delete.rb +2 -3
- data/lib/textus/application/writes/publish.rb +1 -1
- data/lib/textus/application/writes/put.rb +3 -6
- data/lib/textus/builder/pipeline.rb +1 -1
- data/lib/textus/builder/renderer/json.rb +1 -1
- data/lib/textus/builder/renderer/yaml.rb +1 -1
- data/lib/textus/cli/group/key.rb +1 -1
- data/lib/textus/cli/group/refresh.rb +21 -0
- data/lib/textus/cli/group/rule.rb +11 -0
- data/lib/textus/cli/verb/build.rb +1 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -2
- data/lib/textus/cli/verb/hooks.rb +1 -1
- data/lib/textus/cli/verb/{migrate_keys.rb → key_normalize.rb} +1 -1
- data/lib/textus/cli/verb/put.rb +1 -1
- data/lib/textus/cli/verb/{policy_explain.rb → rule_explain.rb} +1 -1
- data/lib/textus/cli/verb/{policy_list.rb → rule_list.rb} +3 -3
- data/lib/textus/cli/verb.rb +3 -2
- data/lib/textus/cli.rb +6 -6
- data/lib/textus/doctor/check/handler_allowlist.rb +1 -1
- data/lib/textus/doctor/check/illegal_keys.rb +39 -16
- data/lib/textus/doctor/check/intake_registration.rb +4 -4
- data/lib/textus/doctor/check/protocol_version.rb +47 -0
- data/lib/textus/doctor/check/{policy_ambiguity.rb → rule_ambiguity.rb} +6 -6
- data/lib/textus/doctor.rb +5 -4
- data/lib/textus/domain/permission.rb +4 -4
- data/lib/textus/domain/policy/predicates/human_accept.rb +31 -0
- data/lib/textus/domain/policy/predicates/schema_valid.rb +50 -0
- data/lib/textus/domain/policy/promotion.rb +45 -0
- data/lib/textus/errors.rb +24 -5
- data/lib/textus/hooks/builtin.rb +5 -5
- data/lib/textus/hooks/dispatcher.rb +1 -1
- data/lib/textus/hooks/dsl.rb +3 -10
- data/lib/textus/hooks/loader.rb +1 -2
- data/lib/textus/hooks/registry.rb +22 -21
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/init.rb +25 -34
- data/lib/textus/intro.rb +9 -9
- data/lib/textus/manifest/entry.rb +66 -6
- data/lib/textus/manifest/{policies.rb → rules.rb} +12 -10
- data/lib/textus/manifest/schema.rb +49 -0
- data/lib/textus/manifest.rb +79 -39
- data/lib/textus/migrate_keys.rb +1 -1
- data/lib/textus/projection.rb +4 -4
- data/lib/textus/refresh.rb +1 -1
- data/lib/textus/store/mover.rb +91 -50
- data/lib/textus/store/staleness/generator_check.rb +88 -0
- data/lib/textus/store/staleness/intake_check.rb +46 -0
- data/lib/textus/store/staleness.rb +9 -104
- data/lib/textus/store/writer.rb +14 -12
- data/lib/textus/store.rb +1 -1
- data/lib/textus/version.rb +2 -2
- data/lib/textus.rb +1 -0
- metadata +15 -7
- data/lib/textus/cli/group/policy.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e14d58006851be5feba9ffa7016f5860b0ea380cef09cbf324a897d24ae56ee8
|
|
4
|
+
data.tar.gz: f928e250b5c3bc6e072c1ffc1928e68fdf61d2965d1746c121657ad4bd07ac75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6eb5690e16fc78f71b42a5f4308d7698684b5aa9f43a92534f73edca5fc36494be65bfab6df2d8d0832f788e7fe7496638ad23f83ee6f69f5a9950b419237745
|
|
7
|
+
data.tar.gz: aa3b5903ff1fb44dcc5d1bb3f0676bffb9d8cce99c6e6237f2d947b3486e3d0cfce53271fcf282b927ea1b2e9b1164e721ed0800ddd5ffde1644f85dd660339a
|
data/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,133 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
The **gem version** (`0.x.y`) is distinct from the **protocol version**
|
|
8
|
-
(currently `textus/
|
|
9
|
-
is
|
|
8
|
+
(currently `textus/3`, embedded in every envelope as `protocol`). A protocol
|
|
9
|
+
bump is a breaking change that requires a store migration; the gem version
|
|
10
|
+
tracks both additive improvements and breaking protocol bumps independently.
|
|
11
|
+
|
|
12
|
+
## 0.12.1 — textus/2 hint fix (2026-05-26)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Manifest parser now points textus/2 stores at the 0.11.x stepping-stone
|
|
16
|
+
migrator instead of the misleading "check YAML frontmatter for syntax errors"
|
|
17
|
+
hint. The protocol_version doctor check carried the correct hint already, but
|
|
18
|
+
was unreachable on textus/2 stores because `Store.discover` → `Manifest.load`
|
|
19
|
+
raises before doctor checks run. Surfaced by v0.12.0 release smoke testing.
|
|
20
|
+
|
|
21
|
+
## 0.12.0 — legacy sweep (2026-05-25)
|
|
22
|
+
|
|
23
|
+
### Removed (breaking)
|
|
24
|
+
- `Role::LEGACY_RENAMES` (`ai`/`script`/`build` → friendly error). Legacy role
|
|
25
|
+
names now fail with the generic `InvalidRole` error.
|
|
26
|
+
- `Manifest::LEGACY_ZONE_RENAMES` (`inbox` → friendly error).
|
|
27
|
+
- `Hooks::Registry::LEGACY_EVENT_RENAMES` (14 legacy event names → friendly
|
|
28
|
+
error). Legacy events now fail with `unknown event: <name>`.
|
|
29
|
+
- `CLI::LEGACY_VERB_RENAMES` / `CLI::LEGACY_GROUP_RENAMES` and the
|
|
30
|
+
`CommandRenamed` error class.
|
|
31
|
+
- `textus migrate --to=textus/3` verb and `lib/textus/migration/**` (eight
|
|
32
|
+
files, ~924 lines).
|
|
33
|
+
- Eight ad-hoc legacy-key guards in `manifest.rb` / `manifest/entry.rb` /
|
|
34
|
+
`manifest/rules.rb`.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- `Manifest::Schema.validate!` — strict-unknown-keys parser. Manifests with
|
|
38
|
+
any unrecognized key fail uniformly with `unknown key 'X' at '<jsonpath>'`.
|
|
39
|
+
- ADR 0003 documenting the sweep and the 0.11.x stepping-stone path.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- `Doctor::Check::ProtocolVersion` hint no longer suggests `textus migrate`
|
|
43
|
+
(the verb is gone); points at 0.11.x docs instead.
|
|
44
|
+
- Test suite consolidated: five batches of disciplined deletions/merges
|
|
45
|
+
(−4 files, −134 LOC from the post-P6 peak). Net effect across the release:
|
|
46
|
+
test suite grew +8.2% LOC to cover new behavior (schema walker, permissive
|
|
47
|
+
audit-log tolerance).
|
|
48
|
+
|
|
49
|
+
### Migration
|
|
50
|
+
- **From textus/2 (gem ≤0.10.x):** install textus 0.11.x first; run
|
|
51
|
+
`textus migrate --to=textus/3`; then upgrade to 0.12.0.
|
|
52
|
+
- **From 0.11.x:** drop-in upgrade.
|
|
53
|
+
|
|
54
|
+
## 0.11.0 — textus/3 vocabulary redesign (2026-05-25)
|
|
55
|
+
|
|
56
|
+
**BREAKING:** Protocol bumps to `textus/3`. Stores authored on 0.10.x must run `textus migrate --to=textus/3` before installing 0.11.0. `textus doctor` refuses to operate on un-migrated stores.
|
|
57
|
+
|
|
58
|
+
### Renamed — actors
|
|
59
|
+
|
|
60
|
+
- `ai` → `agent`, `script` → `runner`, `build` → `builder`. `Role.resolve` rejects legacy names with a one-line migration hint pointing at `--as=<new>`.
|
|
61
|
+
|
|
62
|
+
### Renamed — zone
|
|
63
|
+
|
|
64
|
+
- `inbox` → `intake`. Directory rename + key prefix update + manifest field handled by the migrator.
|
|
65
|
+
|
|
66
|
+
### Renamed — manifest schema
|
|
67
|
+
|
|
68
|
+
- `writable_by:` → `write_policy:`; new explicit `read_policy:` on zones (default `[all]`).
|
|
69
|
+
- `policies:` (top-level) → `rules:`. Class rename: `Manifest::Policies` → `Manifest::Rules`.
|
|
70
|
+
- `projection:` and `generator:` unified under `compute: { kind: projection|external, ... }`.
|
|
71
|
+
- `reduce:` (inside compute/projection) → `transform:`.
|
|
72
|
+
- `handler_allowlist:` → `intake_handler_allowlist:`.
|
|
73
|
+
- `promote_requires:` (reserved in textus/2) → `promotion: { requires: [...] }` and is now **enforced** during `textus accept`.
|
|
74
|
+
|
|
75
|
+
### Renamed — hook events
|
|
76
|
+
|
|
77
|
+
- RPC: `:intake` → `:resolve_intake`, `:reduce` → `:transform_rows`, `:check` → `:validate`.
|
|
78
|
+
- Pub-sub (object_pasttense): `:put` → `:entry_put`, `:deleted` → `:entry_deleted`, `:built` → `:build_completed`, `:mv` → `:entry_renamed`, `:accepted` → `:proposal_accepted`, `:reject` → `:proposal_rejected`, `:published` → `:file_published`, `:loaded` → `:store_loaded`, `:refreshed` → `:entry_refreshed`, `:refresh_began` → `:refresh_started`, `:refresh_detached` → `:refresh_backgrounded`. `:refresh_failed` kept.
|
|
79
|
+
- DSL: single `Textus.on(event, name, **opts) { ... }`. Sugar methods (`Textus.intake`, `Textus.reduce`, `Textus.check`, etc.) and the generic `Textus.hook(...)` form removed.
|
|
80
|
+
|
|
81
|
+
### Renamed — CLI
|
|
82
|
+
|
|
83
|
+
- Namespaced: `textus key mv`, `textus key normalize` (was `key migrate`), `textus rule list` (was `policy list`), `textus rule explain` (was `policy explain`), `textus refresh stale` (was `refresh-stale`).
|
|
84
|
+
- Top-level mutator `textus mv` removed (use `textus key mv`).
|
|
85
|
+
- Envelope-render flag `--format=json` → `--output=json`. Entry-level `format:` in the manifest is unchanged.
|
|
86
|
+
- Legacy spellings emit a `CommandRenamed` envelope (`code: "command_renamed"`); legacy flags emit `FlagRenamed`.
|
|
87
|
+
|
|
88
|
+
### Added
|
|
89
|
+
|
|
90
|
+
- `textus migrate --to=textus/3`: idempotent one-shot migrator (manifest YAML rewrite, zone directory rename `inbox` → `intake`, frontmatter owner sweep across `.md`/`.json`/`.yaml`, audit-log marker, hook DSL scanner that reports old call sites).
|
|
91
|
+
- `textus doctor` check `protocol_version`: refuses textus/2 stores.
|
|
92
|
+
- `promotion.requires` predicates: `schema_valid`, `human_accept`. Enforced by `textus accept` for matching rules.
|
|
93
|
+
|
|
94
|
+
### Internal
|
|
95
|
+
|
|
96
|
+
- `Manifest::Policies` → `Manifest::Rules` (class + file + accessor + doctor check).
|
|
97
|
+
- New errors: `Textus::BadManifest`, `Textus::CommandRenamed`, `Textus::FlagRenamed`.
|
|
98
|
+
- Two new domain classes under `Textus::Domain::Policy::Predicates::` for promotion gating.
|
|
99
|
+
- Migration toolkit under `Textus::Migration::V3::`.
|
|
100
|
+
|
|
101
|
+
### Migration notes for 0.10.x users
|
|
102
|
+
|
|
103
|
+
1. Update `Gemfile`: `gem "textus", "~> 0.11"`.
|
|
104
|
+
2. `bundle update textus`.
|
|
105
|
+
3. `cd` to each textus store and run `textus migrate --to=textus/3`.
|
|
106
|
+
4. Review the hook-scanner findings printed at the end of the migrate output. For each call site, replace `Textus.X(:name) { ... }` with the canonical `Textus.on(:Y, :name) { ... }` per the event rename table above.
|
|
107
|
+
5. Run `textus doctor` — should report `ok: true`.
|
|
108
|
+
6. Commit the rewritten `.textus/` directory (manifest, audit marker, possibly renamed zone dir).
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- **`Doctor::Check::IllegalKeys` now honors `index_filename:`.** Previously the doctor walked every file and directory under a nested entry and flagged any whose basename failed the `[a-z0-9][a-z0-9-]*` segment regex — including `SKILL.md` itself and unrelated siblings like `references/foo.md`. With this fix, when an entry declares `index_filename:`, only the parent-directory segments leading to each matching index file are validated; sibling files and unrelated subtrees are not enumerated and are not flagged. `manifest.enumerate` already filtered correctly via the new glob; this brings the doctor check into parity. Two new specs in `spec/doctor_spec.rb` cover (a) `SKILL.md` is not flagged, (b) sibling `references/` files are not flagged. The pre-existing illegal-parent-segment case (e.g. `Bad_Name/SKILL.md`) still reports `key.illegal`.
|
|
113
|
+
|
|
114
|
+
## 0.10.5 — tech-debt cleanup + `index_filename:` + docs polish (2026-05-25)
|
|
115
|
+
|
|
116
|
+
Patch release. One user-facing feature (`index_filename:` on nested manifest entries) plus internal refactors that remove 7 of 19 `rubocop:disable` suppressions. No protocol bump; existing manifests parse unchanged.
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- **Per-entry `index_filename:` on nested manifest entries.** A nested entry MAY declare `index_filename: SKILL.md` (or any other bare basename) to surface that single file per directory as the row; the row's key segments come from the directory path, and siblings are not enumerated. Lets entries project spec-mandated filenames (e.g. agentskills.io's `SKILL.md`) whose uppercase casing would otherwise be rejected by the `[a-z0-9][a-z0-9-]*` key-segment grammar. `resolve(key)` returns the index-filename path for sub-directories. Validation: requires `nested: true`, basename only (no slashes), extension must match the entry's `format:`. New spec `spec/manifest_index_filename_spec.rb`. Documented in SPEC §4.
|
|
121
|
+
|
|
122
|
+
### Internal
|
|
123
|
+
|
|
124
|
+
- **`Store::Mover#call` refactor.** Replaces an 81-line method (suppressed `Metrics/AbcSize, Metrics/MethodLength`) with an 8-line orchestrator sequenced over four named private phases — `prepare_plan`, `ensure_uid!`, `perform_move`, `record_move` — coordinated through a `MovePlan` value object. The pre-read envelope is threaded separately so `MovePlan` describes only the planned operation.
|
|
125
|
+
- **`Store::Staleness#call` split.** Replaces a 70-line dual-loop method (the most aggressive suppression in the gem — `AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity, BlockLength`) with a composer + two single-purpose checks (`GeneratorCheck`, `IntakeCheck`) and a private filter method on the composer. Each new unit fits default rubocop thresholds.
|
|
126
|
+
- **`Store::Writer` payload + ctx grouping.** Collapses `write_envelope_to_disk`'s 8 keyword args to 5 by introducing `Store::Writer::Payload = Data.define(:meta, :body, :content)` and reusing `Application::Context` for `role` + `correlation_id`. Applies the same `ctx:` pattern to the sibling `delete_envelope_from_disk`. The class-wide `Metrics/ParameterLists` disable is narrowed to a method-level disable on the `put` back-compat shim (which mirrors the user-facing `Store#put` 7-kwarg signature and cannot be changed).
|
|
127
|
+
- **Net suppression change:** 19 `rubocop:disable` lines → 17; 7 metric-cop suppressions removed; one `ParameterLists` disable narrowed in scope. Full suite unchanged.
|
|
128
|
+
|
|
129
|
+
### Documentation
|
|
130
|
+
|
|
131
|
+
- **SPEC.md §5.2.1 added.** Documents the `generator:` field — the externally-generated-derived-entry shape (build runner produces the file; textus tracks `sources:` for staleness via `_meta.generated.at`). The field was always parsed and tested but had no spec coverage. Clarifies that textus never executes `command:` — consistent with §2 "Not an executor."
|
|
132
|
+
- **README.md trimmed.** Removed the duplicated "CLI verbs" and "Zones and roles" tables; readers are pointed at SPEC §5 / §9 and `docs/zones.md` for the canonical surfaces. README narrative kept.
|
|
133
|
+
- **docs/conventions.md** now covers both derived-entry shapes (`projection:` for declarative compute inside textus; `generator:` for external build tools) and the current intake / freshness model (top-level `policies:` + `textus refresh-stale`). Replaces a stale section that described a pre-0.4 build-runner pattern.
|
|
134
|
+
- **CONTRIBUTING.md** sources-of-truth pointer updated. Per-release implementation plans are kept locally by maintainers and no longer signposted in public docs.
|
|
10
135
|
|
|
11
136
|
## 0.10.4 — GitHub folder intake recipe + skill-bundle deferral ADR (2026-05-24)
|
|
12
137
|
|
|
@@ -42,7 +167,7 @@ Patch release. Two pieces of work: (1) docs describe current state only — ever
|
|
|
42
167
|
|
|
43
168
|
### Documentation
|
|
44
169
|
|
|
45
|
-
- **`docs/
|
|
170
|
+
- **`CONTRIBUTING.md`** now points readers at SPEC / ARCHITECTURE / `docs/` / CHANGELOG as the sources of truth. Per-release implementation plans are kept locally by maintainers and are no longer signposted in public docs.
|
|
46
171
|
- **README, SPEC, ARCHITECTURE, docs/zones, docs/events, docs/conventions, examples/claude-plugin/README** — stripped `(0.8.2+)`, `(0.9.0+)`, `(0.9.2)`, `(v1.0)`, `(v1.1)`, `(v1.2)`, `(v0.3)` annotations from headings, parentheticals, and inline notes. Removed "Renamed in 0.9.2" / "Pre-0.9.2 stores" / "New in 0.9.0" / "Backward compatibility (v0.5)" callouts. Example code that used pre-0.9.2 zone names (`canon`, `intake`, `pending`, `derived`) now uses current names (`identity`, `inbox`, `review`, `output`).
|
|
47
172
|
- **`docs/events.md`** — header count corrected to "15 events: 3 RPC and 12 pub-sub" (previously read "12 events", with refresh\_\* mentioned in subtext); stale Linear manifest example updated to use top-level `policies:` block.
|
|
48
173
|
- **SPEC.md §10.2** — removed `legacy_intake_fields` from the builtin doctor-check list.
|
data/README.md
CHANGED
|
@@ -7,17 +7,27 @@
|
|
|
7
7
|
|
|
8
8
|
A 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.
|
|
9
9
|
|
|
10
|
-
Reference implementation in Ruby. Wire format `textus/
|
|
10
|
+
Reference implementation in Ruby. Wire format `textus/3`. SPEC: [`SPEC.md`](SPEC.md). Implementation notes: [`docs/`](docs/).
|
|
11
11
|
|
|
12
12
|
## Versioning
|
|
13
13
|
|
|
14
14
|
Two versions, deliberately independent:
|
|
15
15
|
|
|
16
|
-
- **Protocol wire string:** `textus/
|
|
17
|
-
- **Gem version:** semver, currently `0.
|
|
16
|
+
- **Protocol wire string:** `textus/3`. Stable; breaking changes require `textus/4`.
|
|
17
|
+
- **Gem version:** semver, currently `0.11.0`. The gem version is decoupled from the protocol string — internal refactors bump the gem; only wire-format changes bump the protocol.
|
|
18
18
|
|
|
19
19
|
Envelope payloads carry the `protocol` field. The gem version is irrelevant to the wire format.
|
|
20
20
|
|
|
21
|
+
### Upgrading from textus/2
|
|
22
|
+
|
|
23
|
+
textus 0.12.0 does not include a built-in migrator. If you are upgrading from
|
|
24
|
+
a textus/2 store (gem versions ≤ 0.10.x), first install textus 0.11.x and run:
|
|
25
|
+
|
|
26
|
+
textus migrate --to=textus/3
|
|
27
|
+
|
|
28
|
+
Then upgrade to 0.12.0. Pre-0.11.0 audit-log rows with `role: ai|script|build`
|
|
29
|
+
are tolerated verbatim by the reader — no rewrite step required.
|
|
30
|
+
|
|
21
31
|
## Install
|
|
22
32
|
|
|
23
33
|
```sh
|
|
@@ -49,10 +59,10 @@ You get `.textus/` with all five zone directories, baseline schemas, an empty au
|
|
|
49
59
|
sentinels/ # publish bookkeeping
|
|
50
60
|
zones/
|
|
51
61
|
identity/ # human-only — identity, voice, decisions
|
|
52
|
-
working/ # human /
|
|
53
|
-
|
|
54
|
-
review/ #
|
|
55
|
-
output/ #
|
|
62
|
+
working/ # human / agent / runner — day-to-day catalog
|
|
63
|
+
intake/ # runner — declared external inputs (actions)
|
|
64
|
+
review/ # agent + human — proposals awaiting accept
|
|
65
|
+
output/ # builder only — computed outputs
|
|
56
66
|
```
|
|
57
67
|
|
|
58
68
|
Manifest `path:` fields are relative to `.textus/zones/`. So `working.network.org.jane` lives at `.textus/zones/working/network/org/jane.md`.
|
|
@@ -65,92 +75,41 @@ textus list --zone=working
|
|
|
65
75
|
echo '{"_meta":{"name":"bob","org":"acme"},"body":"hi\n"}' \
|
|
66
76
|
| textus put working.network.org.bob --as=human --stdin
|
|
67
77
|
textus freshness --zone=output # per-entry fresh/stale/never_refreshed/no_policy
|
|
68
|
-
textus
|
|
78
|
+
textus rule list # show every rule block
|
|
69
79
|
textus audit --limit=20 # query the audit log
|
|
70
80
|
```
|
|
71
81
|
|
|
72
|
-
(All verbs return JSON envelopes by default; pass `--
|
|
82
|
+
(All verbs return JSON envelopes by default; pass `--output=json` explicitly if you prefer.)
|
|
73
83
|
|
|
74
84
|
For the full shape — Claude plugin with agents, skills, commands, pending walkthrough, intake action — see [`examples/claude-plugin/`](examples/claude-plugin/).
|
|
75
85
|
|
|
76
86
|
## What ships today
|
|
77
87
|
|
|
78
|
-
- **Per-entry formats.** `format: markdown | json | yaml | text` on a manifest entry. `cat .textus/zones/output/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`, `
|
|
88
|
+
- **Per-entry formats.** `format: markdown | json | yaml | text` on a manifest entry. `cat .textus/zones/output/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`, `transform`).
|
|
79
89
|
- **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.
|
|
80
90
|
- **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.
|
|
81
|
-
- **Strict key grammar.** `/^[a-z0-9][a-z0-9-]*$/`, max 8 segments × 64 chars. `textus key
|
|
91
|
+
- **Strict key grammar.** `/^[a-z0-9][a-z0-9-]*$/`, max 8 segments × 64 chars. `textus key normalize --dry-run|--write` rewrites existing stores with illegal segments deterministically.
|
|
82
92
|
- **`textus intro`.** 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. The boot signal for any agent — one tool call and it knows your store.
|
|
83
93
|
- **`textus doctor`.** Health check across 9 categories: 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.
|
|
84
94
|
- **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.
|
|
95
|
+
- **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.
|
|
85
96
|
|
|
86
97
|
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.
|
|
87
98
|
|
|
88
|
-
## CLI
|
|
89
|
-
|
|
90
|
-
All verbs accept `--
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
| `intro` | Store orientation: zones, entries, hooks, write flows, CLI map |
|
|
97
|
-
| `list [--prefix=K] [--zone=Z]` | Enumerate keys |
|
|
98
|
-
| `where K` | Resolve a key to its filesystem path |
|
|
99
|
-
| `get K` | Full envelope (frontmatter, body, uid, etag, format) |
|
|
100
|
-
| `schema show K` | Schema bound to an entry |
|
|
101
|
-
| `freshness [--prefix=K] [--zone=Z]` | Per-entry status (fresh / stale / never_refreshed / no_policy) against `policies:` |
|
|
102
|
-
| `audit [--key=K] [--zone=Z] [--role=R] [--verb=V] [--since=X] [--correlation-id=ID] [--limit=N]` | Query `.textus/audit.log` |
|
|
103
|
-
| `blame KEY` | Audit rows joined with git commit metadata |
|
|
104
|
-
| `policy list` / `policy explain KEY` | Dump effective policies / per-slot winners for one key |
|
|
105
|
-
| `deps K` / `rdeps K` | Forward / reverse projection dependencies |
|
|
106
|
-
| `published` | List `publish_to:` targets and their backing keys |
|
|
107
|
-
| `doctor --check=schema_violations` | Validate every entry against its schema |
|
|
108
|
-
| `hook list [--event=E]` | Registered hooks grouped by event (intake, reduce, check, put, deleted, refreshed, built, accepted, published, mv, reject, loaded, refresh_began, refresh_failed, refresh_detached) |
|
|
109
|
-
|
|
110
|
-
**Write:**
|
|
111
|
-
|
|
112
|
-
| Verb | Role |
|
|
113
|
-
|---|---|
|
|
114
|
-
| `put K --stdin --as=R [--action=NAME]` | per zone |
|
|
115
|
-
| `hook run NAME [--key=val] [--as=R]` | per zone written (invoke a registered intake hook) |
|
|
116
|
-
| `delete K --if-etag=E --as=R` | per zone |
|
|
117
|
-
| `refresh K --as=script` | per zone (typically `script`) |
|
|
118
|
-
| `key mv old new --as=R [--dry-run]` | per zone (same-zone moves; uid preserved) |
|
|
119
|
-
| `build [--prefix=K] [--dry-run]` | `build` |
|
|
120
|
-
| `accept K --as=human` | `human` only |
|
|
121
|
-
| `reject K --as=human` | `human` only (discards a pending proposal; fires `:reject`) |
|
|
122
|
-
|
|
123
|
-
**Health & maintenance:**
|
|
124
|
-
|
|
125
|
-
| Verb | Purpose |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `doctor` | Health checks (manifest, schemas, templates, hooks, illegal keys, sentinels, audit log, policy ambiguity, handler allowlist, legacy intake fields); `ok: true` when clean |
|
|
128
|
-
| `key migrate [--dry-run]` | Rename files whose basenames violate the strict key grammar |
|
|
129
|
-
|
|
130
|
-
**Scaffolding (human-only):**
|
|
131
|
-
|
|
132
|
-
| Verb | Purpose |
|
|
133
|
-
|---|---|
|
|
134
|
-
| `init` | Scaffold a fresh `.textus/` |
|
|
135
|
-
| `schema init NAME` | Stub a schema |
|
|
136
|
-
| `schema diff NAME` | Compare a schema against entries that claim it |
|
|
137
|
-
| `schema migrate NAME [--rename=OLD:NEW]` | Rewrite frontmatter keys across affected entries |
|
|
138
|
-
|
|
139
|
-
## Zones and roles
|
|
140
|
-
|
|
141
|
-
| Zone | `writable_by` | Purpose |
|
|
142
|
-
|---|---|---|
|
|
143
|
-
| `identity` | `[human]` | Identity, voice, decisions — slow-changing |
|
|
144
|
-
| `working` | `[human, ai, script]` | Active project state |
|
|
145
|
-
| `inbox` | `[script]` | Declared external inputs (actions) |
|
|
146
|
-
| `review` | `[ai, human]` | AI proposals; humans run `textus accept` to apply |
|
|
147
|
-
| `output` | `[build]` | Computed outputs from `textus build` |
|
|
148
|
-
|
|
149
|
-
Mismatches return `write_forbidden` with a hint naming the role that *would* be allowed. Every write records the resolved role in `.textus/audit.log`.
|
|
99
|
+
## CLI and zones
|
|
100
|
+
|
|
101
|
+
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`). Recognized roles: `human`, `agent`, `runner`, `builder`.
|
|
102
|
+
|
|
103
|
+
- Full verb table — read, write, health, scaffolding — is in [SPEC §9](SPEC.md).
|
|
104
|
+
- Zone semantics and the role/`write_policy` mapping live in [SPEC §5](SPEC.md), with a tutorial expansion in [`docs/zones.md`](docs/zones.md).
|
|
105
|
+
|
|
106
|
+
`textus intro` 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.
|
|
150
107
|
|
|
151
108
|
## Compute and publish
|
|
152
109
|
|
|
153
|
-
Derived entries declare
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
For externally-generated entries, declare `compute: { kind: external, sources: [...] }` — textus tracks the declared sources for staleness; the build runner produces the file.
|
|
154
113
|
|
|
155
114
|
`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.
|
|
156
115
|
|
|
@@ -158,15 +117,15 @@ Derived entries declare a `projection:` (`select`, `pluck`, `sort_by`, `limit`,
|
|
|
158
117
|
|
|
159
118
|
textus exposes a hook DSL. Drop `.rb` files into `.textus/hooks/` (subdirectories are fine; files load alphabetically by full path). Events:
|
|
160
119
|
|
|
161
|
-
- `:
|
|
162
|
-
- `:
|
|
163
|
-
- `:
|
|
164
|
-
- `:
|
|
165
|
-
- `:
|
|
120
|
+
- `:resolve_intake` — bring bytes in from elsewhere (returns `{_meta:, body:}`)
|
|
121
|
+
- `:transform_rows` — transform rows during projection (returns rows)
|
|
122
|
+
- `:validate` — custom doctor check (returns issues)
|
|
123
|
+
- `:entry_put`, `:entry_deleted`, `:entry_refreshed`, `:build_completed`, `:proposal_accepted`, `:file_published`, `:entry_renamed`, `:proposal_rejected`, `:store_loaded` — react to lifecycle events
|
|
124
|
+
- `:refresh_started`, `:refresh_failed`, `:refresh_backgrounded` — background-refresh lifecycle
|
|
166
125
|
|
|
167
126
|
```ruby
|
|
168
127
|
# Inside .textus/hooks/local_file.rb
|
|
169
|
-
Textus.
|
|
128
|
+
Textus.on(:resolve_intake, :local_file) do |config:, args:, **|
|
|
170
129
|
path = config["path"] or raise "local-file requires intake.config.path"
|
|
171
130
|
{
|
|
172
131
|
_meta: { "last_refreshed_at" => Time.now.utc.iso8601, "source_path" => path },
|
|
@@ -176,7 +135,7 @@ end
|
|
|
176
135
|
```
|
|
177
136
|
|
|
178
137
|
```ruby
|
|
179
|
-
Textus.
|
|
138
|
+
Textus.on(:transform_rows, :rank_by_recency) do |rows:, **|
|
|
180
139
|
rows.sort_by { |r| r["updated_at"].to_s }.reverse
|
|
181
140
|
end
|
|
182
141
|
```
|
|
@@ -184,18 +143,18 @@ end
|
|
|
184
143
|
To keep a batch of stale intake entries current in one shot:
|
|
185
144
|
|
|
186
145
|
```sh
|
|
187
|
-
textus refresh
|
|
188
|
-
# or just refresh everything stale in the
|
|
189
|
-
textus refresh
|
|
146
|
+
textus refresh stale --prefix=working --zone=intake --as=runner
|
|
147
|
+
# or just refresh everything stale in the intake zone:
|
|
148
|
+
textus refresh stale --zone=intake --as=runner
|
|
190
149
|
```
|
|
191
150
|
|
|
192
|
-
|
|
151
|
+
See SPEC.md §5.10 for the full hook contract.
|
|
193
152
|
|
|
194
153
|
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
154
|
|
|
196
155
|
## Examples
|
|
197
156
|
|
|
198
|
-
[`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
|
|
157
|
+
[`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 transforms and hooks, the agent-propose / human-accept loop, and the `inject_intro:` flag that puts an orientation preamble at the top of `CLAUDE.md`.
|
|
199
158
|
|
|
200
159
|
## Tests
|
|
201
160
|
|