imprnt 0.1.4-edge.89 → 0.1.4-edge.99
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 +6 -2
- package/README.md +4 -3
- package/dist/cli.js +666 -234
- package/dist/imp.js +666 -234
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -109,6 +109,10 @@ household
|
|
|
109
109
|
|
|
110
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
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.
|
|
115
|
+
|
|
112
116
|
## Frontmatter contract
|
|
113
117
|
|
|
114
118
|
`type` is **singular** and records *what the object is*, independent of which folder it browses in: `person`, `org`, `holding`, `project`, `principle`, `note`, `mistake`, `event`. Entities and forms file into a folder of the same name (`person` → `people/`). `principle` and `note` file by **domain** (a belief → `identity/`, a tax fact → `finances/`). The folder is the drawer. `type` is the truth.
|
|
@@ -156,7 +160,7 @@ The discipline that keeps the list lean moved **off the write path** to a non-bl
|
|
|
156
160
|
|
|
157
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.
|
|
158
162
|
|
|
159
|
-
- **`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.)
|
|
160
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.
|
|
161
165
|
|
|
162
166
|
## Updating & contradictions
|
|
@@ -186,7 +190,7 @@ Core is the vault + `ingest → recall → check`. Everything else (a task-mirro
|
|
|
186
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.
|
|
187
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>`.
|
|
188
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).
|
|
189
|
-
- **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.
|
|
190
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).
|
|
191
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.)
|
|
192
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.
|
package/README.md
CHANGED
|
@@ -47,9 +47,10 @@ A vector database or a hidden memory feature could hold your knowledge too. Plai
|
|
|
47
47
|
assistant cheap, honest, and yours:
|
|
48
48
|
|
|
49
49
|
- **Reads cost almost nothing.** Your assistant finds a note by running a local ranked search
|
|
50
|
-
(BM25) over a folder, about 100 tokens
|
|
51
|
-
|
|
52
|
-
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.
|
|
53
54
|
- **The model works once, where it counts.** Reading a messy transcript and deciding what it means
|
|
54
55
|
is worth the model, and it happens once per source. Searching happens thousands of times, so it
|
|
55
56
|
stays plain local code. Frequency draws the line.
|