symbion 0.1.0__tar.gz
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.
- symbion-0.1.0/LICENSE +21 -0
- symbion-0.1.0/MANIFEST.in +2 -0
- symbion-0.1.0/PKG-INFO +333 -0
- symbion-0.1.0/README.md +313 -0
- symbion-0.1.0/pyproject.toml +39 -0
- symbion-0.1.0/setup.cfg +4 -0
- symbion-0.1.0/src/symbion/__init__.py +0 -0
- symbion-0.1.0/src/symbion/api.py +625 -0
- symbion-0.1.0/src/symbion/catalog.py +273 -0
- symbion-0.1.0/src/symbion/cli.py +1892 -0
- symbion-0.1.0/src/symbion/config.py +223 -0
- symbion-0.1.0/src/symbion/data/skill/SKILL.md +218 -0
- symbion-0.1.0/src/symbion/data/skill/adoption.md +154 -0
- symbion-0.1.0/src/symbion/data/skill/catalogs.md +74 -0
- symbion-0.1.0/src/symbion/data/skill/session_start.sh +59 -0
- symbion-0.1.0/src/symbion/gitref.py +270 -0
- symbion-0.1.0/src/symbion/gui/__init__.py +0 -0
- symbion-0.1.0/src/symbion/gui/arcs.py +119 -0
- symbion-0.1.0/src/symbion/gui/chrome.py +91 -0
- symbion-0.1.0/src/symbion/gui/filters.py +84 -0
- symbion-0.1.0/src/symbion/gui/notes.py +410 -0
- symbion-0.1.0/src/symbion/gui/pages.py +282 -0
- symbion-0.1.0/src/symbion/gui/serve.py +75 -0
- symbion-0.1.0/src/symbion/gui/theme.py +182 -0
- symbion-0.1.0/src/symbion/kinds.py +101 -0
- symbion-0.1.0/src/symbion/store.py +1232 -0
- symbion-0.1.0/src/symbion/summary.py +540 -0
- symbion-0.1.0/src/symbion/term.py +260 -0
- symbion-0.1.0/src/symbion.egg-info/PKG-INFO +333 -0
- symbion-0.1.0/src/symbion.egg-info/SOURCES.txt +57 -0
- symbion-0.1.0/src/symbion.egg-info/dependency_links.txt +1 -0
- symbion-0.1.0/src/symbion.egg-info/entry_points.txt +2 -0
- symbion-0.1.0/src/symbion.egg-info/requires.txt +9 -0
- symbion-0.1.0/src/symbion.egg-info/top_level.txt +1 -0
- symbion-0.1.0/tests/_resolvers.py +53 -0
- symbion-0.1.0/tests/conftest.py +101 -0
- symbion-0.1.0/tests/test_api.py +863 -0
- symbion-0.1.0/tests/test_arcs.py +190 -0
- symbion-0.1.0/tests/test_author.py +76 -0
- symbion-0.1.0/tests/test_catalog.py +291 -0
- symbion-0.1.0/tests/test_cli.py +3949 -0
- symbion-0.1.0/tests/test_concurrency.py +63 -0
- symbion-0.1.0/tests/test_config.py +312 -0
- symbion-0.1.0/tests/test_filters.py +103 -0
- symbion-0.1.0/tests/test_gitref.py +360 -0
- symbion-0.1.0/tests/test_gui_arcs.py +176 -0
- symbion-0.1.0/tests/test_gui_notes.py +685 -0
- symbion-0.1.0/tests/test_gui_pages.py +438 -0
- symbion-0.1.0/tests/test_gui_seam.py +212 -0
- symbion-0.1.0/tests/test_gui_serve.py +68 -0
- symbion-0.1.0/tests/test_gui_theme.py +80 -0
- symbion-0.1.0/tests/test_hook.py +261 -0
- symbion-0.1.0/tests/test_init.py +303 -0
- symbion-0.1.0/tests/test_kinds.py +95 -0
- symbion-0.1.0/tests/test_reconcile.py +173 -0
- symbion-0.1.0/tests/test_schema.py +119 -0
- symbion-0.1.0/tests/test_store.py +654 -0
- symbion-0.1.0/tests/test_summary.py +762 -0
- symbion-0.1.0/tests/test_summary_predicates.py +75 -0
symbion-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 phreakocious
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
symbion-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: symbion
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A notebook and ticket registry for agents: dated rows attached to a commit, a file, an item, an arc or the project, kept in a sibling git repo of JSONL.
|
|
5
|
+
Author: phreakocious
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/phreakocious/symbion
|
|
8
|
+
Project-URL: Issues, https://github.com/phreakocious/symbion/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: rich>=13
|
|
13
|
+
Requires-Dist: rich-argparse>=1.5
|
|
14
|
+
Provides-Extra: gui
|
|
15
|
+
Requires-Dist: nicegui>=3.0; extra == "gui"
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest; extra == "test"
|
|
18
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# symbion
|
|
22
|
+
|
|
23
|
+
A per-project notebook and ticket registry for small projects, driven by a
|
|
24
|
+
human and a coding agent through one CLI. The CLI works on its own; the agent
|
|
25
|
+
surface (a skill and a SessionStart hook) is written for Claude Code.
|
|
26
|
+
|
|
27
|
+
A note is dated, attributed, retractable, and attached to something stable: a
|
|
28
|
+
commit, a file, a free-form item, or the project as a whole. Seven kinds by
|
|
29
|
+
default — `check` (a dated verification), `decision` (an ADR), `bug` (known
|
|
30
|
+
broken), `task` (a next action), `question` (needs the owner's answer), `idea`
|
|
31
|
+
(parked), `note` (everything else) — and a project may declare its own labels
|
|
32
|
+
in `symbion.toml` under `[kinds]`; every kind is a label on three bits
|
|
33
|
+
(`status`, `parked`, `verdict`), and `symbion schema` prints the table. An
|
|
34
|
+
**arc** is a named line of work — an epic, if you like — rendered as a
|
|
35
|
+
per-object checklist with its decisions and notes filed under it. The store is
|
|
36
|
+
a private sibling git repo of JSONL, so checking out an old branch never
|
|
37
|
+
time-travels your tickets.
|
|
38
|
+
|
|
39
|
+
Design and rationale: [`docs/superpowers/specs/2026-09-04-symbion-design.md`](https://github.com/phreakocious/symbion/blob/main/docs/superpowers/specs/2026-09-04-symbion-design.md).
|
|
40
|
+
The agent-facing surface, which is also the best command reference:
|
|
41
|
+
`src/symbion/data/skill/SKILL.md` (linked at `~/.claude/skills/symbion`).
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
Python 3.11+ and git. The core needs two packages, `rich` and `rich-argparse`,
|
|
46
|
+
for a person at a terminal: `list`, `show` and `context` as aligned, coloured
|
|
47
|
+
columns with bodies rendered as markdown, and every other command and `--help`
|
|
48
|
+
in the same colours. A pipe gets plain text. One optional extra, `gui`, adds the web UI
|
|
49
|
+
(`symbion serve`):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pipx install 'symbion[gui]'
|
|
53
|
+
# or: uv tool install 'symbion[gui]'
|
|
54
|
+
# drop [gui] for the core alone; for the unreleased main branch:
|
|
55
|
+
# pipx install 'symbion[gui] @ git+https://github.com/phreakocious/symbion'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To work on symbion itself, install a clone editable:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pipx install -e /path/to/symbion # or: uv tool install -e /path/to/symbion
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Without pipx or uv, install into a venv and put the console script on PATH:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cd /path/to/symbion
|
|
68
|
+
python3 -m venv .venv && .venv/bin/pip install -e .
|
|
69
|
+
ln -s "$PWD/.venv/bin/symbion" ~/.local/bin/symbion # or any directory on your PATH
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Verify from any other directory:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
command -v symbion
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This matters more than it looks. The SessionStart hook below finds `symbion`
|
|
79
|
+
through PATH. With a venv-only install the hook prints only `symbion: store
|
|
80
|
+
exists at … but 'symbion' is not on PATH` where a store exists, and nothing
|
|
81
|
+
where none does: easy to read past as a project that never adopted symbion.
|
|
82
|
+
|
|
83
|
+
## Adopt in a fresh repo
|
|
84
|
+
|
|
85
|
+
Run from the repo's root.
|
|
86
|
+
|
|
87
|
+
**1. Create the store and link the agent files.**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
symbion init
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This creates `../<repo>-notes` (a git repo, no remote) with an annotated
|
|
94
|
+
`symbion.toml`; nothing requires the toml, every value has a default. The
|
|
95
|
+
first `init` on a machine also links `~/.claude/skills/symbion` to the skill
|
|
96
|
+
directory inside the installed package (SKILL.md, adoption.md, catalogs.md
|
|
97
|
+
and the SessionStart hook script), so an upgrade reaches every project with nothing
|
|
98
|
+
to refresh, and registers the hook in `~/.claude/settings.json` when that file
|
|
99
|
+
does not exist. If it exists, `init` reads its `hooks.SessionStart` and says
|
|
100
|
+
which of the three it found: `kept hook in ...`, the block to add instead of
|
|
101
|
+
merging, or that the file is not readable as JSON so neither answer holds. A `~/.claude/skills/symbion` that is not that link is left alone and
|
|
102
|
+
named. Nothing is written into the project except a `.symbion` pointer when
|
|
103
|
+
the store is not the default sibling. The hook runs in every project and says
|
|
104
|
+
nothing in one without a store.
|
|
105
|
+
|
|
106
|
+
**2. Check the read side works.** Start a session, or run the hook by hand:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
CLAUDE_PROJECT_DIR=$PWD bash ~/.claude/skills/symbion/session_start.sh
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`init` marks a `.symbion` pointer it writes `(git: ignored)` or `(git: not
|
|
113
|
+
ignored)`; one marked not ignored goes in with the next `git add -A`.
|
|
114
|
+
|
|
115
|
+
| output | meaning |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `symbion: open outside arcs: bug 0, task 0, question 0` (and `; N open in arcs` when arcs hold open rows), then one line per arc (`id done/total age-in-days name`) and the open heads | working |
|
|
118
|
+
| `symbion --dir ../other-notes: open outside arcs: …` | the same, for a store this repo's tree does not name (`--dir`, `SYMBION_DIR`, or the cwd inside a store); the header carries the flag that reaches it, so two summaries read in one session are told apart |
|
|
119
|
+
| the same, then ` N notes not yet in the store's git (symbion commit)` | working; `symbion commit` when the session ends |
|
|
120
|
+
| the same, then ` N per-project symbion copies from an older init: …` | an `init` from before the user-level link left the skill, the hook or its registration in this project; remove them (the link replaces them) |
|
|
121
|
+
| `symbion: store exists at … but 'symbion' is not on PATH` | fix Install |
|
|
122
|
+
| `symbion: no store at …; run \`symbion init\`` | no store yet, or `.symbion`/`SYMBION_DIR` names a path that does not exist |
|
|
123
|
+
| nothing | no store, no `.symbion` pointer and no `SYMBION_DIR` (a project that never adopted symbion), or the hook is not registered in `~/.claude/settings.json` |
|
|
124
|
+
|
|
125
|
+
**3. Tell the agent.** One line in `CLAUDE.md`, for example:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
- We use symbion for durable notes and tickets. Surface friction with it so it can be addressed.
|
|
129
|
+
```
|
|
130
|
+
If that repo's `CLAUDE.md` is gitignored or its owner reviews every change to
|
|
131
|
+
it, propose the line to the owner instead of writing it.
|
|
132
|
+
|
|
133
|
+
## First session
|
|
134
|
+
|
|
135
|
+
A repo that already holds known issues, TODOs, docs or a handoff file has
|
|
136
|
+
history: read `adoption.md` (installed beside `SKILL.md`) before the first
|
|
137
|
+
row, since it says which catalogs to turn on and what to bring in. Either way:
|
|
138
|
+
|
|
139
|
+
**The first note is a check, and it goes in now.** It is the kind that earns
|
|
140
|
+
its place fastest: dated, re-checkable, and it goes stale visibly. A check
|
|
141
|
+
stamps HEAD and whether the tree was dirty, and untracked files count, so on
|
|
142
|
+
a dirty tree (the CLAUDE.md edit from step 3, or any untracked file) it reads
|
|
143
|
+
`state=unverifiable (dirty tree)`. That is honest, not blocked: commit, write
|
|
144
|
+
it again, and it reads `current`. Files the checked command writes count too:
|
|
145
|
+
`pytest` leaves `__pycache__/`, so git-ignore those first or the second write
|
|
146
|
+
reads dirty again. A check on something outside the repo (DNS, a host)
|
|
147
|
+
takes `--external` and lists its age instead. A bootstrap that ends with no
|
|
148
|
+
check row skipped the step that pays first.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
symbion add --kind check --type commit --name HEAD --checked "pytest -q" --result "147 passed"
|
|
152
|
+
symbion list --kind check # unverifiable (dirty tree) on a dirty tree; current on a clean one; behind N once HEAD moves
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**The first checklist is an arc over free-form items.** Use `add` per
|
|
156
|
+
item, not `seed`. `seed` mints one bodiless task per name and exists for
|
|
157
|
+
fanning out over a catalog; a hand-picked list wants a body on each ticket.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
aid=$(symbion arc create --name "Adoption" --scope item --desc "Make this usable by someone who was not in the room.")
|
|
161
|
+
symbion add --kind task --type item --name "write the README" --arc-id "$aid" --body "What done looks like: a stranger can install and record a note."
|
|
162
|
+
symbion arc todo "$aid" --json # open items, same rows as `list --json`
|
|
163
|
+
symbion resolve <id> # tick the box
|
|
164
|
+
symbion arc list # done/total per arc
|
|
165
|
+
symbion list --arc "$aid" --json # every item, resolved included
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Commit the store at the end of a session.** Writes never auto-commit, so a
|
|
169
|
+
git failure can never block a note. `symbion summary` shows the uncommitted
|
|
170
|
+
count until you do.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
symbion commit -m "adoption: README ticket closed"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Adopting a repo with history?** The store starts empty and the project does
|
|
177
|
+
not. `adoption.md`, installed beside `SKILL.md`, is the procedure, written
|
|
178
|
+
from the adoptions it cites. The rows it produces go in as one `symbion add
|
|
179
|
+
--from-json -`, one JSON object per line, validated together before any is
|
|
180
|
+
written.
|
|
181
|
+
|
|
182
|
+
## Browse it (optional)
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
symbion serve # needs the gui extra: see Install
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
A local page at `http://127.0.0.1:43210` (another free port when that one is
|
|
189
|
+
busy; `serve` prints the address, and `--port` picks one): boards, a filtered note list where
|
|
190
|
+
every tag/kind/author/target chip is a link, a search box (`/` focuses it; every
|
|
191
|
+
word, in any case, taken literally, or a pasted note id), a page per note with
|
|
192
|
+
its earlier versions, a tag index, and arc checklists you tick. It writes as **you** — `SYMBION_AUTHOR`, else
|
|
193
|
+
`git user.name` — never as `claude`, and shows the resolved name in the top
|
|
194
|
+
bar. `--author NAME` overrides.
|
|
195
|
+
|
|
196
|
+
`serve` is the only thing that needs the extra.
|
|
197
|
+
|
|
198
|
+
## Catalogs, when a homogeneous set exists
|
|
199
|
+
|
|
200
|
+
A catalog is a shell command that prints one name per line and nothing else.
|
|
201
|
+
Declare it in `../<repo>-notes/symbion.toml`:
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[catalogs]
|
|
205
|
+
file = "git ls-files --cached --others --exclude-standard '*.py'"
|
|
206
|
+
test = "git ls-files --cached --others --exclude-standard 'tests/test_*.py'"
|
|
207
|
+
|
|
208
|
+
[renames]
|
|
209
|
+
file = "git" # so reconcile can follow renamed files
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Each key becomes a target type: `--type file --name src/x.py`. An arc
|
|
213
|
+
seeded over it gets one task per member, and `arc reconcile` reports
|
|
214
|
+
which are live, renamed, or stale as the tree changes.
|
|
215
|
+
|
|
216
|
+
Scope the command to where notes will attach: no pathspec when docs and
|
|
217
|
+
results matter, a glob when only code does. A broad catalog costs nothing
|
|
218
|
+
except a refusal on an ambiguous substring. `--others --exclude-standard`
|
|
219
|
+
adds files not yet committed: without it, a note on a file made this session
|
|
220
|
+
warns `taken as typed` on a correct name, and that warning is the only thing
|
|
221
|
+
that catches a typo.
|
|
222
|
+
|
|
223
|
+
**Always dry-run the first seed of any catalog type.** A command's output
|
|
224
|
+
depends on the tool's version and on the project's own config; the only way
|
|
225
|
+
to know what it prints is to run it here, today.
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
symbion arc seed --scope file --dry-run # no arc needed; writes nothing
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Names resolve exact, then unique substring, else verbatim. `cli.py` is
|
|
232
|
+
ambiguous between `src/symbion/cli.py` and `tests/test_cli.py` and is refused;
|
|
233
|
+
use the full path.
|
|
234
|
+
|
|
235
|
+
A catalog type whose names are **measured** (a reading, a serial) needs a
|
|
236
|
+
resolver too, or a near-miss mints a second target: substring matching cannot
|
|
237
|
+
see that `3.1416` is `3.14159`. So does every **store-derived** catalog
|
|
238
|
+
(`symbion list … --type X | jq …`): it is empty until its first `X` row
|
|
239
|
+
exists, and that row cannot be written against an empty catalog, so the first
|
|
240
|
+
`add` is refused until a resolver decides what a first sighting stores.
|
|
241
|
+
Declare one beside the catalog:
|
|
242
|
+
|
|
243
|
+
```toml
|
|
244
|
+
[resolvers]
|
|
245
|
+
reading = "python3 tools/resolve_reading.py" # stdin: query, then names; exit 2 = ambiguous
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The starter `symbion.toml` carries the full protocol. A failing resolver
|
|
249
|
+
stores nothing; it never falls back to the substring rule.
|
|
250
|
+
|
|
251
|
+
Within one write (a batch `add --from-json`, an `arc seed --name …`), names
|
|
252
|
+
already resolved by earlier rows are candidates for later rows, resolver or
|
|
253
|
+
not — so a batch that names `src/parser.py` and then `p` against a catalog
|
|
254
|
+
holding `src/pipe.py` is refused as ambiguous where two separate adds would
|
|
255
|
+
store both.
|
|
256
|
+
|
|
257
|
+
## Conventions
|
|
258
|
+
|
|
259
|
+
- **Bodies are markdown.** `list --full`, `show` and `context` print them as
|
|
260
|
+
written on a pipe and render them on a terminal; a plain `list` page and
|
|
261
|
+
`summary` flatten each to one clipped line.
|
|
262
|
+
- **A terminal and a pipe get different text.** A terminal shows each row as
|
|
263
|
+
columns: the id's last 10 characters, its age, a `○`/`✓` status mark, the
|
|
264
|
+
kind, the target, its tags and a count of its refs, then one body line cut to
|
|
265
|
+
the terminal's width. `show` takes that id tail. A pipe, which is what
|
|
266
|
+
scripts and agents read, always gets the plain line.
|
|
267
|
+
- **`idea` is a kind, and parked means parked.** `add --kind idea --type project
|
|
268
|
+
--body …` shelves a thought; it never prints in `arc todo` or `context`, and
|
|
269
|
+
in `summary` only when it carries a `--due` date that is near or past (how a
|
|
270
|
+
thought comes back on a date) or a person other than the reader wrote it
|
|
271
|
+
(`from <author>`). Close it with `resolve <id> --add-tag adopted --body "…"` or
|
|
272
|
+
`--add-tag retired`. `list --kind idea` is the shelf; `list --tag idea` still
|
|
273
|
+
finds rows written before it was a kind.
|
|
274
|
+
- **Kinds are the project's.** Rename or add a label in `symbion.toml`
|
|
275
|
+
`[kinds]`; a label that already has rows also needs a one-line `sed` over
|
|
276
|
+
`notes.jsonl` (`init` writes the recipe into the toml). `status` +
|
|
277
|
+
`verdict` is a pre-registration: `resolve --result` closes it.
|
|
278
|
+
- **`priority` tag.** `symbion supersede <id> --add-tag priority` stars a note,
|
|
279
|
+
whoever writes it, so the next session's summary lists it under `priority`
|
|
280
|
+
until the row is resolved or `--rm-tag priority` takes the star off.
|
|
281
|
+
- **Due dates.** `--due 2026-10-01` (or an ISO datetime) on any status row.
|
|
282
|
+
Past due or due within 7 days, an open row leads the session-start summary
|
|
283
|
+
as `overdue 2d` or `due in 3d`, in an arc or not; `list --overdue` lists
|
|
284
|
+
the ones past due, and `supersede <id> --due ''` clears one.
|
|
285
|
+
- **Author.** Inside a Claude Code session the author is `claude`; otherwise
|
|
286
|
+
git `user.name`. `--author` or `SYMBION_AUTHOR` override. A human typing
|
|
287
|
+
`! symbion add …` inside a session is recorded as `claude` without one.
|
|
288
|
+
An agent's session start lists open rows a person wrote, parked ones
|
|
289
|
+
included, as `from <author>`: the owner's word reaches the next session.
|
|
290
|
+
- **Close done work as a resolved task**, not by deleting the ticket.
|
|
291
|
+
`add --kind task --status resolved --body "done: …"` records work that
|
|
292
|
+
was finished before it was ever ticketed.
|
|
293
|
+
|
|
294
|
+
## Where things live
|
|
295
|
+
|
|
296
|
+
- **Store:** `../<repo>-notes`, beside the *main* worktree. Linked worktrees
|
|
297
|
+
share it. `SYMBION_DIR` or `--dir PATH` override; `--dir` may sit anywhere
|
|
298
|
+
in the argv. A `<repo>-notes` store named from another repo resolves in
|
|
299
|
+
`<repo>`, and says so on stderr. So does a command run from inside the
|
|
300
|
+
store itself (to edit `symbion.toml`, say): the store is the one you are
|
|
301
|
+
in, and `init` there refuses.
|
|
302
|
+
- **A store that is not named after the repo:** put its path in a
|
|
303
|
+
`.symbion` file in the repo root — one line, relative to the repo or
|
|
304
|
+
absolute, first non-blank line wins. `symbion --dir ../other-notes init`
|
|
305
|
+
writes it for you. Commit it; a relative pointer survives a clone.
|
|
306
|
+
This knob cannot live in `symbion.toml`, because that file is inside the
|
|
307
|
+
store you are trying to find.
|
|
308
|
+
|
|
309
|
+
It is worth setting whenever the two names differ: rename a repo directory
|
|
310
|
+
and every command reports `no store at ../<new-name>-notes` (the old store
|
|
311
|
+
is still beside the old name) until a `.symbion` file names it. A write
|
|
312
|
+
refuses too; only `symbion init` creates a store.
|
|
313
|
+
- **Files:** `notes.jsonl` (append-only; corrections are new rows that
|
|
314
|
+
supersede old ones), `arcs.jsonl`, `symbion.toml`.
|
|
315
|
+
- **Reading:** `symbion summary` (what the hook prints), `symbion context
|
|
316
|
+
--target TYPE:NAME`, `symbion context --branch REF`, `symbion list --json`.
|
|
317
|
+
Every command that prints rows takes `--json` (`tags` prints counts and
|
|
318
|
+
does not).
|
|
319
|
+
|
|
320
|
+
## Development
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
python3 -m venv .venv && .venv/bin/pip install -e '.[test,gui]'
|
|
324
|
+
.venv/bin/python -m pytest -q
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The specs are dated design records: why each decision was made, and the design as of that date. `SKILL.md` is
|
|
328
|
+
what an agent reads; keep it short and keep its footgun list honest. It lives
|
|
329
|
+
at `src/symbion/data/skill/SKILL.md`, beside `adoption.md` and the hook. If
|
|
330
|
+
this clone is your installed symbion (an editable install, then `symbion
|
|
331
|
+
init`), `~/.claude/skills/symbion` links to that directory: an edit reaches
|
|
332
|
+
every session on the machine at once, the same way an edit to `src/` reaches
|
|
333
|
+
every `symbion` call.
|
symbion-0.1.0/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# symbion
|
|
2
|
+
|
|
3
|
+
A per-project notebook and ticket registry for small projects, driven by a
|
|
4
|
+
human and a coding agent through one CLI. The CLI works on its own; the agent
|
|
5
|
+
surface (a skill and a SessionStart hook) is written for Claude Code.
|
|
6
|
+
|
|
7
|
+
A note is dated, attributed, retractable, and attached to something stable: a
|
|
8
|
+
commit, a file, a free-form item, or the project as a whole. Seven kinds by
|
|
9
|
+
default — `check` (a dated verification), `decision` (an ADR), `bug` (known
|
|
10
|
+
broken), `task` (a next action), `question` (needs the owner's answer), `idea`
|
|
11
|
+
(parked), `note` (everything else) — and a project may declare its own labels
|
|
12
|
+
in `symbion.toml` under `[kinds]`; every kind is a label on three bits
|
|
13
|
+
(`status`, `parked`, `verdict`), and `symbion schema` prints the table. An
|
|
14
|
+
**arc** is a named line of work — an epic, if you like — rendered as a
|
|
15
|
+
per-object checklist with its decisions and notes filed under it. The store is
|
|
16
|
+
a private sibling git repo of JSONL, so checking out an old branch never
|
|
17
|
+
time-travels your tickets.
|
|
18
|
+
|
|
19
|
+
Design and rationale: [`docs/superpowers/specs/2026-09-04-symbion-design.md`](https://github.com/phreakocious/symbion/blob/main/docs/superpowers/specs/2026-09-04-symbion-design.md).
|
|
20
|
+
The agent-facing surface, which is also the best command reference:
|
|
21
|
+
`src/symbion/data/skill/SKILL.md` (linked at `~/.claude/skills/symbion`).
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Python 3.11+ and git. The core needs two packages, `rich` and `rich-argparse`,
|
|
26
|
+
for a person at a terminal: `list`, `show` and `context` as aligned, coloured
|
|
27
|
+
columns with bodies rendered as markdown, and every other command and `--help`
|
|
28
|
+
in the same colours. A pipe gets plain text. One optional extra, `gui`, adds the web UI
|
|
29
|
+
(`symbion serve`):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pipx install 'symbion[gui]'
|
|
33
|
+
# or: uv tool install 'symbion[gui]'
|
|
34
|
+
# drop [gui] for the core alone; for the unreleased main branch:
|
|
35
|
+
# pipx install 'symbion[gui] @ git+https://github.com/phreakocious/symbion'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To work on symbion itself, install a clone editable:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install -e /path/to/symbion # or: uv tool install -e /path/to/symbion
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Without pipx or uv, install into a venv and put the console script on PATH:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd /path/to/symbion
|
|
48
|
+
python3 -m venv .venv && .venv/bin/pip install -e .
|
|
49
|
+
ln -s "$PWD/.venv/bin/symbion" ~/.local/bin/symbion # or any directory on your PATH
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Verify from any other directory:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
command -v symbion
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This matters more than it looks. The SessionStart hook below finds `symbion`
|
|
59
|
+
through PATH. With a venv-only install the hook prints only `symbion: store
|
|
60
|
+
exists at … but 'symbion' is not on PATH` where a store exists, and nothing
|
|
61
|
+
where none does: easy to read past as a project that never adopted symbion.
|
|
62
|
+
|
|
63
|
+
## Adopt in a fresh repo
|
|
64
|
+
|
|
65
|
+
Run from the repo's root.
|
|
66
|
+
|
|
67
|
+
**1. Create the store and link the agent files.**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
symbion init
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This creates `../<repo>-notes` (a git repo, no remote) with an annotated
|
|
74
|
+
`symbion.toml`; nothing requires the toml, every value has a default. The
|
|
75
|
+
first `init` on a machine also links `~/.claude/skills/symbion` to the skill
|
|
76
|
+
directory inside the installed package (SKILL.md, adoption.md, catalogs.md
|
|
77
|
+
and the SessionStart hook script), so an upgrade reaches every project with nothing
|
|
78
|
+
to refresh, and registers the hook in `~/.claude/settings.json` when that file
|
|
79
|
+
does not exist. If it exists, `init` reads its `hooks.SessionStart` and says
|
|
80
|
+
which of the three it found: `kept hook in ...`, the block to add instead of
|
|
81
|
+
merging, or that the file is not readable as JSON so neither answer holds. A `~/.claude/skills/symbion` that is not that link is left alone and
|
|
82
|
+
named. Nothing is written into the project except a `.symbion` pointer when
|
|
83
|
+
the store is not the default sibling. The hook runs in every project and says
|
|
84
|
+
nothing in one without a store.
|
|
85
|
+
|
|
86
|
+
**2. Check the read side works.** Start a session, or run the hook by hand:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
CLAUDE_PROJECT_DIR=$PWD bash ~/.claude/skills/symbion/session_start.sh
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`init` marks a `.symbion` pointer it writes `(git: ignored)` or `(git: not
|
|
93
|
+
ignored)`; one marked not ignored goes in with the next `git add -A`.
|
|
94
|
+
|
|
95
|
+
| output | meaning |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `symbion: open outside arcs: bug 0, task 0, question 0` (and `; N open in arcs` when arcs hold open rows), then one line per arc (`id done/total age-in-days name`) and the open heads | working |
|
|
98
|
+
| `symbion --dir ../other-notes: open outside arcs: …` | the same, for a store this repo's tree does not name (`--dir`, `SYMBION_DIR`, or the cwd inside a store); the header carries the flag that reaches it, so two summaries read in one session are told apart |
|
|
99
|
+
| the same, then ` N notes not yet in the store's git (symbion commit)` | working; `symbion commit` when the session ends |
|
|
100
|
+
| the same, then ` N per-project symbion copies from an older init: …` | an `init` from before the user-level link left the skill, the hook or its registration in this project; remove them (the link replaces them) |
|
|
101
|
+
| `symbion: store exists at … but 'symbion' is not on PATH` | fix Install |
|
|
102
|
+
| `symbion: no store at …; run \`symbion init\`` | no store yet, or `.symbion`/`SYMBION_DIR` names a path that does not exist |
|
|
103
|
+
| nothing | no store, no `.symbion` pointer and no `SYMBION_DIR` (a project that never adopted symbion), or the hook is not registered in `~/.claude/settings.json` |
|
|
104
|
+
|
|
105
|
+
**3. Tell the agent.** One line in `CLAUDE.md`, for example:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
- We use symbion for durable notes and tickets. Surface friction with it so it can be addressed.
|
|
109
|
+
```
|
|
110
|
+
If that repo's `CLAUDE.md` is gitignored or its owner reviews every change to
|
|
111
|
+
it, propose the line to the owner instead of writing it.
|
|
112
|
+
|
|
113
|
+
## First session
|
|
114
|
+
|
|
115
|
+
A repo that already holds known issues, TODOs, docs or a handoff file has
|
|
116
|
+
history: read `adoption.md` (installed beside `SKILL.md`) before the first
|
|
117
|
+
row, since it says which catalogs to turn on and what to bring in. Either way:
|
|
118
|
+
|
|
119
|
+
**The first note is a check, and it goes in now.** It is the kind that earns
|
|
120
|
+
its place fastest: dated, re-checkable, and it goes stale visibly. A check
|
|
121
|
+
stamps HEAD and whether the tree was dirty, and untracked files count, so on
|
|
122
|
+
a dirty tree (the CLAUDE.md edit from step 3, or any untracked file) it reads
|
|
123
|
+
`state=unverifiable (dirty tree)`. That is honest, not blocked: commit, write
|
|
124
|
+
it again, and it reads `current`. Files the checked command writes count too:
|
|
125
|
+
`pytest` leaves `__pycache__/`, so git-ignore those first or the second write
|
|
126
|
+
reads dirty again. A check on something outside the repo (DNS, a host)
|
|
127
|
+
takes `--external` and lists its age instead. A bootstrap that ends with no
|
|
128
|
+
check row skipped the step that pays first.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
symbion add --kind check --type commit --name HEAD --checked "pytest -q" --result "147 passed"
|
|
132
|
+
symbion list --kind check # unverifiable (dirty tree) on a dirty tree; current on a clean one; behind N once HEAD moves
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**The first checklist is an arc over free-form items.** Use `add` per
|
|
136
|
+
item, not `seed`. `seed` mints one bodiless task per name and exists for
|
|
137
|
+
fanning out over a catalog; a hand-picked list wants a body on each ticket.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
aid=$(symbion arc create --name "Adoption" --scope item --desc "Make this usable by someone who was not in the room.")
|
|
141
|
+
symbion add --kind task --type item --name "write the README" --arc-id "$aid" --body "What done looks like: a stranger can install and record a note."
|
|
142
|
+
symbion arc todo "$aid" --json # open items, same rows as `list --json`
|
|
143
|
+
symbion resolve <id> # tick the box
|
|
144
|
+
symbion arc list # done/total per arc
|
|
145
|
+
symbion list --arc "$aid" --json # every item, resolved included
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Commit the store at the end of a session.** Writes never auto-commit, so a
|
|
149
|
+
git failure can never block a note. `symbion summary` shows the uncommitted
|
|
150
|
+
count until you do.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
symbion commit -m "adoption: README ticket closed"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Adopting a repo with history?** The store starts empty and the project does
|
|
157
|
+
not. `adoption.md`, installed beside `SKILL.md`, is the procedure, written
|
|
158
|
+
from the adoptions it cites. The rows it produces go in as one `symbion add
|
|
159
|
+
--from-json -`, one JSON object per line, validated together before any is
|
|
160
|
+
written.
|
|
161
|
+
|
|
162
|
+
## Browse it (optional)
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
symbion serve # needs the gui extra: see Install
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
A local page at `http://127.0.0.1:43210` (another free port when that one is
|
|
169
|
+
busy; `serve` prints the address, and `--port` picks one): boards, a filtered note list where
|
|
170
|
+
every tag/kind/author/target chip is a link, a search box (`/` focuses it; every
|
|
171
|
+
word, in any case, taken literally, or a pasted note id), a page per note with
|
|
172
|
+
its earlier versions, a tag index, and arc checklists you tick. It writes as **you** — `SYMBION_AUTHOR`, else
|
|
173
|
+
`git user.name` — never as `claude`, and shows the resolved name in the top
|
|
174
|
+
bar. `--author NAME` overrides.
|
|
175
|
+
|
|
176
|
+
`serve` is the only thing that needs the extra.
|
|
177
|
+
|
|
178
|
+
## Catalogs, when a homogeneous set exists
|
|
179
|
+
|
|
180
|
+
A catalog is a shell command that prints one name per line and nothing else.
|
|
181
|
+
Declare it in `../<repo>-notes/symbion.toml`:
|
|
182
|
+
|
|
183
|
+
```toml
|
|
184
|
+
[catalogs]
|
|
185
|
+
file = "git ls-files --cached --others --exclude-standard '*.py'"
|
|
186
|
+
test = "git ls-files --cached --others --exclude-standard 'tests/test_*.py'"
|
|
187
|
+
|
|
188
|
+
[renames]
|
|
189
|
+
file = "git" # so reconcile can follow renamed files
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Each key becomes a target type: `--type file --name src/x.py`. An arc
|
|
193
|
+
seeded over it gets one task per member, and `arc reconcile` reports
|
|
194
|
+
which are live, renamed, or stale as the tree changes.
|
|
195
|
+
|
|
196
|
+
Scope the command to where notes will attach: no pathspec when docs and
|
|
197
|
+
results matter, a glob when only code does. A broad catalog costs nothing
|
|
198
|
+
except a refusal on an ambiguous substring. `--others --exclude-standard`
|
|
199
|
+
adds files not yet committed: without it, a note on a file made this session
|
|
200
|
+
warns `taken as typed` on a correct name, and that warning is the only thing
|
|
201
|
+
that catches a typo.
|
|
202
|
+
|
|
203
|
+
**Always dry-run the first seed of any catalog type.** A command's output
|
|
204
|
+
depends on the tool's version and on the project's own config; the only way
|
|
205
|
+
to know what it prints is to run it here, today.
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
symbion arc seed --scope file --dry-run # no arc needed; writes nothing
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Names resolve exact, then unique substring, else verbatim. `cli.py` is
|
|
212
|
+
ambiguous between `src/symbion/cli.py` and `tests/test_cli.py` and is refused;
|
|
213
|
+
use the full path.
|
|
214
|
+
|
|
215
|
+
A catalog type whose names are **measured** (a reading, a serial) needs a
|
|
216
|
+
resolver too, or a near-miss mints a second target: substring matching cannot
|
|
217
|
+
see that `3.1416` is `3.14159`. So does every **store-derived** catalog
|
|
218
|
+
(`symbion list … --type X | jq …`): it is empty until its first `X` row
|
|
219
|
+
exists, and that row cannot be written against an empty catalog, so the first
|
|
220
|
+
`add` is refused until a resolver decides what a first sighting stores.
|
|
221
|
+
Declare one beside the catalog:
|
|
222
|
+
|
|
223
|
+
```toml
|
|
224
|
+
[resolvers]
|
|
225
|
+
reading = "python3 tools/resolve_reading.py" # stdin: query, then names; exit 2 = ambiguous
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The starter `symbion.toml` carries the full protocol. A failing resolver
|
|
229
|
+
stores nothing; it never falls back to the substring rule.
|
|
230
|
+
|
|
231
|
+
Within one write (a batch `add --from-json`, an `arc seed --name …`), names
|
|
232
|
+
already resolved by earlier rows are candidates for later rows, resolver or
|
|
233
|
+
not — so a batch that names `src/parser.py` and then `p` against a catalog
|
|
234
|
+
holding `src/pipe.py` is refused as ambiguous where two separate adds would
|
|
235
|
+
store both.
|
|
236
|
+
|
|
237
|
+
## Conventions
|
|
238
|
+
|
|
239
|
+
- **Bodies are markdown.** `list --full`, `show` and `context` print them as
|
|
240
|
+
written on a pipe and render them on a terminal; a plain `list` page and
|
|
241
|
+
`summary` flatten each to one clipped line.
|
|
242
|
+
- **A terminal and a pipe get different text.** A terminal shows each row as
|
|
243
|
+
columns: the id's last 10 characters, its age, a `○`/`✓` status mark, the
|
|
244
|
+
kind, the target, its tags and a count of its refs, then one body line cut to
|
|
245
|
+
the terminal's width. `show` takes that id tail. A pipe, which is what
|
|
246
|
+
scripts and agents read, always gets the plain line.
|
|
247
|
+
- **`idea` is a kind, and parked means parked.** `add --kind idea --type project
|
|
248
|
+
--body …` shelves a thought; it never prints in `arc todo` or `context`, and
|
|
249
|
+
in `summary` only when it carries a `--due` date that is near or past (how a
|
|
250
|
+
thought comes back on a date) or a person other than the reader wrote it
|
|
251
|
+
(`from <author>`). Close it with `resolve <id> --add-tag adopted --body "…"` or
|
|
252
|
+
`--add-tag retired`. `list --kind idea` is the shelf; `list --tag idea` still
|
|
253
|
+
finds rows written before it was a kind.
|
|
254
|
+
- **Kinds are the project's.** Rename or add a label in `symbion.toml`
|
|
255
|
+
`[kinds]`; a label that already has rows also needs a one-line `sed` over
|
|
256
|
+
`notes.jsonl` (`init` writes the recipe into the toml). `status` +
|
|
257
|
+
`verdict` is a pre-registration: `resolve --result` closes it.
|
|
258
|
+
- **`priority` tag.** `symbion supersede <id> --add-tag priority` stars a note,
|
|
259
|
+
whoever writes it, so the next session's summary lists it under `priority`
|
|
260
|
+
until the row is resolved or `--rm-tag priority` takes the star off.
|
|
261
|
+
- **Due dates.** `--due 2026-10-01` (or an ISO datetime) on any status row.
|
|
262
|
+
Past due or due within 7 days, an open row leads the session-start summary
|
|
263
|
+
as `overdue 2d` or `due in 3d`, in an arc or not; `list --overdue` lists
|
|
264
|
+
the ones past due, and `supersede <id> --due ''` clears one.
|
|
265
|
+
- **Author.** Inside a Claude Code session the author is `claude`; otherwise
|
|
266
|
+
git `user.name`. `--author` or `SYMBION_AUTHOR` override. A human typing
|
|
267
|
+
`! symbion add …` inside a session is recorded as `claude` without one.
|
|
268
|
+
An agent's session start lists open rows a person wrote, parked ones
|
|
269
|
+
included, as `from <author>`: the owner's word reaches the next session.
|
|
270
|
+
- **Close done work as a resolved task**, not by deleting the ticket.
|
|
271
|
+
`add --kind task --status resolved --body "done: …"` records work that
|
|
272
|
+
was finished before it was ever ticketed.
|
|
273
|
+
|
|
274
|
+
## Where things live
|
|
275
|
+
|
|
276
|
+
- **Store:** `../<repo>-notes`, beside the *main* worktree. Linked worktrees
|
|
277
|
+
share it. `SYMBION_DIR` or `--dir PATH` override; `--dir` may sit anywhere
|
|
278
|
+
in the argv. A `<repo>-notes` store named from another repo resolves in
|
|
279
|
+
`<repo>`, and says so on stderr. So does a command run from inside the
|
|
280
|
+
store itself (to edit `symbion.toml`, say): the store is the one you are
|
|
281
|
+
in, and `init` there refuses.
|
|
282
|
+
- **A store that is not named after the repo:** put its path in a
|
|
283
|
+
`.symbion` file in the repo root — one line, relative to the repo or
|
|
284
|
+
absolute, first non-blank line wins. `symbion --dir ../other-notes init`
|
|
285
|
+
writes it for you. Commit it; a relative pointer survives a clone.
|
|
286
|
+
This knob cannot live in `symbion.toml`, because that file is inside the
|
|
287
|
+
store you are trying to find.
|
|
288
|
+
|
|
289
|
+
It is worth setting whenever the two names differ: rename a repo directory
|
|
290
|
+
and every command reports `no store at ../<new-name>-notes` (the old store
|
|
291
|
+
is still beside the old name) until a `.symbion` file names it. A write
|
|
292
|
+
refuses too; only `symbion init` creates a store.
|
|
293
|
+
- **Files:** `notes.jsonl` (append-only; corrections are new rows that
|
|
294
|
+
supersede old ones), `arcs.jsonl`, `symbion.toml`.
|
|
295
|
+
- **Reading:** `symbion summary` (what the hook prints), `symbion context
|
|
296
|
+
--target TYPE:NAME`, `symbion context --branch REF`, `symbion list --json`.
|
|
297
|
+
Every command that prints rows takes `--json` (`tags` prints counts and
|
|
298
|
+
does not).
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
python3 -m venv .venv && .venv/bin/pip install -e '.[test,gui]'
|
|
304
|
+
.venv/bin/python -m pytest -q
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The specs are dated design records: why each decision was made, and the design as of that date. `SKILL.md` is
|
|
308
|
+
what an agent reads; keep it short and keep its footgun list honest. It lives
|
|
309
|
+
at `src/symbion/data/skill/SKILL.md`, beside `adoption.md` and the hook. If
|
|
310
|
+
this clone is your installed symbion (an editable install, then `symbion
|
|
311
|
+
init`), `~/.claude/skills/symbion` links to that directory: an edit reaches
|
|
312
|
+
every session on the machine at once, the same way an edit to `src/` reaches
|
|
313
|
+
every `symbion` call.
|