imprnt 0.1.3-edge.53 → 0.1.3-edge.54

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 CHANGED
@@ -13,6 +13,16 @@ You never run the CLI by hand. You speak in plain language ("ingest this", "load
13
13
 
14
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
15
 
16
+ ## Memory: the vault is the only store
17
+
18
+ One knowledge store, reached on demand. Do **not** use the host's auto-memory (Claude Code's `MEMORY.md` + `memory/*.md`), and do not treat it as a place facts may live. It is a second always-on store `recall` cannot search, so anything parked there is invisible knowledge that silently misleads the next session.
19
+
20
+ The routing test, applied before saving anything durable:
21
+ - **Knowledge** (a fact about a person, a record, a number, project state, a reference) is a **vault note**, found by `recall`.
22
+ - **Behavior** (a standing instruction, a taste rule, how to act) is a markdown fragment loaded every session: this contract for the universal rules, `CLAUDE.local.md` + `plugins/_personal/*` (gitignored) for the personal ones. Never the auto-memory.
23
+
24
+ The same fact never lives in both. Two copies drift, and one is invisible to search. `imprnt check` sweeps every project's host auto-memory (read-only) and flags any that is non-empty, so a leak is caught, not trusted. This is also what keeps imprnt vendor-neutral: the knowledge is plain files you own, with nothing locked inside one vendor's memory, so the agent underneath is swappable.
25
+
16
26
  ## The one rule: deterministic-first = ration the LLM by WHERE it runs
17
27
 
18
28
  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*:
package/dist/cli.js CHANGED
@@ -2036,6 +2036,28 @@ function targetFolders(target) {
2036
2036
  function linksEntity(target) {
2037
2037
  return targetFolders(target).some((f) => ENTITY_FOLDERS.has(f));
2038
2038
  }
2039
+ function hostMemoryStores() {
2040
+ const override = process.env.IMPRNT_HOST_MEMORY_DIR;
2041
+ if (override)
2042
+ return [override];
2043
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join14(homedir3(), ".claude");
2044
+ const projectsDir = join14(configDir, "projects");
2045
+ if (!existsSync12(projectsDir))
2046
+ return [];
2047
+ let entries;
2048
+ try {
2049
+ entries = readdirSync8(projectsDir);
2050
+ } catch {
2051
+ return [];
2052
+ }
2053
+ return entries.map((p) => join14(projectsDir, p, "memory")).filter((d) => {
2054
+ try {
2055
+ return statSync8(d).isDirectory();
2056
+ } catch {
2057
+ return false;
2058
+ }
2059
+ }).sort();
2060
+ }
2039
2061
  function syncNeedsReview(lines2) {
2040
2062
  const p = join14(vault4, "_needs-review.md");
2041
2063
  const exists = existsSync12(p);
@@ -2076,7 +2098,7 @@ ${REVIEW_END}
2076
2098
  writeFileSync9(p, next);
2077
2099
  return lines2.length ? "written" : "cleared";
2078
2100
  }
2079
- var args4, vault4, all = false, ENTITY_FOLDERS, DOMAIN_FOLDERS2, LINK, notes, allSlugs, folderOf, byBasename, orphans, disconnected, domainIssues, untagged, review, referencedRaw, tagVocab, hasRealTag = (tags) => tags.some((t) => /[\p{L}\p{N}]/u.test(normalize(tagVocab, t))), hasTagsFile, addedTags, dupPairs, manifest3, rawEntries, norm = (p) => p.replace(/^\.\//, "").replace(/^.*\/raw\//, "raw/").replace(/\.md$/, ""), refNorm, uncovered, hostMemoryDir, strayMemory, REVIEW_BEGIN = "<!-- imprnt-check:begin (regenerated by `imprnt check` - do not edit between the markers) -->", REVIEW_END = "<!-- imprnt-check:end -->", cap = (xs, n = 25) => xs.slice(0, n).concat(xs.length > n ? [` … +${xs.length - n} more`] : []), count, folders, synced, issues;
2101
+ var args4, vault4, all = false, ENTITY_FOLDERS, DOMAIN_FOLDERS2, LINK, notes, allSlugs, folderOf, byBasename, orphans, disconnected, domainIssues, untagged, review, referencedRaw, tagVocab, hasRealTag = (tags) => tags.some((t) => /[\p{L}\p{N}]/u.test(normalize(tagVocab, t))), hasTagsFile, addedTags, dupPairs, manifest3, rawEntries, norm = (p) => p.replace(/^\.\//, "").replace(/^.*\/raw\//, "raw/").replace(/\.md$/, ""), refNorm, uncovered, memoryStores, strayStores, strayCount, REVIEW_BEGIN = "<!-- imprnt-check:begin (regenerated by `imprnt check` - do not edit between the markers) -->", REVIEW_END = "<!-- imprnt-check:end -->", cap = (xs, n = 25) => xs.slice(0, n).concat(xs.length > n ? [` … +${xs.length - n} more`] : []), count, folders, synced, issues;
2080
2102
  var init_check = __esm(() => {
2081
2103
  init_roots();
2082
2104
  init_moc();
@@ -2214,10 +2236,14 @@ var init_check = __esm(() => {
2214
2236
  uncovered = [...new Set(rawEntries.map(norm))].filter((r) => !refNorm.has(r)).sort();
2215
2237
  for (const r of uncovered)
2216
2238
  review.push(`- [ ] unclassified snapshot \`${r}\` — no vault note points back`);
2217
- hostMemoryDir = process.env.IMPRNT_HOST_MEMORY_DIR ?? join14(homedir3(), ".claude", "projects", projectRoot().replace(/[/.]/g, "-"), "memory");
2218
- strayMemory = existsSync12(hostMemoryDir) ? readdirSync8(hostMemoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort() : [];
2219
- if (strayMemory.length)
2220
- review.push(`- [ ] host auto-memory not empty — ${strayMemory.length} note(s) in ${hostMemoryDir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
2239
+ memoryStores = hostMemoryStores();
2240
+ strayStores = memoryStores.map((dir2) => ({
2241
+ dir: dir2,
2242
+ files: existsSync12(dir2) ? readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort() : []
2243
+ })).filter((s) => s.files.length > 0);
2244
+ strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
2245
+ for (const s of strayStores)
2246
+ review.push(`- [ ] host auto-memory not empty — ${s.files.length} note(s) in ${s.dir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
2221
2247
  console.log(`imprnt check — ${notes.length} notes in ${vault4}
2222
2248
  `);
2223
2249
  if (orphans.length) {
@@ -2269,14 +2295,17 @@ var init_check = __esm(() => {
2269
2295
  } else
2270
2296
  console.log("✓ every raw snapshot has a derived note");
2271
2297
  }
2272
- if (strayMemory.length) {
2273
- console.log(`⚠ host auto-memory not empty (${strayMemory.length}) — a second store recall can't see, at ${hostMemoryDir}:`);
2274
- console.log(cap(strayMemory.map((f) => " " + f)).join(`
2298
+ if (strayCount) {
2299
+ console.log(`⚠ host auto-memory not empty (${strayCount} in ${strayStores.length} project store(s)) — a second store recall can't see:`);
2300
+ for (const s of strayStores) {
2301
+ console.log(` ${s.dir}`);
2302
+ console.log(cap(s.files.map((f) => " " + f)).join(`
2275
2303
  `));
2276
- console.log(` migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it.
2304
+ }
2305
+ console.log(` migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty each.
2277
2306
  `);
2278
2307
  } else
2279
- console.log(`✓ host auto-memory empty (vault is the only store) — checked ${hostMemoryDir}`);
2308
+ console.log(`✓ host auto-memory empty (vault is the only store) — swept ${memoryStores.length} project store(s)`);
2280
2309
  ({ count, folders } = generateIndex(vault4));
2281
2310
  console.log(`↻ regenerated index.md — ${count} notes across ${folders} folders`);
2282
2311
  synced = syncNeedsReview(review);
@@ -2284,7 +2313,7 @@ var init_check = __esm(() => {
2284
2313
  console.log(`↻ ${review.length} finding(s) → _needs-review.md (run \`imprnt hot\` to see them)`);
2285
2314
  else if (synced === "cleared")
2286
2315
  console.log("↻ cleared resolved findings from _needs-review.md");
2287
- issues = orphans.length + disconnected.length + domainIssues.length + untagged.length + uncovered.length + dupPairs.length + strayMemory.length;
2316
+ issues = orphans.length + disconnected.length + domainIssues.length + untagged.length + uncovered.length + dupPairs.length + strayCount;
2288
2317
  console.log(issues ? `
2289
2318
  ${issues} thing(s) to look at above.` : `
2290
2319
  clean.`);
@@ -2334,7 +2363,7 @@ all plugin checks passed.`);
2334
2363
 
2335
2364
  // scripts/cli.ts
2336
2365
  var exports_cli = {};
2337
- import { cpSync as cpSync3, mkdirSync as mkdirSync5, existsSync as existsSync13, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync9, realpathSync as realpathSync2 } from "node:fs";
2366
+ import { cpSync as cpSync3, mkdirSync as mkdirSync5, chmodSync, existsSync as existsSync13, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync9, realpathSync as realpathSync2 } from "node:fs";
2338
2367
  import { spawnSync as spawnSync4 } from "node:child_process";
2339
2368
  import { join as join15, dirname as dirname4, resolve as resolve6, basename as basename5 } from "node:path";
2340
2369
  import { homedir as homedir4 } from "node:os";
@@ -2743,6 +2772,11 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
2743
2772
  cpSync3(join15(pkgRoot, "CLAUDE.md"), claudeMd);
2744
2773
  added.push("CLAUDE.md");
2745
2774
  }
2775
+ try {
2776
+ chmodSync(target, 448);
2777
+ } catch (e) {
2778
+ console.error(`could not set owner-only (chmod 700) on ${target}: ${e instanceof Error ? e.message : String(e)} — tighten it by hand to honor the private-vault promise`);
2779
+ }
2746
2780
  const reg = registerVault(target, { force: rest.includes("--register") });
2747
2781
  if (reg.status === "registered")
2748
2782
  console.log(`registered as imp's default vault project (${configPath()})`);
package/dist/imp.js CHANGED
@@ -2036,6 +2036,28 @@ function targetFolders(target) {
2036
2036
  function linksEntity(target) {
2037
2037
  return targetFolders(target).some((f) => ENTITY_FOLDERS.has(f));
2038
2038
  }
2039
+ function hostMemoryStores() {
2040
+ const override = process.env.IMPRNT_HOST_MEMORY_DIR;
2041
+ if (override)
2042
+ return [override];
2043
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join14(homedir3(), ".claude");
2044
+ const projectsDir = join14(configDir, "projects");
2045
+ if (!existsSync12(projectsDir))
2046
+ return [];
2047
+ let entries;
2048
+ try {
2049
+ entries = readdirSync8(projectsDir);
2050
+ } catch {
2051
+ return [];
2052
+ }
2053
+ return entries.map((p) => join14(projectsDir, p, "memory")).filter((d) => {
2054
+ try {
2055
+ return statSync8(d).isDirectory();
2056
+ } catch {
2057
+ return false;
2058
+ }
2059
+ }).sort();
2060
+ }
2039
2061
  function syncNeedsReview(lines2) {
2040
2062
  const p = join14(vault4, "_needs-review.md");
2041
2063
  const exists = existsSync12(p);
@@ -2076,7 +2098,7 @@ ${REVIEW_END}
2076
2098
  writeFileSync9(p, next);
2077
2099
  return lines2.length ? "written" : "cleared";
2078
2100
  }
2079
- var args4, vault4, all = false, ENTITY_FOLDERS, DOMAIN_FOLDERS2, LINK, notes, allSlugs, folderOf, byBasename, orphans, disconnected, domainIssues, untagged, review, referencedRaw, tagVocab, hasRealTag = (tags) => tags.some((t) => /[\p{L}\p{N}]/u.test(normalize(tagVocab, t))), hasTagsFile, addedTags, dupPairs, manifest3, rawEntries, norm = (p) => p.replace(/^\.\//, "").replace(/^.*\/raw\//, "raw/").replace(/\.md$/, ""), refNorm, uncovered, hostMemoryDir, strayMemory, REVIEW_BEGIN = "<!-- imprnt-check:begin (regenerated by `imprnt check` - do not edit between the markers) -->", REVIEW_END = "<!-- imprnt-check:end -->", cap = (xs, n = 25) => xs.slice(0, n).concat(xs.length > n ? [` … +${xs.length - n} more`] : []), count, folders, synced, issues;
2101
+ var args4, vault4, all = false, ENTITY_FOLDERS, DOMAIN_FOLDERS2, LINK, notes, allSlugs, folderOf, byBasename, orphans, disconnected, domainIssues, untagged, review, referencedRaw, tagVocab, hasRealTag = (tags) => tags.some((t) => /[\p{L}\p{N}]/u.test(normalize(tagVocab, t))), hasTagsFile, addedTags, dupPairs, manifest3, rawEntries, norm = (p) => p.replace(/^\.\//, "").replace(/^.*\/raw\//, "raw/").replace(/\.md$/, ""), refNorm, uncovered, memoryStores, strayStores, strayCount, REVIEW_BEGIN = "<!-- imprnt-check:begin (regenerated by `imprnt check` - do not edit between the markers) -->", REVIEW_END = "<!-- imprnt-check:end -->", cap = (xs, n = 25) => xs.slice(0, n).concat(xs.length > n ? [` … +${xs.length - n} more`] : []), count, folders, synced, issues;
2080
2102
  var init_check = __esm(() => {
2081
2103
  init_roots();
2082
2104
  init_moc();
@@ -2214,10 +2236,14 @@ var init_check = __esm(() => {
2214
2236
  uncovered = [...new Set(rawEntries.map(norm))].filter((r) => !refNorm.has(r)).sort();
2215
2237
  for (const r of uncovered)
2216
2238
  review.push(`- [ ] unclassified snapshot \`${r}\` — no vault note points back`);
2217
- hostMemoryDir = process.env.IMPRNT_HOST_MEMORY_DIR ?? join14(homedir3(), ".claude", "projects", projectRoot().replace(/[/.]/g, "-"), "memory");
2218
- strayMemory = existsSync12(hostMemoryDir) ? readdirSync8(hostMemoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort() : [];
2219
- if (strayMemory.length)
2220
- review.push(`- [ ] host auto-memory not empty — ${strayMemory.length} note(s) in ${hostMemoryDir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
2239
+ memoryStores = hostMemoryStores();
2240
+ strayStores = memoryStores.map((dir2) => ({
2241
+ dir: dir2,
2242
+ files: existsSync12(dir2) ? readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort() : []
2243
+ })).filter((s) => s.files.length > 0);
2244
+ strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
2245
+ for (const s of strayStores)
2246
+ review.push(`- [ ] host auto-memory not empty — ${s.files.length} note(s) in ${s.dir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
2221
2247
  console.log(`imprnt check — ${notes.length} notes in ${vault4}
2222
2248
  `);
2223
2249
  if (orphans.length) {
@@ -2269,14 +2295,17 @@ var init_check = __esm(() => {
2269
2295
  } else
2270
2296
  console.log("✓ every raw snapshot has a derived note");
2271
2297
  }
2272
- if (strayMemory.length) {
2273
- console.log(`⚠ host auto-memory not empty (${strayMemory.length}) — a second store recall can't see, at ${hostMemoryDir}:`);
2274
- console.log(cap(strayMemory.map((f) => " " + f)).join(`
2298
+ if (strayCount) {
2299
+ console.log(`⚠ host auto-memory not empty (${strayCount} in ${strayStores.length} project store(s)) — a second store recall can't see:`);
2300
+ for (const s of strayStores) {
2301
+ console.log(` ${s.dir}`);
2302
+ console.log(cap(s.files.map((f) => " " + f)).join(`
2275
2303
  `));
2276
- console.log(` migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it.
2304
+ }
2305
+ console.log(` migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty each.
2277
2306
  `);
2278
2307
  } else
2279
- console.log(`✓ host auto-memory empty (vault is the only store) — checked ${hostMemoryDir}`);
2308
+ console.log(`✓ host auto-memory empty (vault is the only store) — swept ${memoryStores.length} project store(s)`);
2280
2309
  ({ count, folders } = generateIndex(vault4));
2281
2310
  console.log(`↻ regenerated index.md — ${count} notes across ${folders} folders`);
2282
2311
  synced = syncNeedsReview(review);
@@ -2284,7 +2313,7 @@ var init_check = __esm(() => {
2284
2313
  console.log(`↻ ${review.length} finding(s) → _needs-review.md (run \`imprnt hot\` to see them)`);
2285
2314
  else if (synced === "cleared")
2286
2315
  console.log("↻ cleared resolved findings from _needs-review.md");
2287
- issues = orphans.length + disconnected.length + domainIssues.length + untagged.length + uncovered.length + dupPairs.length + strayMemory.length;
2316
+ issues = orphans.length + disconnected.length + domainIssues.length + untagged.length + uncovered.length + dupPairs.length + strayCount;
2288
2317
  console.log(issues ? `
2289
2318
  ${issues} thing(s) to look at above.` : `
2290
2319
  clean.`);
@@ -2334,7 +2363,7 @@ all plugin checks passed.`);
2334
2363
 
2335
2364
  // scripts/cli.ts
2336
2365
  var exports_cli = {};
2337
- import { cpSync as cpSync3, mkdirSync as mkdirSync5, existsSync as existsSync13, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync9, realpathSync as realpathSync2 } from "node:fs";
2366
+ import { cpSync as cpSync3, mkdirSync as mkdirSync5, chmodSync, existsSync as existsSync13, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync9, realpathSync as realpathSync2 } from "node:fs";
2338
2367
  import { spawnSync as spawnSync4 } from "node:child_process";
2339
2368
  import { join as join15, dirname as dirname4, resolve as resolve6, basename as basename5 } from "node:path";
2340
2369
  import { homedir as homedir4 } from "node:os";
@@ -2743,6 +2772,11 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
2743
2772
  cpSync3(join15(pkgRoot, "CLAUDE.md"), claudeMd);
2744
2773
  added.push("CLAUDE.md");
2745
2774
  }
2775
+ try {
2776
+ chmodSync(target, 448);
2777
+ } catch (e) {
2778
+ console.error(`could not set owner-only (chmod 700) on ${target}: ${e instanceof Error ? e.message : String(e)} — tighten it by hand to honor the private-vault promise`);
2779
+ }
2746
2780
  const reg = registerVault(target, { force: rest.includes("--register") });
2747
2781
  if (reg.status === "registered")
2748
2782
  console.log(`registered as imp's default vault project (${configPath()})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imprnt",
3
- "version": "0.1.3-edge.53",
3
+ "version": "0.1.3-edge.54",
4
4
  "description": "Deterministic-first, plain-markdown knowledge vault. Code does the bulk transform. The LLM only touches the irreducibly-semantic 20%. Native grep plus BM25 retrieval, zero MCP on the vault.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,5 +8,7 @@ decisions, finances, health, and history.
8
8
  `imprnt recall "<keywords>"`, then read the top hits.
9
9
  - Before filing or editing ANYTHING in the vault, run `imprnt context` and follow what it prints.
10
10
  Never write a vault note without it.
11
+ - The vault is the only knowledge store. Durable facts go in a vault note, never the host's
12
+ auto-memory (Claude Code's `MEMORY.md` / `memory/`), which `recall` cannot search.
11
13
  - The vault is private data. Never copy its contents into committed files, code, or external
12
14
  services.