imprnt 0.1.3 → 0.1.4-edge.101
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.
- package/CLAUDE.md +22 -5
- package/README.md +56 -15
- package/dist/cli.js +1053 -213
- package/dist/imp.js +1053 -213
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -94,7 +94,24 @@ vault/
|
|
|
94
94
|
|
|
95
95
|
**holdings/ vs reference: the cut is CHANGING STATE, not the word "tool."** Anything you follow over time is a holding: a premium, a dose, a balance, a status, a renewal. A paid subscription with a cost/renewal (a transit pass) is a holding even if you'd call it a "service." Static stuff with no state to track is **not** a holding: a free CLI, your dotfiles, tech-stack preferences are pure reference → file them in their domain (`work/`, `health/`). This cut is what keeps `holdings/` a real tracked-entity type instead of drifting back into a `things/` junk drawer.
|
|
96
96
|
|
|
97
|
-
**Domains are user-defined.** One person's set is `identity/ health/ finances/ work/ life/`. A consultant's would add `clients/`, a researcher's `topics/`. imprnt ships the mechanism + sensible defaults, not a fixed domain set
|
|
97
|
+
**Domains are user-defined, and the vault declares them.** One person's set is `identity/ health/ finances/ work/ life/`. A consultant's would add `clients/`, a researcher's `topics/`. imprnt ships the mechanism + sensible defaults, not a fixed domain set — `vault/_folders.md` is where a vault says which of its folders hold **entities**, which hold **domains**, which hold **forms**, and which hold a **mount**. No file means the shipped defaults, so nothing changes until you want it to.
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
## Entities
|
|
101
|
+
people, orgs, holdings
|
|
102
|
+
|
|
103
|
+
## Domains
|
|
104
|
+
identity, health, finances, work, life, clients
|
|
105
|
+
|
|
106
|
+
## Mounts
|
|
107
|
+
household
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A **mount** is the fourth role and the one the defaults have no name for: a folder holding a tree that is complete on its own and maintained elsewhere — a shared repo checked out inside `vault/`, an imported corpus. Its notes are not part of this vault's entity graph, so `check` does not ask them to link one. Without that, every note under a folder the defaults never heard of is reported disconnected forever with nothing the owner can do, and permanent noise in `_needs-review.md` is worse than no check at all. An absent section keeps its default; an empty one is a deliberate "none". `type:` in frontmatter (below) records *what each note is* even when it sits in a domain folder, so nothing is lost by filing topically.
|
|
111
|
+
|
|
112
|
+
A mount may carry its **own `_folders.md`**, and `check` then applies those roles scoped to the mount (a note in a mount domain folder carries `domain:` matching its folder *inside* the mount). A mount that declares nothing is exempt from the **role** checks alone, the folder-versus-field ones that need a declaration to compare against, and `ingest --apply` files into such a mount using the shipped default folders. The **seam findings** below fire on every mount, declared or not: they watch the boundary itself, which exists the moment a folder is named a mount. Its evidence locker is `<mount>/_raw/`, underscore-prefixed so the walk skips it exactly as it skips `raw/`. `ingest --apply` files a staged note into a mount when the note names one (**`mount: household`**, a routing key stripped from the filed bytes), which only prefixes the ordinary filing decision: `type: person` lands in the mount's own `people/`, a `domain:` note in the mount's own life-area folder, and the note carries no `source:` into your private `raw/`.
|
|
113
|
+
|
|
114
|
+
**Sharing a note is moving it: `imprnt vault move <note> <mount>/<folder>`.** The note goes into the mount, the private copy is deleted, its path becomes its new ID, every `[[old/path]]` in the vault is rewritten, `domain:` is set from the destination (or dropped when the mount declares no roles), one `log.md` line is appended on the mover's side, and the command prints who now sees it plus what only a human can settle. No stub, and no `copy` verb: a copy is a fork, and the two halves drift. It **refuses** a note whose `source:` points into this vault's private `raw/` (dead across the seam, and `--force` never overrides that), or whose entity links have no answer inside the mount (`--force` moves anyway and leaves them). Two `check` findings watch the same boundary afterwards: **`seam-leak`**, a mount note whose link resolves only through this vault's private folders, so the note is broken for everyone else who has the mount, and **`seam-dead-source`**, a mount note carrying `source: "[[raw/...]]"`. Both are reported in the vault doing the checking — the only side that can see what the link points at, and the side whose `_needs-review.md` is private. The move is written so a crash leaves a trace: the destination is written, the `log.md` line goes down marked in progress, the source is deleted, the links are rewritten, and the line is finalised. A marker still sitting there is **`move-fork`**, one note in two places, which `check` reports with the path to delete.
|
|
98
115
|
|
|
99
116
|
## Frontmatter contract
|
|
100
117
|
|
|
@@ -143,7 +160,7 @@ The discipline that keeps the list lean moved **off the write path** to a non-bl
|
|
|
143
160
|
|
|
144
161
|
imprnt keeps exactly two "robot" helpers, carried over from PAI (the system imprnt grew out of), and both are **commands you run**, never background hooks. Always-on auto-magic is the cost pattern this avoids: machinery you pay for in every session whether you use it or not.
|
|
145
162
|
|
|
146
|
-
- **`imprnt check`** - integrity + regenerate. Flags orphan `[[links]]`, notes that resolve no entity, **untagged notes** (empty `tags:`, the topic axis is blank), unclassified snapshots,
|
|
163
|
+
- **`imprnt check`** - integrity + regenerate. Flags orphan `[[links]]`, notes that resolve no entity, **untagged notes** (empty `tags:`, the topic axis is blank), unclassified snapshots, near-duplicate tags, the two seam findings above, **`conflict-markers`** (a note holding a `<<<<<<<` line git left behind), and **`move-fork`** (a `vault move` that never finished). Rebuilds `index.md` deterministically from every note's `summary` + tags + links, and **syncs new tags into `_tags.md`** (the auto-growing vocabulary). Run it after an ingest or any hand-edit. Writes only the three non-note control files (`index.md`, `_tags.md`, and `_needs-review.md`, where it regenerates only its own delimited section), never mutates a note. (The dedup audit catches *spelling*-near tags only. *Semantic* synonyms like `clothing`/`wardrobe` are the LLM's call at write time + a `_tags.md` synonym entry. Code never merges meaning.)
|
|
147
164
|
- **harvest** - the conversation→vault bridge: at the end of a chat, you ask the assistant to harvest the session ("harvest this", "wrap it up") and it hands the durable learnings to `imprnt ingest`, so a decision made in conversation becomes a filed note. Conscious, on demand, never automatic.
|
|
148
165
|
|
|
149
166
|
## Updating & contradictions
|
|
@@ -173,14 +190,14 @@ Core is the vault + `ingest → recall → check`. Everything else (a task-mirro
|
|
|
173
190
|
- **The one rule:** core never knows a plugin exists. Litmus: **you can add or remove any plugin with zero edits to `packages/imprnt/`.** A plugin depends on exactly two things: `vault/` notes (+ their frontmatter *format*) and its own sibling folder. Nothing else. Not core code, not another plugin, not another plugin's folder/labels.
|
|
174
191
|
- **Entry point = the agent fragment.** Each plugin ships `plugins/<name>/agent.md`, a fixed-size fragment that tells the *agent* (never the core code) what the plugin is, where its data/mirror/join-table lives, its commands, and any always-on rules. Install = add one `@plugins/<name>/agent.md` import line to **`CLAUDE.local.md`** (gitignored, per-machine, auto-loaded after this contract). Never wire plugins into this committed file. Remove = delete that line + `rm -rf plugins/<name>`.
|
|
175
192
|
- **Read** direct (parse the note format). Core publishes **no importable code as a contract**. Plugins **copy** the ~12-line header reader (reversible: promote to a shared lib later only if duplication actually hurts).
|
|
176
|
-
- **Write** single-writer-per-path: a plugin writes only its own folder and **proposes** vault-note changes for `ingest`/you to apply. It never mutates a note. Frontmatter labels are **slug-namespaced** (`
|
|
193
|
+
- **Write** single-writer-per-path: a plugin writes only its own folder and **proposes** vault-note changes for `ingest`/you to apply. It never mutates a note. Frontmatter labels are **slug-namespaced** (`kopeika.synced`), read-your-own-only. Core ignores unknown keys.
|
|
177
194
|
- **`recall` searches `vault/` only, forever.** Sibling dirs + `raw/` are outside the corpus by construction. A plugin surfaces into search only by **proposing one low-frequency summary note** (the escape hatch).
|
|
178
195
|
- **Behavior plugins** are a separate class: ship a fixed-size fragment the **user wires into the agent's system prompt**. The vault never auto-injects. Remove = delete the wire-in. No referee for conflicting fragments, the user owns composition. (The old system imposed. imprnt composes.)
|
|
179
196
|
- **Harness plugins** are the third class: they customize Claude Code itself, never the vault (timemachine's PreToolUse snapshot hook, the statusline, a skill). The same plugin folder doubles as a **native Claude Code plugin** (`.claude-plugin/plugin.json` + `hooks/` + `skills/`, Anthropic's format) and/or carries **`imp-settings.json`** (the settings keys Claude only takes via config, e.g. `statusLine`, with `${PLUGIN_DIR}` as the plugin's absolute dir). `imp` carries enabled ones into every claude session it launches: one `--plugin-dir` per native plugin + one merged `--settings` (gemini has no host for claude harness plugins, so `imp --gemini` skips them with a warning). Stock `claude` stays stock, nothing is ever written to settings files, `plugin rm` undoes everything.
|
|
180
|
-
- **Capability modules** are the one bend in "share nothing": a module may *provide* a capability another *consumes* (
|
|
197
|
+
- **Capability modules** are the one bend in "share nothing": a module may *provide* a capability another *consumes* (a warm-browser module brokering an authed-session token to a watcher that needs a login is the shape). Bounded so it can't re-bloat core: the consumer copies a tiny client (`sessionToken(site) -> string | null`) over a **localhost broker**, never imports the provider or reads its folder. The invariant that replaces "no cross-plugin reads": **removing a provider degrades a consumer gracefully** (`null` = host down, fall back), never breaks it. This is a third contact surface (vault . agent-context . broker), fenced like the sync edge: deterministic, answer-on-request, auditable (token fingerprint, never the token), never resident. The litmus extends: zero core edits to add/remove a *provider* too, because there is no capability registry in core. Credential-holding modules follow the auth finding: human login once, read-only attach, never copy a profile, never automate the password. Full rules: `plugins/README.md`.
|
|
181
198
|
- **Secrets are env vars at the plugin's edge.** A plugin calling an external service reads its key from an env var named in its README and fails loud when missing. Keys never land in the vault, the plugin folder, or anything committed. No central secret store.
|
|
182
199
|
- **Commands only, no forced daemon.** The user schedules sync. Install/remove is manual (README `## Install` / `## Remove` + `rm -rf`), no registry.
|
|
183
|
-
- **Core ↔ plugin contact = exactly four convention-based contact points**, all dumb and uniform: `imprnt check --all` globs `plugins/*/check.js` (each plugin's built artifact), runs each with `node`, reads **exit code only**, forwards stdout verbatim, never parses plugin output. `imprnt ingest --apply` files staged notes the plugins drop in `plugins/*/proposed/`. `imp` turns the enable list into launch flags (`--plugin-dir` per native plugin + one merged `--settings`) for the session it spawns. `imprnt <module> <command>` dispatches to `plugins/<module>/<module>.js` (stdio inherited, exit code passed through, a built-in subcommand always wins, zero per-module logic). All discover by filename/dir convention, never by import, never by naming a specific plugin. Fence: core may provide read-only *aggregation* helpers, never write/orchestration. (Not k8s liveness/readiness. Nothing runs, so the only health question is "is the data sound", which is what `check` answers.)
|
|
200
|
+
- **Core ↔ plugin contact = exactly four convention-based contact points**, all dumb and uniform: `imprnt check --all` globs `plugins/*/check.js` (each plugin's built artifact, or `check.mjs`, with `.js` winning when both exist), runs each with `node`, reads **exit code only**, forwards stdout verbatim, never parses plugin output. `imprnt ingest --apply` files staged notes the plugins drop in `plugins/*/proposed/`. `imp` turns the enable list into launch flags (`--plugin-dir` per native plugin + one merged `--settings`) for the session it spawns. `imprnt <module> <command>` dispatches to `plugins/<module>/<module>.js` or `<module>.mjs` (stdio inherited, exit code passed through, a built-in subcommand always wins, zero per-module logic). All discover by filename/dir convention, never by import, never by naming a specific plugin. Fence: core may provide read-only *aggregation* helpers, never write/orchestration. (Not k8s liveness/readiness. Nothing runs, so the only health question is "is the data sound", which is what `check` answers.)
|
|
184
201
|
|
|
185
202
|
## Out of scope (on purpose)
|
|
186
203
|
No task management. No auto-injected context. No background loop. No self-grading. No MCP/vector/embeddings on the vault. No sensitivity machinery (it's private by being private). **No `out/` zone for deliverables.** A produced artifact (an article, an export) lives in your artifacts dir and a vault note points to it. The vault holds knowledge, not outputs. Every plugin is a self-contained dir you can `rm -rf`. "It belongs" is not a reason to add it. (Plugin-contract out-of-scope list lives in `plugins/README.md`.)
|
package/README.md
CHANGED
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
[](https://github.com/aleksandr-bogdanov/imprnt/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
|
+
[](https://www.npmjs.com/package/imprnt)
|
|
8
9
|
|
|
9
10
|
Your assistant starts every chat blank. You re-explain your projects, your people, and your
|
|
10
11
|
decisions every time, and whatever it learns dies with the session. imprnt fixes that. You talk,
|
|
11
12
|
your assistant files what matters into plain text files on your disk, and weeks later it answers
|
|
12
13
|
from your real history. You can read every note with your own eyes, and no company can switch
|
|
13
|
-
them off.
|
|
14
|
+
them off. Sibling to [Whenful](https://whenful.com): Whenful answers *when* do I do my tasks,
|
|
15
|
+
imprnt holds *what* I know.
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
> in a runtime."
|
|
17
|
-
> - Jensen Huang, NVIDIA (GTC Taipei keynote, June 2026)
|
|
17
|
+
<picture><source media="(prefers-color-scheme: dark)" srcset=".github/assets/recall-dark.svg"><img src=".github/assets/recall-light.svg" alt="imprnt recall: ranked results from a local BM25 search" width="830"></picture>
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
That is the whole read path. Term frequencies and a bit of arithmetic over a folder, with no model
|
|
20
|
+
in the loop.
|
|
21
21
|
|
|
22
22
|
## See it work
|
|
23
23
|
|
|
@@ -41,13 +41,16 @@ plain, cheap, local code.
|
|
|
41
41
|
|
|
42
42
|
## Why plain files win
|
|
43
43
|
|
|
44
|
+
<picture><source media="(prefers-color-scheme: dark)" srcset=".github/assets/lanes-dark.svg"><img src=".github/assets/lanes-light.svg" alt="The write path runs once per source and uses the model. The read path runs thousands of times and uses grep plus BM25." width="830"></picture>
|
|
45
|
+
|
|
44
46
|
A vector database or a hidden memory feature could hold your knowledge too. Plain files make your
|
|
45
47
|
assistant cheap, honest, and yours:
|
|
46
48
|
|
|
47
49
|
- **Reads cost almost nothing.** Your assistant finds a note by running a local ranked search
|
|
48
|
-
(BM25) over a folder, about 100 tokens
|
|
49
|
-
|
|
50
|
-
your
|
|
50
|
+
(BM25) over a folder, about 100 tokens, on any machine that has the files, with no server
|
|
51
|
+
running and no index to rebuild. A vector store pays an embedding pass every time a note
|
|
52
|
+
changes and leaves your knowledge readable only by its own stack. Cheap, dependency-free
|
|
53
|
+
reads mean your assistant can lean on your whole history, every session.
|
|
51
54
|
- **The model works once, where it counts.** Reading a messy transcript and deciding what it means
|
|
52
55
|
is worth the model, and it happens once per source. Searching happens thousands of times, so it
|
|
53
56
|
stays plain local code. Frequency draws the line.
|
|
@@ -59,6 +62,44 @@ assistant cheap, honest, and yours:
|
|
|
59
62
|
- **Yours, in the strongest sense.** Plain text on your disk. It opens in any editor, graphs in
|
|
60
63
|
[Obsidian](https://obsidian.md), and cannot 404, bloat, or hold your context hostage.
|
|
61
64
|
|
|
65
|
+
## The numbers, including the one we lose
|
|
66
|
+
|
|
67
|
+
LoCoMo is the benchmark this category is rated on, 1,540 questions about conversations that ran
|
|
68
|
+
for months. On the same answering model Letta published with, imprnt scores 58.6 against their
|
|
69
|
+
74.0. We lose that comparison by about fifteen points, and the confidence intervals do not
|
|
70
|
+
overlap.
|
|
71
|
+
|
|
72
|
+
Running it turned up something worth more than the score. Take one fixed set of imprnt's answers,
|
|
73
|
+
change only the LLM judge, and the result moves 9 points: 68.4 with Claude grading, 77.6 with
|
|
74
|
+
gpt-4.1. That single swap is wider than the entire published spread in the category (mem0 68.44,
|
|
75
|
+
Letta 74.0, Zep 75.14). A LoCoMo number without a named judge is close to meaningless, ours
|
|
76
|
+
included.
|
|
77
|
+
|
|
78
|
+
On the paper's own scorer, token F1 across all five categories, imprnt gets 64.2%. The human
|
|
79
|
+
baseline on the same questions is 87.9%, and the paper's own GPT-4-turbo baseline is 32.1%.
|
|
80
|
+
|
|
81
|
+
imprnt also ships a retrieval eval over the two example vaults: the right note comes back first
|
|
82
|
+
for 89.7% of questions and appears in the top five for 97.4%, across 39 hand-written questions.
|
|
83
|
+
Small corpus, and we wrote the questions, so run it yourself with `bun run eval`.
|
|
84
|
+
|
|
85
|
+
Every run, interval, and reproduction step is written up in
|
|
86
|
+
[`docs/benchmark-2026-08.md`](docs/benchmark-2026-08.md). The tool-by-tool read lives at
|
|
87
|
+
[imprnt.dev/comparison](https://imprnt.dev/comparison/).
|
|
88
|
+
|
|
89
|
+
Prior art, by name: Letta/MemGPT, mem0, and Zep run memory as a service with their own store and
|
|
90
|
+
their own retrieval. Khoj indexes your files with embeddings. Obsidian gives you the plain-file
|
|
91
|
+
vault without an agent contract on top. PAI wires an assistant into a fixed personal structure.
|
|
92
|
+
imprnt keeps both halves: plain markdown you own, and an agent contract that files into it and reads it back with grep and BM25.
|
|
93
|
+
|
|
94
|
+
## Known limits
|
|
95
|
+
|
|
96
|
+
BM25 is lexical. It ranks on the words a note actually contains, so imprnt bridges vocabulary at
|
|
97
|
+
ingest: the model assigns tags, proposes aliases, and records synonyms once per note, and every
|
|
98
|
+
later search rides on that one pass. Embeddings pay that cost per query instead. The bridge has
|
|
99
|
+
holes. Ask "what do I do for work" when the note says "freelance designer" and nothing connects
|
|
100
|
+
the two, and you can miss. The vault is single-user by design. That is the trade for a read path
|
|
101
|
+
that is free, local, and identical every time you run it.
|
|
102
|
+
|
|
62
103
|
## The robot does the work. You stay the boss.
|
|
63
104
|
|
|
64
105
|
The current crop of AI tools wants the wheel. Resident agents (OpenClaw and friends) read your
|
|
@@ -123,14 +164,10 @@ asking ("add the anti-slop plugin"), each a separate `imprnt-plugin-*` package:
|
|
|
123
164
|
|
|
124
165
|
| Package | What it gives your assistant |
|
|
125
166
|
|---------|------------------------------|
|
|
126
|
-
| `imprnt-plugin-character` | A voice and standards to write in. "Scribe" is the default you copy and personalize. |
|
|
127
167
|
| `imprnt-plugin-anti-slop` | Rules that keep its prose from reading like AI. |
|
|
128
|
-
| `imprnt-plugin-
|
|
129
|
-
| `imprnt-plugin-kleinanzeigen` | A watcher for your Kleinanzeigen inbox: regex triage of buyer messages, drafts, a phone digest. You press send. |
|
|
130
|
-
| `imprnt-plugin-session-host` | A warm browser holding your logged-in sessions, providing the authed-session capability. You enroll each site once. Not yet on npm: install from a repo checkout with `imprnt plugin add session-host --from <dir>`. |
|
|
131
|
-
| `imprnt-plugin-timemachine` | Snapshots your work before each change so you can recover what the agent breaks. |
|
|
168
|
+
| `imprnt-plugin-character` | A voice and standards to write in. "Scribe" is the default you copy and personalize. |
|
|
132
169
|
| `imprnt-plugin-statusline` | A customizable status line: model, branch, context, cost, rate limits, clock. |
|
|
133
|
-
| `imprnt-plugin-
|
|
170
|
+
| `imprnt-plugin-timemachine` | Snapshots your work before each change so you can recover what the agent breaks. |
|
|
134
171
|
|
|
135
172
|
Adding one copies it into your project and wires it into `CLAUDE.local.md`, the per-machine file
|
|
136
173
|
your assistant loads each session. A fresh setup loads zero plugins until you add them. The full
|
|
@@ -180,3 +217,7 @@ only, never needed by people who use it through their assistant). Clone, `bun in
|
|
|
180
217
|
## License
|
|
181
218
|
|
|
182
219
|
MIT (c) 2026 Aleksandr Bogdanov
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
<img src=".github/mark.svg" height="15" alt=""> built by [bogdanov.wtf](https://bogdanov.wtf)
|