imprnt 0.1.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.
- package/CLAUDE.md +173 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/cli.js +2339 -0
- package/dist/imp.js +2342 -0
- package/package.json +46 -0
- package/templates/_tags.md +23 -0
- package/templates/hot.md +28 -0
- package/templates/index.md +33 -0
- package/templates/log.md +8 -0
- package/templates/pointer.md +12 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# imprnt - vault contract
|
|
2
|
+
|
|
3
|
+
> The schema for the vault. When an agent works inside an imprnt vault, this is the standing context it needs. Keep it small.
|
|
4
|
+
> Headline: **the LLM builds the tools, the tools do the work.**
|
|
5
|
+
|
|
6
|
+
<!-- Plugins are NOT wired here - this committed contract ships clean. Enable them per-machine in CLAUDE.local.md (gitignored), which Claude Code auto-loads after this file. See ## Plugins and plugins/README.md. -->
|
|
7
|
+
|
|
8
|
+
## Entry point: you talk, the assistant runs the tools
|
|
9
|
+
|
|
10
|
+
You never run the CLI by hand. You speak in plain language ("ingest this", "load my context on taxes") and the agent runs the CLI underneath. Sources are anything: a meeting transcript, a pasted doc, a prose dump, or a single fact to file.
|
|
11
|
+
|
|
12
|
+
## Privacy: it's a private vault, full stop
|
|
13
|
+
|
|
14
|
+
The whole vault is yours, local, owner-only (`chmod 700`), never shared. There is **no** sensitivity field, no redaction pass, no secret-fencing. It holds everything, including medical, financial, and personal data, because that's the point. The only rule: it never goes near a public repo (`.gitignore` guards it if the dir is ever git-init'd). If you ever publish a subset (an article), that's an **export-time** filter you run consciously then, not a tax paid on every note at ingest.
|
|
15
|
+
|
|
16
|
+
## The one rule: deterministic-first = ration the LLM by WHERE it runs
|
|
17
|
+
|
|
18
|
+
Deterministic-first does **not** mean "avoid the LLM." It means **invest it where it pays and keep it out of the hot path**. The line is drawn by *how often a step runs*:
|
|
19
|
+
- **WRITE path (runs once per item)** is where the LLM earns its keep: understand unstructured prose, decide the type, write the summary, pull decisions/actions with judgment, assign tags + `kind`, propose `aliases`, wire links, and optionally clean/rephrase a messy dump into a usable note. Building the content map *well* is the important one-off work. Spend the tokens here.
|
|
20
|
+
- **IMPORT (one-time bulk)** is exactly that one-off WRITE work at scale: one pass of LLM understanding/cleaning over the source so every later read is cheap. That's the purpose, not an excess.
|
|
21
|
+
- **READ path (runs thousands of times)** must be **cheap, deterministic, local**: grep + **BM25 ranking (core)**, no LLM in the loop. The LLM only shapes the question into keywords at the front and reads the top-N hits at the end. It never re-reads the whole vault (the unconscious trap), never re-ranks per query.
|
|
22
|
+
|
|
23
|
+
"The dumbest thing that works" is measured **against the LLM**: BM25 is pure local arithmetic (term frequencies + idf), zero LLM, zero deps, so it is the **default ranker**, not an opt-in. The discipline is that you don't pay the LLM on every query, and you don't make code guess at meaning on the write side.
|
|
24
|
+
|
|
25
|
+
The same line, step by step (note *who* and *why*, frequency is the axis):
|
|
26
|
+
|
|
27
|
+
| Step | Who | Why |
|
|
28
|
+
|------|-----|-----|
|
|
29
|
+
| Snapshot source → `raw/<source>/` (one folder per source), hash, manifest (incremental) | **code** | mechanical, must be exact, free |
|
|
30
|
+
| Parse structure from *structured* input (speakers, dates, frontmatter, headings) | **code** | reliable when the shape is regular |
|
|
31
|
+
| Read *unstructured* prose to find that structure | **LLM** | there's nothing to parse, it takes reading |
|
|
32
|
+
| Decide the note's **type** + folder + write `summary` + extract decisions/actions with judgment + assign tags + set `kind` | **LLM** | irreducibly semantic, the conscious ~20% |
|
|
33
|
+
| **File** the note into its folder | **code** | once the type + folder are decided, writing is mechanical |
|
|
34
|
+
| Generate `index.md` from each note's `summary` + tags + links | **code** | a map-of-content is a deterministic read over frontmatter, `imprnt check` rebuilds it |
|
|
35
|
+
| Append the one `log.md` chronological line | **LLM** (the conscious step) | the title + one-line gist is a fresh judgment the agent that made the note writes |
|
|
36
|
+
| Entity resolution: exact + `aliases[]` grep, MERGE on hit | **code** | the common case is a lookup, not a judgment |
|
|
37
|
+
| Adjudicate a *genuinely ambiguous* identity / propose new `aliases` | **LLM** | only the uncertain ones, not every resolution |
|
|
38
|
+
| Corpus scan for retrieval (BM25 ranking over title/tags/body) | **code** | fast, free, transparent, runs over 1000s of notes |
|
|
39
|
+
| Turn a natural-language question into keywords/tags, and read the top hits to answer | **LLM** | it's the interface, it already has the query and the results in hand |
|
|
40
|
+
|
|
41
|
+
"Not sure → hand it to the LLM" is a **first-class allowed move on the WRITE side**. The discipline is that you don't make code guess at meaning, and you don't put the LLM in the read loop.
|
|
42
|
+
|
|
43
|
+
## Retrieval: BM25 ranking (deterministic, local), LLM at the two ends only
|
|
44
|
+
|
|
45
|
+
1. The LLM (already talking to you) shapes your question into keywords + candidate tags. *(conscious, cheap, front)*
|
|
46
|
+
2. `recall` runs **BM25** over each note's title/tags/body and returns a **tight ranked candidate set** (top ~15). *(code, free)*
|
|
47
|
+
3. The LLM reads the top hits and answers. *(conscious, back)*
|
|
48
|
+
|
|
49
|
+
BM25 is the **core** ranker: standard term-frequency × inverse-document-frequency with field boosts (a term in the title/aliases outweighs the same term in tags, which outweighs body), pure local arithmetic, no LLM, no deps. Its idf already floats a rare matched term above common ones, and a single matched term still scores, so it returns a tight, well-separated set, not the whole vault. Explicitly: **no per-query LLM re-ranking in core, no embeddings, no vectors, no MCP over the vault.** The LLM shapes the query and reads the top-N. It is never in the middle. `recall` greps `vault/` only. `raw/` is never searched.
|
|
50
|
+
|
|
51
|
+
## Layout - entities · domains · forms
|
|
52
|
+
|
|
53
|
+
Three folder groups, each a genuinely different reason to exist. **Folders are browse drawers, not the search axis.** `recall` is grep + BM25 and ignores folders entirely, so layout is a pure human-browsing choice. Humans browse by life-area, so **domains** carry most content. **Entities** get cross-cutting homes because they're referenced from everywhere. **Forms** are distinct by how you use them.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
raw/ immutable source snapshots, ONE FOLDER PER SOURCE - never edited, never searched
|
|
57
|
+
<legacy>/<mirror> a migrated tree, mirrored as-is
|
|
58
|
+
transcripts/<dated> ad-hoc dumps, dated + slugged
|
|
59
|
+
<bundle>/ evidence bundles (e.g. tax CSVs / PDFs), untouched
|
|
60
|
+
vault/
|
|
61
|
+
index.md generated map of content - code builds it from each note's `summary`
|
|
62
|
+
hot.md ~500-tok primer + needs-review + (optional) review-due list
|
|
63
|
+
log.md append-only chronological stream
|
|
64
|
+
_tags.md auto-growing tag vocabulary + bidirectional synonym map (check syncs it)
|
|
65
|
+
|
|
66
|
+
# entities - cross-cutting, one canonical home, linked from every domain
|
|
67
|
+
people/<slug>.md a human
|
|
68
|
+
orgs/<slug>.md an institution - employer, insurer, authority, bank, vendor
|
|
69
|
+
holdings/<slug>.md an owned thing with TRACKED CHANGING STATE - a policy, a med+dose, an account+balance, a paid subscription+renewal
|
|
70
|
+
|
|
71
|
+
# domains - life-areas, topical/reference content lives here (USER-DEFINED, not fixed)
|
|
72
|
+
identity/<slug>.md the spine - mission, goals, beliefs, models, frames, strategies, held positions, who you are
|
|
73
|
+
health/ · finances/ · work/ · life/ · projects/
|
|
74
|
+
|
|
75
|
+
# forms - distinct by how you use them, not by topic
|
|
76
|
+
events/<YYYY-MM-DD>-<slug>.md a dated occurrence worth its own note
|
|
77
|
+
mistakes/<slug>.md a lesson - believed / found_false / true_now
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Filing decision (the LLM's conscious call, per note):**
|
|
81
|
+
1. Is it a **person / org / holding** (a tracked-state owned thing)? → entity folder, regardless of topic.
|
|
82
|
+
2. Is it a **dated occurrence** → `events/`. A **lesson learned** → `mistakes/`. A **bounded effort with a status** → `projects/`.
|
|
83
|
+
3. Otherwise it's **topical content** → file by **domain (life-area)**: a held position / identity-spine note → `identity/`, everything else by its life-area (`health/`, `finances/`, `work/`, `life/`).
|
|
84
|
+
|
|
85
|
+
**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.
|
|
86
|
+
|
|
87
|
+
**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. `type:` in frontmatter (below) records *what each note is* even when it sits in a domain folder, so nothing is lost by filing topically.
|
|
88
|
+
|
|
89
|
+
## Frontmatter contract
|
|
90
|
+
|
|
91
|
+
`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.
|
|
92
|
+
|
|
93
|
+
Every note carries `type`, `tags`, and **`summary`**, a single line the LLM writes once at ingest. `summary` is the field the READ side leans on deterministically: `imprnt check` builds `index.md` purely from each note's `summary` + tags + links, no LLM (it falls back to the H1 title if `summary` is absent). **The H1 (`# Title`) is the title.** No `title:` key, no universal `created:`. Use `updated:` on notes that change. Events carry their own time fields.
|
|
94
|
+
|
|
95
|
+
**Self-describing placement + links.** A note in a **domain folder** (`identity/health/finances/work/life`) carries **`domain: <that folder>`** in frontmatter, so the note knows its life-area without parsing the path. `imprnt check` fails if folder and field disagree (the redundancy `type:` already has with entity folders, made a checked invariant). Entity/form notes need **no** `domain` (their `type` already mirrors their folder, and an entity is cross-cutting, with no single domain). **`projects/` is self-describing by its `type: project`** (the folder mirrors the type, like `events/` and `mistakes/`), so a project note carries no `domain:` field and is exempt from the domain-match check. **`source:` is a wikilink** to the immutable snapshot, written `source: "[[raw/...]]"` (no `.md`), clickable in Obsidian when the vault is opened at the repo root so `raw/` resolves. `recall` never searches `raw/` and `check` never treats a `raw/` link as an orphan. **Entity-valued fields are wikilinks too** (`owner: "[[people/sam]]"`, `participants: ["[[people/...]]"]`, `policyholder`, `beneficiary`), so ownership/authorship edges are real graph links, not bare strings. The principal is a first-class entity: your own `people/` note.
|
|
96
|
+
|
|
97
|
+
- **people**: `team · role` *(opt: `owns[] · aliases[] · status`)*
|
|
98
|
+
- **orgs**: `kind(employer|insurer|authority|bank|vendor)` *(opt: `aliases[]`)*
|
|
99
|
+
- **holdings**: `kind(policy|med|account|subscription|asset) · owner · status` *(opt: `aliases[] · review_by`)*. `status` carries the tracked state (a balance, a dose, a renewal date, a coverage tier). The `kind` set is deliberately the tracked-state objects. A free tool/dotfile is **not** a holding (no state), it's a `note` in its domain.
|
|
100
|
+
- **projects**: `status · owner · updated` *(opt: `artifact · target · people[] · holdings[]`)*. `status` free string (`active|paused|done`). `artifact` = the app/CLI/output as a string, not a separate note. `target` = a plain date intent marker, NOT a scheduler (no loop reads it).
|
|
101
|
+
- **principles**: `kind(belief|model|frame|strategy|mission|narrative|challenge|problem|wisdom)` *(opt: `aliases[]`)*. The identity spine, lives in `identity/`.
|
|
102
|
+
- **notes**: `kind(reference|howto|preference|rating|strategy|collection)` *(opt: `aliases[]`)*. Reference knowledge, lives in its **domain** folder.
|
|
103
|
+
- **mistakes**: `project`. The believed / found-false / true-now lesson lives in the body as prose (mark `{inferred}` only where the conclusion is the LLM's), not frontmatter.
|
|
104
|
+
- **events**: `date · participants[] · project · source · source_hash · status · ingested`. Emitted by `ingest`. `status` goes `draft-deterministic` → `enriched`.
|
|
105
|
+
|
|
106
|
+
Three orthogonal axes, each doing real work: **type** (frontmatter) = what object · **kind** (field) = what form · **tags** = what topic. The **folder** is a fourth, human-only axis (where it browses), never the search axis. The `domain:` field is not a fifth axis: it only mirrors the folder so placement stays checkable. Tags, not folders, carry topic for search.
|
|
107
|
+
|
|
108
|
+
Optional on any note: `review_by: <date>` for perishable facts. Surfaced **only on demand** via `hot.md`. Never a background loop, never a separate command.
|
|
109
|
+
|
|
110
|
+
## Tags: an auto-growing vocabulary (not a gated list)
|
|
111
|
+
|
|
112
|
+
`vault/_tags.md` holds the tag values + a bidirectional synonym map. The vocabulary **grows automatically**. There is **no human-approval gate**. At ingest the LLM applies the **best-fitting tag**. If none fits, it **coins a new one** (kebab-case, one concept) and uses it. `imprnt check` then **syncs every tag the notes carry into `_tags.md`** deterministically (a tag is just a string the note already holds, no LLM, no approval). So a new domain (wardrobe, shoes, a client) never hits a wall: tag the note, run `check`, the vocabulary catches up.
|
|
113
|
+
|
|
114
|
+
The discipline that keeps the list lean moved **off the write path** to a non-blocking audit: `check` flags **near-duplicate tags** (prefix or edit-distance-1, e.g. `finance ~ finances`, `shoe ~ shoes`) so they can be merged into a **synonym** consciously. `check` never auto-merges. Picking the canonical is judgment, not arithmetic, and that's the one tag step that stays an LLM/human call. Before coining, the LLM should still scan the existing list + synonyms and reuse a fit. One concept = one tag remains the goal, now enforced by the dedup audit rather than a pre-approval. The synonym map is a deterministic assist applied the same at write and at search. Keep it lean, avoid over-broad canonicals that collapse specific terms.
|
|
115
|
+
|
|
116
|
+
## The ingest pass
|
|
117
|
+
|
|
118
|
+
1. **snapshot + parse (code)** - copy the source into `raw/<source>/` (one folder per source, immutable), hash, write any deterministic skeleton, update the manifest. Incremental: unchanged sources skip. A multi-topic source stays ONE `raw/` entry even when it fans out into many vault notes. Provenance is keyed to origin, not topic.
|
|
119
|
+
2. **classify + enrich (LLM, the conscious step)** - for each object in the source: pick `type`, choose the folder (entity / domain / form per the filing decision), write the one-line `summary`, pull decisions/actions/questions with judgment, assign tags + `kind`, propose `aliases`, ensure ≥1 link to another entity.
|
|
120
|
+
3. **resolve + file (code)** - grep names + `aliases` across `people/ orgs/ holdings/`, MERGE on hit (never duplicate: a rename moves the old name to `aliases` and keeps the slug), write the note. Append the one `log.md` line (the LLM's gist).
|
|
121
|
+
4. **regenerate + soft-fail (code)** - `imprnt check` rebuilds `index.md` from every `summary`, and flags any note that links nothing / resolves no entity / has an orphan `[[link]]` into `needs-review`, surfaced atop `hot.md`. Never block, never silently drop.
|
|
122
|
+
|
|
123
|
+
## Fidelity: the data IS the knowledge (the cardinal ingest rule)
|
|
124
|
+
|
|
125
|
+
**The note must carry the source's structured payload: tables, lists, entries, IDs, numbers, dates, prices, doses, contact details, verbatim legal/clause text.** `recall` searches `vault/` ONLY. Anything left in `raw/` is **invisible**. So the summary is *in addition to* the data, never *instead of* it.
|
|
126
|
+
|
|
127
|
+
- **NEVER summarize a catalog to prose and point at the snapshot.** "The live table lives in the source" is the failure that silently deletes knowledge. The rows ARE the note. A rated list, a price table, a backlog, an account/cadastral/contract/insurance number, a verbatim policy clause: copy it INTO the note, in full, never rounded or paraphrased.
|
|
128
|
+
- **enrich = ADD (summary, tags, links, `kind`), never REMOVE.** Reformatting prose to prose is fine. Dropping a table, an enumeration, or a specific figure is data loss. Preserve tables AS tables, enumerations AS enumerations.
|
|
129
|
+
- **Anti-slop governs PROSE, not DATA.** The "no bullet-flood / paragraphs over bullets" rule is about narrative writing. A rated catalog or a record table is data. Keep it structured, it is exempt.
|
|
130
|
+
- **The lookup test (apply before declaring a note done):** could you answer a specific question from the VAULT note alone, for example "which plan tier is this account on", "what is the policy number", "what are the property's registration details", "what is the payout schedule"? If the answer is only in `raw/`, you dropped the knowledge. Re-derive.
|
|
131
|
+
|
|
132
|
+
## The two robot commands (explicit, never a daemon)
|
|
133
|
+
|
|
134
|
+
imprnt keeps exactly two "robot" helpers stolen from the system it replaces, and both are **commands you run**, never background hooks. The auto-magic is what made that system bill rent.
|
|
135
|
+
|
|
136
|
+
- **`imprnt check`** - integrity + regenerate. Flags orphan `[[links]]`, notes that resolve no entity, **untagged notes** (empty `tags:`, the topic axis is blank), unclassified snapshots, and near-duplicate tags. 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.)
|
|
137
|
+
- **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.
|
|
138
|
+
|
|
139
|
+
## Updating & contradictions
|
|
140
|
+
|
|
141
|
+
Correct the ONE entity note. Everything links by ID so the fix propagates. Old name → `aliases`. A contradiction stamps the stale line `> superseded by [[...]]`. Marked, never silently overwritten. Because `raw/` is immutable, any claim is traceable to its snapshot, and a schema change is just a `reingest` over `raw/`.
|
|
142
|
+
|
|
143
|
+
## Migrating existing structured knowledge
|
|
144
|
+
|
|
145
|
+
A migration is the one-time bulk WRITE: snapshot every chosen source into `raw/<source>/` (immutable, complete), then the LLM fans each source out into atomic, domain-filed, linked notes. Two rules keep it honest:
|
|
146
|
+
|
|
147
|
+
- **Re-derive from the ORIGINAL source, not a prior vault.** When you rebuild the vault, go back to the original snapshots in `raw/`. Don't reshuffle a previous vault's notes (that migrates yesterday's mistakes forward). A schema change is just a `reingest` over `raw/`.
|
|
148
|
+
- **But preserve genuine prior enrichment.** When a *source* is already an atomic, enriched note (a prior system's knowledge notes), map its fields onto this contract and keep its typed links. Don't re-derive it from scratch (that pays the LLM to downgrade good work). Re-deriving from scratch is for *unstructured* sources (the dense prose blobs).
|
|
149
|
+
|
|
150
|
+
**A dense multi-topic source splits into atomic linked notes: one snapshot, many notes.** Real migration sources are dense blobs covering many objects at once (a health-overview file = the med stack + a health profile + an insurance-critical prescription decision + the doctor-as-a-person). Don't file the whole blob as one note. That loses the entity graph (the doctor never becomes a `people/`, the decision never becomes a linkable note). Instead: split it into one atomic note per object across the right folders (`people/` for the doctor, `holdings/kind:med` for a tracked drug+dose, `health/`/`identity/` for the profile/decision), link them, and keep the single `raw/` snapshot as the shared provenance for all of them. `ingest` writes one snapshot. The LLM fans it out into the right number of notes.
|
|
151
|
+
|
|
152
|
+
**Goals route in with no extra folder.** A goal that's a bounded effort with a status (`lock in disability insurance`, `stack cash through next spring`) → `projects/` with a `status` (and an optional `target:` date string). A north-star *intent* that isn't yet a bounded effort (a mission/narrative) → `identity/` with `type:principle kind:mission`. No `goals/` folder.
|
|
153
|
+
|
|
154
|
+
## Conventions
|
|
155
|
+
- `[[people/boris-carter]]` wikilinks. Orphans surface in `needs-review`. Links use the entity folders (`people/ orgs/ holdings/`) as the stable namespace, but any note is linkable by its `folder/slug`.
|
|
156
|
+
- Provenance: mark only the **exceptions**: `{inferred}` (the LLM concluded it, not in the source) · `{ambiguous}` (uncertain, needs review). **Unmarked = straight from the source.** The common case carries no marker, because tagging every line is noise that drowns the signal. Plain brace tags, NOT `^[...]` (a leading `^[` is caret-notation for the ESC control char and corrupts on copy/paste).
|
|
157
|
+
- Slugs kebab-case ≤60. Filename = permanent ID. Links use the ID, never the display name.
|
|
158
|
+
|
|
159
|
+
## Plugins (opt-in add-ons) - the contract
|
|
160
|
+
|
|
161
|
+
Core is the vault + `ingest → recall → check`. Everything else (a task-mirror sync, a documents librarian, an anti-slop behavior ruleset, graph lint, the guard) is a **pluggable plugin**. Full contract + the worked instances: `plugins/README.md`. The standing rules:
|
|
162
|
+
|
|
163
|
+
- **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.
|
|
164
|
+
- **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>`.
|
|
165
|
+
- **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).
|
|
166
|
+
- **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** (`whenful.synced`), read-your-own-only. Core ignores unknown keys.
|
|
167
|
+
- **`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).
|
|
168
|
+
- **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.)
|
|
169
|
+
- **Commands only, no forced daemon.** The user schedules sync. Install/remove is manual (README `## Install` / `## Remove` + `rm -rf`), no registry.
|
|
170
|
+
- **Core ↔ plugin contact = exactly two convention-based aggregators**, both 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/`. Both 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.)
|
|
171
|
+
|
|
172
|
+
## Out of scope (on purpose)
|
|
173
|
+
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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksandr Bogdanov
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# imprnt
|
|
2
|
+
|
|
3
|
+
> The long-term memory your AI assistant is missing. Plain markdown, on your disk, yours forever.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/aleksandr-bogdanov/imprnt/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
Your assistant starts every chat blank. You re-explain your projects, your people, and your
|
|
10
|
+
decisions every time, and whatever it learns dies with the session. imprnt fixes that. You talk,
|
|
11
|
+
your assistant files what matters into plain text files on your disk, and weeks later it answers
|
|
12
|
+
from your real history. You can read every note with your own eyes, and no company can switch
|
|
13
|
+
them off.
|
|
14
|
+
|
|
15
|
+
> "You can think of the model as the brain, the harness as the body, and the tools it uses working
|
|
16
|
+
> in a runtime."
|
|
17
|
+
> - Jensen Huang, NVIDIA (GTC Taipei keynote, June 2026)
|
|
18
|
+
|
|
19
|
+
imprnt is the tool layer Huang is pointing at, holding the part that lasts. Sibling to
|
|
20
|
+
[Whenful](https://whenful.com): Whenful answers *when* do I do my tasks, imprnt holds *what* I know.
|
|
21
|
+
|
|
22
|
+
## See it work
|
|
23
|
+
|
|
24
|
+
You do not learn commands. You talk, and your assistant keeps your knowledge for you.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
You: Here is my 1:1 with Boris from this morning. [paste or drop the transcript]
|
|
28
|
+
Claude: Filed it. Created people/boris-carter, updated projects/access-platform with the new
|
|
29
|
+
cutover date, and logged the meeting under events/.
|
|
30
|
+
|
|
31
|
+
(weeks later)
|
|
32
|
+
|
|
33
|
+
You: What did we decide about the access-platform cutover?
|
|
34
|
+
Claude: From your notes: the cutover moved to July 15, gated on the two-week parallel-run numbers.
|
|
35
|
+
Boris owns it. The earlier June date is marked superseded.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Behind those two replies, Claude ran the imprnt engine: it filed the transcript into structured,
|
|
39
|
+
linked notes, then ranked your vault to answer the question. You saw a conversation. The work was
|
|
40
|
+
plain, cheap, local code.
|
|
41
|
+
|
|
42
|
+
## Why plain files win
|
|
43
|
+
|
|
44
|
+
A vector database or a hidden memory feature could hold your knowledge too. Plain files make your
|
|
45
|
+
assistant cheap, honest, and yours:
|
|
46
|
+
|
|
47
|
+
- **Reads cost almost nothing.** Your assistant finds a note by running a local ranked search
|
|
48
|
+
(BM25) over a folder, about 100 tokens. The same lookup through a vector database or an MCP
|
|
49
|
+
server costs orders of magnitude more and goes stale every time a note changes. Cheap reads mean
|
|
50
|
+
your assistant can lean on your whole history, every session.
|
|
51
|
+
- **The model works once, where it counts.** Reading a messy transcript and deciding what it means
|
|
52
|
+
is worth the model, and it happens once per source. Searching happens thousands of times, so it
|
|
53
|
+
stays plain local code. Frequency draws the line.
|
|
54
|
+
- **The data survives in full.** Tables stay tables. Numbers, dates, IDs, and exact wording are
|
|
55
|
+
kept verbatim, with a summary added on top, so your assistant answers from facts, not a
|
|
56
|
+
paraphrase.
|
|
57
|
+
- **Corrections cost one edit.** Notes link by permanent ID. Fix a person's note once and every
|
|
58
|
+
meeting, project, and decision that mentions them is right.
|
|
59
|
+
- **Yours, in the strongest sense.** Plain text on your disk. It opens in any editor, graphs in
|
|
60
|
+
[Obsidian](https://obsidian.md), and cannot 404, bloat, or hold your context hostage.
|
|
61
|
+
|
|
62
|
+
## Start in two minutes
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm i -g imprnt # install the engine your assistant drives
|
|
66
|
+
imprnt init # scaffold your vault, drop CLAUDE.md (the contract your assistant reads)
|
|
67
|
+
imp # open your assistant and talk
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Temporarily off npm (June 2026).** The package is unpublished while the installer gets a final
|
|
71
|
+
> polish, so `npm i -g imprnt` 404s right now. Until it returns, install from source (needs
|
|
72
|
+
> [Bun](https://bun.sh) to build):
|
|
73
|
+
>
|
|
74
|
+
> ```sh
|
|
75
|
+
> git clone https://github.com/aleksandr-bogdanov/imprnt
|
|
76
|
+
> cd imprnt && bun install && bun run build
|
|
77
|
+
> (cd packages/imprnt && bun run shipdocs) # stage the CLAUDE.md contract the install ships
|
|
78
|
+
> npm i -g ./packages/imprnt # the same imprnt + imp commands, from your clone
|
|
79
|
+
> ```
|
|
80
|
+
>
|
|
81
|
+
> The `shipdocs` step is what npm's publish runs for you. From source you run it once by hand so
|
|
82
|
+
> `imprnt init` has the contract to drop. The global install symlinks to your clone, so keep the
|
|
83
|
+
> clone in place.
|
|
84
|
+
|
|
85
|
+
`imprnt init` scaffolds the vault, writes the `CLAUDE.md` contract that teaches your assistant
|
|
86
|
+
how it works, and registers the folder so `imp` finds it from anywhere. Runs on
|
|
87
|
+
[Node](https://nodejs.org) 18 or newer, driving [Claude Code](https://claude.com/claude-code) as
|
|
88
|
+
the assistant.
|
|
89
|
+
|
|
90
|
+
`imp` is the front door, and you can type it instead of `claude` in any directory:
|
|
91
|
+
|
|
92
|
+
- **`imp`** opens Claude where you stand. Your enabled plugins ride along, and your vault is
|
|
93
|
+
within reach, so a coding session can answer "who owns the downstream consumers?" from your
|
|
94
|
+
own notes. Typing `imp` instead of `claude` is the whole opt-in: stock `claude` stays stock,
|
|
95
|
+
and nothing is ever injected into sessions you didn't ask it into.
|
|
96
|
+
- **`imp lair`** opens Claude inside the vault project, your assistant's home. The full contract
|
|
97
|
+
loads there, and your personal conversations accumulate in one resumable place.
|
|
98
|
+
|
|
99
|
+
Want the vault folder somewhere other than `./vault`? Set `export IMPRNT_VAULT=~/notes/vault` in
|
|
100
|
+
your shell profile and both the engine and imp follow it.
|
|
101
|
+
|
|
102
|
+
The first thing to ask your assistant: "file a person note for me." You appear in nearly every
|
|
103
|
+
transcript, so a self-note lets it link you to everything from then on.
|
|
104
|
+
|
|
105
|
+
## What your assistant runs
|
|
106
|
+
|
|
107
|
+
These are the engine's jobs. You trigger them by asking, in plain language. Claude picks the
|
|
108
|
+
right one.
|
|
109
|
+
|
|
110
|
+
| You say something like | Claude runs | What happens |
|
|
111
|
+
|------------------------|-------------|--------------|
|
|
112
|
+
| "Save this transcript / note / doc." | ingest | Snapshots the source untouched, files structured notes into your vault. |
|
|
113
|
+
| "What do I know about X?" / "What did we decide on Y?" | recall | Ranks your notes locally (BM25) and answers from the top hits. |
|
|
114
|
+
| "Tidy up / what needs my attention?" | check, hot | Rebuilds the index, syncs tags, surfaces anything that needs review. |
|
|
115
|
+
|
|
116
|
+
The engine itself uses no AI for any of this. The model sits only at the two ends: turning your
|
|
117
|
+
ask into a search at the front, reading the results at the back. Everything in between is free
|
|
118
|
+
local code.
|
|
119
|
+
|
|
120
|
+
## Plugins: new behavior with one ask
|
|
121
|
+
|
|
122
|
+
Core is your vault plus the file, recall, and tidy jobs. Everything else is a behavior you add by
|
|
123
|
+
asking ("add the anti-slop plugin"), each a separate `imprnt-plugin-*` package:
|
|
124
|
+
|
|
125
|
+
| Package | What it gives your assistant |
|
|
126
|
+
|---------|------------------------------|
|
|
127
|
+
| `imprnt-plugin-character` | A voice and standards to write in. "Scribe" is the default you copy and personalize. |
|
|
128
|
+
| `imprnt-plugin-anti-slop` | Rules that keep its prose from reading like AI. |
|
|
129
|
+
| `imprnt-plugin-whenful` | A local mirror of your [Whenful](https://whenful.com) tasks, shown inline at read. |
|
|
130
|
+
| `imprnt-plugin-guard` | A deterministic blocklist for dangerous shell commands. |
|
|
131
|
+
|
|
132
|
+
Adding one copies it into your project and wires it into `CLAUDE.local.md`, the per-machine file
|
|
133
|
+
your assistant loads each session. A fresh setup loads zero plugins until you add them. The full
|
|
134
|
+
contract is in [`plugins/README.md`](plugins/README.md).
|
|
135
|
+
|
|
136
|
+
## Vault vs assistant memory
|
|
137
|
+
|
|
138
|
+
Your assistant ships its own memory feature, a private scratchpad it writes for itself. That is a
|
|
139
|
+
different thing from the vault, and treating them as one defeats the point.
|
|
140
|
+
|
|
141
|
+
| | imprnt **vault** | assistant **memory** |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| Holds | your knowledge: finances, health, people, projects | the agent's working notes about helping you |
|
|
144
|
+
| Lives | plain files on your disk, the version of record | inside the assistant, opaque to you |
|
|
145
|
+
| You can | read it, edit it, trace each note to its source | barely see it |
|
|
146
|
+
|
|
147
|
+
Anything durable and about your life goes in the vault, where it is yours and you can read it.
|
|
148
|
+
Keep the assistant's private memory thin.
|
|
149
|
+
|
|
150
|
+
## Examples
|
|
151
|
+
|
|
152
|
+
Two worked vaults live in [`examples/`](examples/), each showing the same flow of talking to an
|
|
153
|
+
assistant that files and recalls for you:
|
|
154
|
+
|
|
155
|
+
- **[`digital-assistant/`](examples/digital-assistant/)** is a personal vault for one person:
|
|
156
|
+
identity, health, finances, people, daily life.
|
|
157
|
+
- **[`organization/`](examples/organization/)** is a small company's vault: employees, a customer,
|
|
158
|
+
a project with a ranked backlog, a decision, and a postmortem.
|
|
159
|
+
|
|
160
|
+
## Docs
|
|
161
|
+
|
|
162
|
+
- [`docs/architecture.md`](docs/architecture.md), how the whole thing works, in plain English. Start here.
|
|
163
|
+
- [`docs/design-decisions.md`](docs/design-decisions.md), the durable calls and why they were made.
|
|
164
|
+
- [`docs/releasing.md`](docs/releasing.md), how a change becomes a published package.
|
|
165
|
+
- [`CLAUDE.md`](CLAUDE.md), the contract your assistant reads inside the vault: note formats, conventions.
|
|
166
|
+
|
|
167
|
+
## Hacking on imprnt
|
|
168
|
+
|
|
169
|
+
The engine is built with [Bun](https://bun.sh) and [Turborepo](https://turborepo.com) (dev tools
|
|
170
|
+
only, never needed by people who use it through their assistant). Clone, `bun install`,
|
|
171
|
+
`bun run build`, `bun run test`. The architecture and the contributor map are in
|
|
172
|
+
[`docs/architecture.md`](docs/architecture.md).
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT (c) 2026 Aleksandr Bogdanov
|