imprnt 0.1.4-edge.88 → 0.1.4-edge.89

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.
Files changed (4) hide show
  1. package/CLAUDE.md +14 -1
  2. package/dist/cli.js +137 -67
  3. package/dist/imp.js +137 -67
  4. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -94,7 +94,20 @@ 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. `type:` in frontmatter (below) records *what each note is* even when it sits in a domain folder, so nothing is lost by filing topically.
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.
98
111
 
99
112
  ## Frontmatter contract
100
113
 
package/dist/cli.js CHANGED
@@ -2454,11 +2454,72 @@ var init_snapshot = __esm(() => {
2454
2454
  console.log(` next: the LLM reads raw/${dest}/ and fans sources out into vault notes (source: raw/${dest}/...).`);
2455
2455
  });
2456
2456
 
2457
+ // scripts/lib/folders.ts
2458
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
2459
+ import { join as join14 } from "node:path";
2460
+ function section2(text2, name) {
2461
+ return text2.match(new RegExp(`##\\s*${name}\\s*\\n([\\s\\S]*?)(?:\\n##\\s|\\s*$)`, "i"))?.[1] ?? "";
2462
+ }
2463
+ function folderTokens(line) {
2464
+ const t = line.trim();
2465
+ if (t === "" || t.startsWith("#") || t.startsWith("<!--") || t.startsWith(">"))
2466
+ return [];
2467
+ return t.replace(/^[-*]\s+/, "").split(",").map((s) => s.trim().normalize("NFC").toLowerCase()).filter((s) => FOLDER_TOKEN.test(s));
2468
+ }
2469
+ function read(text2, name, fallback) {
2470
+ const found = [];
2471
+ for (const line of section2(text2, name).split(/\r?\n/))
2472
+ found.push(...folderTokens(line));
2473
+ return new RegExp(`##\\s*${name}\\s*\\n`, "i").test(text2) ? new Set(found) : new Set(fallback);
2474
+ }
2475
+ function loadFolders(vault4) {
2476
+ const p = join14(vault4, "_folders.md");
2477
+ if (!existsSync12(p)) {
2478
+ return {
2479
+ entities: new Set(DEFAULT_ENTITIES),
2480
+ domains: new Set(DEFAULT_DOMAINS),
2481
+ forms: new Set(DEFAULT_FORMS),
2482
+ mounts: new Set,
2483
+ declared: false
2484
+ };
2485
+ }
2486
+ let text2 = "";
2487
+ try {
2488
+ text2 = readFileSync13(p, "utf8");
2489
+ } catch {
2490
+ return { entities: new Set(DEFAULT_ENTITIES), domains: new Set(DEFAULT_DOMAINS), forms: new Set(DEFAULT_FORMS), mounts: new Set, declared: false };
2491
+ }
2492
+ const roles = {
2493
+ entities: read(text2, "Entities", DEFAULT_ENTITIES),
2494
+ domains: read(text2, "Domains", DEFAULT_DOMAINS),
2495
+ forms: read(text2, "Forms", DEFAULT_FORMS),
2496
+ mounts: read(text2, "Mounts", []),
2497
+ declared: true
2498
+ };
2499
+ for (const f of roles.entities) {
2500
+ roles.domains.delete(f);
2501
+ roles.forms.delete(f);
2502
+ roles.mounts.delete(f);
2503
+ }
2504
+ for (const f of roles.mounts) {
2505
+ roles.domains.delete(f);
2506
+ roles.forms.delete(f);
2507
+ }
2508
+ return roles;
2509
+ }
2510
+ var DEFAULT_ENTITIES, DEFAULT_DOMAINS, DEFAULT_FORMS, FOLDER_TOKEN;
2511
+ var init_folders = __esm(() => {
2512
+ DEFAULT_ENTITIES = ["people", "orgs", "holdings"];
2513
+ DEFAULT_DOMAINS = ["identity", "health", "finances", "work", "life"];
2514
+ DEFAULT_FORMS = ["events", "mistakes", "projects"];
2515
+ FOLDER_TOKEN = /^[\p{L}\p{N}_-]+$/u;
2516
+ });
2517
+
2457
2518
  // scripts/check.ts
2458
2519
  var exports_check = {};
2459
- import { readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync8, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "node:fs";
2520
+ import { readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync8, existsSync as existsSync13, writeFileSync as writeFileSync10 } from "node:fs";
2460
2521
  import { spawnSync as spawnSync3 } from "node:child_process";
2461
- import { join as join14, relative as relative6, dirname as dirname3 } from "node:path";
2522
+ import { join as join15, relative as relative6, dirname as dirname3 } from "node:path";
2462
2523
  import { homedir as homedir3 } from "node:os";
2463
2524
  function resolves(target) {
2464
2525
  const t = target.trim().replace(/^\.\//, "").replace(/\.md$/, "");
@@ -2485,9 +2546,9 @@ function hostMemoryStores() {
2485
2546
  const override = process.env.IMPRNT_HOST_MEMORY_DIR;
2486
2547
  if (override)
2487
2548
  return [override];
2488
- const configDir = process.env.CLAUDE_CONFIG_DIR || join14(homedir3(), ".claude");
2489
- const projectsDir = join14(configDir, "projects");
2490
- if (!existsSync12(projectsDir))
2549
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join15(homedir3(), ".claude");
2550
+ const projectsDir = join15(configDir, "projects");
2551
+ if (!existsSync13(projectsDir))
2491
2552
  return [];
2492
2553
  let entries;
2493
2554
  try {
@@ -2495,7 +2556,7 @@ function hostMemoryStores() {
2495
2556
  } catch {
2496
2557
  return [];
2497
2558
  }
2498
- return entries.map((p) => join14(projectsDir, p, "memory")).filter((d) => {
2559
+ return entries.map((p) => join15(projectsDir, p, "memory")).filter((d) => {
2499
2560
  try {
2500
2561
  return statSync8(d).isDirectory();
2501
2562
  } catch {
@@ -2505,17 +2566,17 @@ function hostMemoryStores() {
2505
2566
  }
2506
2567
  function memoryIndexHasContent(dir2) {
2507
2568
  try {
2508
- return readFileSync13(join14(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
2569
+ return readFileSync14(join15(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
2509
2570
  } catch {
2510
2571
  return false;
2511
2572
  }
2512
2573
  }
2513
2574
  function syncNeedsReview(lines2) {
2514
- const p = join14(vault4, "_needs-review.md");
2515
- const exists = existsSync12(p);
2575
+ const p = join15(vault4, "_needs-review.md");
2576
+ const exists = existsSync13(p);
2516
2577
  if (!exists && lines2.length === 0)
2517
2578
  return "none";
2518
- const prev = exists ? readFileSync13(p, "utf8") : `---
2579
+ const prev = exists ? readFileSync14(p, "utf8") : `---
2519
2580
  type: needs-review
2520
2581
  ---
2521
2582
 
@@ -2524,7 +2585,7 @@ type: needs-review
2524
2585
  `;
2525
2586
  const b = prev.indexOf(REVIEW_BEGIN);
2526
2587
  const e = b !== -1 ? prev.indexOf(REVIEW_END, b + REVIEW_BEGIN.length) : -1;
2527
- const section2 = `${REVIEW_BEGIN}
2588
+ const section3 = `${REVIEW_BEGIN}
2528
2589
  ${lines2.join(`
2529
2590
  `)}
2530
2591
  ${REVIEW_END}
@@ -2538,22 +2599,23 @@ ${REVIEW_END}
2538
2599
  } else {
2539
2600
  after = prev.slice(b + REVIEW_BEGIN.length).replace(/^\n/, "");
2540
2601
  }
2541
- next = before + (lines2.length ? section2 : "") + after;
2602
+ next = before + (lines2.length ? section3 : "") + after;
2542
2603
  } else {
2543
2604
  if (lines2.length === 0)
2544
2605
  return "none";
2545
2606
  next = (prev.endsWith(`
2546
2607
  `) ? prev : prev + `
2547
- `) + section2;
2608
+ `) + section3;
2548
2609
  }
2549
2610
  if (next !== prev || !exists)
2550
2611
  writeFileSync10(p, next);
2551
2612
  return lines2.length ? "written" : "cleared";
2552
2613
  }
2553
- 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;
2614
+ var args4, vault4, all = false, roles, ENTITY_FOLDERS, DOMAIN_FOLDERS2, MOUNT_FOLDERS, 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;
2554
2615
  var init_check = __esm(() => {
2555
2616
  init_roots();
2556
2617
  init_moc();
2618
+ init_folders();
2557
2619
  init_tags();
2558
2620
  init_manifest();
2559
2621
  args4 = process.argv.slice(2);
@@ -2569,12 +2631,14 @@ var init_check = __esm(() => {
2569
2631
  } else if (args4[i] === "--all")
2570
2632
  all = true;
2571
2633
  }
2572
- if (!existsSync12(vault4)) {
2634
+ if (!existsSync13(vault4)) {
2573
2635
  console.error(`no vault at ${vault4} — run \`imprnt init\` first`);
2574
2636
  process.exit(1);
2575
2637
  }
2576
- ENTITY_FOLDERS = new Set(["people", "orgs", "holdings"]);
2577
- DOMAIN_FOLDERS2 = new Set(["identity", "health", "finances", "work", "life"]);
2638
+ roles = loadFolders(vault4);
2639
+ ENTITY_FOLDERS = roles.entities;
2640
+ DOMAIN_FOLDERS2 = roles.domains;
2641
+ MOUNT_FOLDERS = roles.mounts;
2578
2642
  LINK = /\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]/g;
2579
2643
  notes = collectNotes(vault4);
2580
2644
  allSlugs = new Set(notes.map((n) => n.slug));
@@ -2592,7 +2656,7 @@ var init_check = __esm(() => {
2592
2656
  referencedRaw = new Set;
2593
2657
  tagVocab = loadTags(vault4);
2594
2658
  for (const n of notes) {
2595
- const raw = readFileSync13(n.path, "utf8");
2659
+ const raw = readFileSync14(n.path, "utf8");
2596
2660
  const fm2 = frontmatter(raw);
2597
2661
  const links = [...stripCode(raw).matchAll(LINK)].map((m) => m[1].trim()).filter((l) => !l.startsWith("raw/"));
2598
2662
  for (const l of links)
@@ -2600,7 +2664,7 @@ var init_check = __esm(() => {
2600
2664
  orphans.push(` ${n.slug} → [[${l}]]`);
2601
2665
  review.push(`- [ ] orphan link [[${l}]] — from [[${n.slug}]], target note missing`);
2602
2666
  }
2603
- if (!ENTITY_FOLDERS.has(n.folder) && !links.some(linksEntity)) {
2667
+ if (!ENTITY_FOLDERS.has(n.folder) && !MOUNT_FOLDERS.has(n.folder) && !links.some(linksEntity)) {
2604
2668
  disconnected.push(` ${n.slug}`);
2605
2669
  review.push(`- [ ] disconnected note [[${n.slug}]] — links no entity`);
2606
2670
  }
@@ -2620,7 +2684,7 @@ var init_check = __esm(() => {
2620
2684
  for (const s of fmList(fm2, "sources"))
2621
2685
  referencedRaw.add(s.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^\.\//, ""));
2622
2686
  }
2623
- hasTagsFile = existsSync12(join14(vault4, "_tags.md"));
2687
+ hasTagsFile = existsSync13(join15(vault4, "_tags.md"));
2624
2688
  addedTags = [];
2625
2689
  dupPairs = [];
2626
2690
  if (hasTagsFile) {
@@ -2692,7 +2756,7 @@ var init_check = __esm(() => {
2692
2756
  strayStores = memoryStores.map((dir2) => {
2693
2757
  let files3 = [];
2694
2758
  try {
2695
- if (existsSync12(dir2))
2759
+ if (existsSync13(dir2))
2696
2760
  files3 = readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
2697
2761
  } catch (e) {
2698
2762
  console.error(`⚠ host auto-memory sweep: could not read ${dir2} (${e instanceof Error ? e.message : String(e)}) - skipping it`);
@@ -2704,8 +2768,14 @@ var init_check = __esm(() => {
2704
2768
  strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
2705
2769
  for (const s of strayStores)
2706
2770
  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`);
2707
- console.log(`imprnt check — ${notes.length} notes in ${vault4}
2708
- `);
2771
+ console.log(`imprnt check — ${notes.length} notes in ${vault4}`);
2772
+ if (roles.declared) {
2773
+ const parts = [`entities: ${[...ENTITY_FOLDERS].join(" ") || "(none)"}`, `domains: ${[...DOMAIN_FOLDERS2].join(" ") || "(none)"}`];
2774
+ if (MOUNT_FOLDERS.size)
2775
+ parts.push(`mounts: ${[...MOUNT_FOLDERS].join(" ")}`);
2776
+ console.log(`folder roles from _folders.md — ${parts.join(", ")}`);
2777
+ }
2778
+ console.log("");
2709
2779
  if (orphans.length) {
2710
2780
  console.log(`⚠ orphan links (${orphans.length}) — target note missing:`);
2711
2781
  console.log(cap(orphans).join(`
@@ -2778,18 +2848,18 @@ var init_check = __esm(() => {
2778
2848
  ${issues} thing(s) to look at above.` : `
2779
2849
  clean.`);
2780
2850
  if (all) {
2781
- const pluginsDir = join14(projectRoot(), "plugins");
2851
+ const pluginsDir = join15(projectRoot(), "plugins");
2782
2852
  const checks = [];
2783
- if (existsSync12(pluginsDir)) {
2853
+ if (existsSync13(pluginsDir)) {
2784
2854
  for (const entry of readdirSync8(pluginsDir)) {
2785
2855
  let isDir2 = false;
2786
2856
  try {
2787
- isDir2 = statSync8(join14(pluginsDir, entry)).isDirectory();
2857
+ isDir2 = statSync8(join15(pluginsDir, entry)).isDirectory();
2788
2858
  } catch {
2789
2859
  continue;
2790
2860
  }
2791
- const p = join14(pluginsDir, entry, "check.js");
2792
- if (isDir2 && existsSync12(p))
2861
+ const p = join15(pluginsDir, entry, "check.js");
2862
+ if (isDir2 && existsSync13(p))
2793
2863
  checks.push(p);
2794
2864
  }
2795
2865
  }
@@ -2824,12 +2894,12 @@ all plugin checks passed.`);
2824
2894
  // scripts/vault.ts
2825
2895
  var exports_vault = {};
2826
2896
  import { spawnSync as spawnSync4 } from "node:child_process";
2827
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
2897
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
2828
2898
  import { homedir as homedir4 } from "node:os";
2829
- import { basename as basename6, dirname as dirname4, join as join15, resolve as resolve6 } from "node:path";
2899
+ import { basename as basename6, dirname as dirname4, join as join16, resolve as resolve6 } from "node:path";
2830
2900
  function noteCount(project) {
2831
- const root = join15(project, "vault");
2832
- if (!existsSync13(root))
2901
+ const root = join16(project, "vault");
2902
+ if (!existsSync14(root))
2833
2903
  return 0;
2834
2904
  let n = 0;
2835
2905
  const walk3 = (dir2, atRoot) => {
@@ -2841,7 +2911,7 @@ function noteCount(project) {
2841
2911
  if (atRoot && CONTROL3.has(e.name))
2842
2912
  continue;
2843
2913
  if (e.isDirectory())
2844
- walk3(join15(dir2, e.name), false);
2914
+ walk3(join16(dir2, e.name), false);
2845
2915
  else if (e.name.endsWith(".md"))
2846
2916
  n++;
2847
2917
  }
@@ -2884,7 +2954,7 @@ function tar(...a) {
2884
2954
  var args5, sub, flag = (name) => {
2885
2955
  const i = args5.indexOf(name);
2886
2956
  return i >= 0 ? args5[i + 1] : undefined;
2887
- }, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join15(homedir4(), p.slice(2)) : p), CONTROL3;
2957
+ }, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join16(homedir4(), p.slice(2)) : p), CONTROL3;
2888
2958
  var init_vault = __esm(() => {
2889
2959
  init_registry();
2890
2960
  args5 = process.argv.slice(2);
@@ -2919,7 +2989,7 @@ var init_vault = __esm(() => {
2919
2989
  const outDir = expand(flag("--out") ?? ".");
2920
2990
  mkdirSync5(outDir, { recursive: true });
2921
2991
  const stamp = new Date().toISOString().slice(0, 10);
2922
- const out = join15(outDir, `${name}-${stamp}.tar.gz`);
2992
+ const out = join16(outDir, `${name}-${stamp}.tar.gz`);
2923
2993
  if (tar("czf", out, "-C", dirname4(path), basename6(path)) !== 0) {
2924
2994
  console.error("archive failed");
2925
2995
  process.exit(1);
@@ -2940,11 +3010,11 @@ var init_vault = __esm(() => {
2940
3010
  console.error("usage: imprnt vault restore <archive.tar.gz> <dir>");
2941
3011
  process.exit(1);
2942
3012
  }
2943
- if (!existsSync13(src3)) {
3013
+ if (!existsSync14(src3)) {
2944
3014
  console.error(`no archive at ${src3}`);
2945
3015
  process.exit(1);
2946
3016
  }
2947
- if (existsSync13(dest2) && readdirSync9(dest2).length) {
3017
+ if (existsSync14(dest2) && readdirSync9(dest2).length) {
2948
3018
  console.error(`${dest2} is not empty — restore into a new directory, then move it`);
2949
3019
  process.exit(1);
2950
3020
  }
@@ -2953,7 +3023,7 @@ var init_vault = __esm(() => {
2953
3023
  console.error("restore failed");
2954
3024
  process.exit(1);
2955
3025
  }
2956
- const inner = readdirSync9(dest2).map((e) => join15(dest2, e)).filter((p) => statSync9(p).isDirectory());
3026
+ const inner = readdirSync9(dest2).map((e) => join16(dest2, e)).filter((p) => statSync9(p).isDirectory());
2957
3027
  const project = inner.find((p) => isVaultProject(p)) ?? (isVaultProject(dest2) ? dest2 : undefined);
2958
3028
  if (!project) {
2959
3029
  console.error(`extracted, but ${dest2} holds no vault project — check the archive`);
@@ -2981,9 +3051,9 @@ pointed at nothing — so the vault outlives whatever reads it.`);
2981
3051
 
2982
3052
  // scripts/cli.ts
2983
3053
  var exports_cli = {};
2984
- import { cpSync as cpSync3, mkdirSync as mkdirSync6, chmodSync, existsSync as existsSync14, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync10, realpathSync as realpathSync2 } from "node:fs";
3054
+ import { cpSync as cpSync3, mkdirSync as mkdirSync6, chmodSync, existsSync as existsSync15, readFileSync as readFileSync15, lstatSync as lstatSync4, statSync as statSync10, realpathSync as realpathSync2 } from "node:fs";
2985
3055
  import { spawnSync as spawnSync5 } from "node:child_process";
2986
- import { join as join16, dirname as dirname5, resolve as resolve7, basename as basename7 } from "node:path";
3056
+ import { join as join17, dirname as dirname5, resolve as resolve7, basename as basename7 } from "node:path";
2987
3057
  import { homedir as homedir5 } from "node:os";
2988
3058
  import { fileURLToPath } from "node:url";
2989
3059
  import { createInterface as createInterface2 } from "node:readline/promises";
@@ -2991,7 +3061,7 @@ function pluginRoot() {
2991
3061
  const local = projectRoot();
2992
3062
  if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
2993
3063
  return local;
2994
- if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
3064
+ if (existsSync15(join17(local, "vault")) || existsSync15(join17(local, "CLAUDE.local.md")))
2995
3065
  return local;
2996
3066
  return registeredRoot() ?? local;
2997
3067
  }
@@ -3020,7 +3090,7 @@ function resolvePath(input, base = process.cwd(), home = homedir5()) {
3020
3090
  if (p === "~")
3021
3091
  p = home;
3022
3092
  else if (p.startsWith("~/"))
3023
- p = join16(home, p.slice(2));
3093
+ p = join17(home, p.slice(2));
3024
3094
  return resolve7(base, p);
3025
3095
  }
3026
3096
  function initPositional(args6) {
@@ -3029,7 +3099,7 @@ function initPositional(args6) {
3029
3099
  function physicalTarget(target) {
3030
3100
  let existing = target;
3031
3101
  const tail = [];
3032
- while (!existsSync14(existing)) {
3102
+ while (!existsSync15(existing)) {
3033
3103
  const parent = dirname5(existing);
3034
3104
  if (parent === existing)
3035
3105
  return target;
@@ -3037,7 +3107,7 @@ function physicalTarget(target) {
3037
3107
  existing = parent;
3038
3108
  }
3039
3109
  try {
3040
- return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
3110
+ return tail.length ? join17(realpathSync2(existing), ...tail) : realpathSync2(existing);
3041
3111
  } catch {
3042
3112
  return target;
3043
3113
  }
@@ -3073,7 +3143,7 @@ var init_cli = __esm(async () => {
3073
3143
  init_registry();
3074
3144
  init_launch();
3075
3145
  here = dirname5(fileURLToPath(import.meta.url));
3076
- pkgRoot = join16(here, "..");
3146
+ pkgRoot = join17(here, "..");
3077
3147
  [cmd, ...rest] = process.argv.slice(2);
3078
3148
  asImp = globalThis.__IMPRNT_IMP__ === true;
3079
3149
  if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
@@ -3103,12 +3173,12 @@ var init_cli = __esm(async () => {
3103
3173
  console.log(r);
3104
3174
  console.log("");
3105
3175
  }
3106
- const p = join16(vault5, "hot.md");
3107
- if (!existsSync14(p)) {
3176
+ const p = join17(vault5, "hot.md");
3177
+ if (!existsSync15(p)) {
3108
3178
  console.error(`no hot.md at ${p} — run \`imprnt init\``);
3109
3179
  process.exit(1);
3110
3180
  }
3111
- console.log(readFileSync14(p, "utf8"));
3181
+ console.log(readFileSync15(p, "utf8"));
3112
3182
  break;
3113
3183
  }
3114
3184
  case "lair": {
@@ -3120,12 +3190,12 @@ var init_cli = __esm(async () => {
3120
3190
  }
3121
3191
  case "context": {
3122
3192
  const home = requireVaultHome();
3123
- const contract = join16(home, "CLAUDE.md");
3124
- if (!existsSync14(contract)) {
3193
+ const contract = join17(home, "CLAUDE.md");
3194
+ if (!existsSync15(contract)) {
3125
3195
  console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
3126
3196
  process.exit(1);
3127
3197
  }
3128
- process.stdout.write(readFileSync14(contract, "utf8"));
3198
+ process.stdout.write(readFileSync15(contract, "utf8"));
3129
3199
  break;
3130
3200
  }
3131
3201
  case "agent": {
@@ -3324,7 +3394,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3324
3394
  process.exit(1);
3325
3395
  }
3326
3396
  case "global": {
3327
- const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
3397
+ const globalDir = process.env.CLAUDE_CONFIG_DIR || join17(homedir5(), ".claude");
3328
3398
  const [sub2, ...gargs] = rest;
3329
3399
  if (sub2 === "list") {
3330
3400
  const enabled = listGlobalModules(globalDir);
@@ -3359,8 +3429,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3359
3429
  }
3360
3430
  const name = names[0];
3361
3431
  const proj = pluginRoot();
3362
- const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
3363
- if (!existsSync14(srcDir)) {
3432
+ const srcDir = from ? resolve7(from) : join17(proj, "plugins", name);
3433
+ if (!existsSync15(srcDir)) {
3364
3434
  console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
3365
3435
  process.exit(1);
3366
3436
  }
@@ -3404,7 +3474,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3404
3474
  } else if (process.stdin.isTTY && process.stdout.isTTY) {
3405
3475
  const inPlace = isVaultProject(process.cwd());
3406
3476
  const display = inPlace ? process.cwd() : "~/imprnt";
3407
- const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
3477
+ const fallback = inPlace ? process.cwd() : join17(homedir5(), "imprnt");
3408
3478
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
3409
3479
  const answer = (await rl.question(`vault location [${display}]: `)).trim();
3410
3480
  rl.close();
@@ -3413,7 +3483,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3413
3483
  target = process.cwd();
3414
3484
  }
3415
3485
  const toCwd = target === process.cwd();
3416
- const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
3486
+ const rel = (sub2) => toCwd ? `./${sub2}` : join17(target, sub2);
3417
3487
  const lexicalEnclosing = projectRoot(target);
3418
3488
  const physical = physicalTarget(target);
3419
3489
  const physicalEnclosing = projectRoot(physical);
@@ -3426,12 +3496,12 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3426
3496
  const domains = ["identity", "health", "finances", "work", "life", "projects"];
3427
3497
  const forms = ["events", "mistakes"];
3428
3498
  const vaultDirs = [...entities, ...domains, ...forms];
3429
- const vaultPath = join16(target, "vault");
3430
- const vaultExisted = existsSync14(vaultPath);
3499
+ const vaultPath = join17(target, "vault");
3500
+ const vaultExisted = existsSync15(vaultPath);
3431
3501
  let createdDirs = 0;
3432
3502
  for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
3433
- const abs = d ? join16(target, d) : target;
3434
- if (!existsSync14(abs))
3503
+ const abs = d ? join17(target, d) : target;
3504
+ if (!existsSync15(abs))
3435
3505
  createdDirs++;
3436
3506
  try {
3437
3507
  mkdirSync6(abs, { recursive: true });
@@ -3446,25 +3516,25 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3446
3516
  };
3447
3517
  const added = [];
3448
3518
  for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
3449
- const dst = join16(vaultPath, f);
3519
+ const dst = join17(vaultPath, f);
3450
3520
  const slot = controlSlot(dst);
3451
3521
  if (slot.startsWith("blocked")) {
3452
3522
  console.error(blockedMsg(`vault/${f}`, slot));
3453
3523
  process.exit(1);
3454
3524
  }
3455
3525
  if (slot === "create") {
3456
- cpSync3(join16(pkgRoot, "templates", f), dst);
3526
+ cpSync3(join17(pkgRoot, "templates", f), dst);
3457
3527
  added.push(`vault/${f}`);
3458
3528
  }
3459
3529
  }
3460
- const claudeMd = join16(target, "CLAUDE.md");
3530
+ const claudeMd = join17(target, "CLAUDE.md");
3461
3531
  const cmSlot = controlSlot(claudeMd);
3462
3532
  if (cmSlot.startsWith("blocked")) {
3463
3533
  console.error(blockedMsg("CLAUDE.md", cmSlot));
3464
3534
  process.exit(1);
3465
3535
  }
3466
- if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
3467
- cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
3536
+ if (cmSlot === "create" && existsSync15(join17(pkgRoot, "CLAUDE.md"))) {
3537
+ cpSync3(join17(pkgRoot, "CLAUDE.md"), claudeMd);
3468
3538
  added.push("CLAUDE.md");
3469
3539
  }
3470
3540
  try {
@@ -3506,8 +3576,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3506
3576
  const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
3507
3577
  const bare = cmd === undefined;
3508
3578
  if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
3509
- const modScript = join16(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3510
- if (existsSync14(modScript)) {
3579
+ const modScript = join17(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3580
+ if (existsSync15(modScript)) {
3511
3581
  const proc = spawnSync5(process.execPath, [modScript, ...rest], { stdio: "inherit" });
3512
3582
  process.exit(proc.status ?? 1);
3513
3583
  }
package/dist/imp.js CHANGED
@@ -2454,11 +2454,72 @@ var init_snapshot = __esm(() => {
2454
2454
  console.log(` next: the LLM reads raw/${dest}/ and fans sources out into vault notes (source: raw/${dest}/...).`);
2455
2455
  });
2456
2456
 
2457
+ // scripts/lib/folders.ts
2458
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
2459
+ import { join as join14 } from "node:path";
2460
+ function section2(text2, name) {
2461
+ return text2.match(new RegExp(`##\\s*${name}\\s*\\n([\\s\\S]*?)(?:\\n##\\s|\\s*$)`, "i"))?.[1] ?? "";
2462
+ }
2463
+ function folderTokens(line) {
2464
+ const t = line.trim();
2465
+ if (t === "" || t.startsWith("#") || t.startsWith("<!--") || t.startsWith(">"))
2466
+ return [];
2467
+ return t.replace(/^[-*]\s+/, "").split(",").map((s) => s.trim().normalize("NFC").toLowerCase()).filter((s) => FOLDER_TOKEN.test(s));
2468
+ }
2469
+ function read(text2, name, fallback) {
2470
+ const found = [];
2471
+ for (const line of section2(text2, name).split(/\r?\n/))
2472
+ found.push(...folderTokens(line));
2473
+ return new RegExp(`##\\s*${name}\\s*\\n`, "i").test(text2) ? new Set(found) : new Set(fallback);
2474
+ }
2475
+ function loadFolders(vault4) {
2476
+ const p = join14(vault4, "_folders.md");
2477
+ if (!existsSync12(p)) {
2478
+ return {
2479
+ entities: new Set(DEFAULT_ENTITIES),
2480
+ domains: new Set(DEFAULT_DOMAINS),
2481
+ forms: new Set(DEFAULT_FORMS),
2482
+ mounts: new Set,
2483
+ declared: false
2484
+ };
2485
+ }
2486
+ let text2 = "";
2487
+ try {
2488
+ text2 = readFileSync13(p, "utf8");
2489
+ } catch {
2490
+ return { entities: new Set(DEFAULT_ENTITIES), domains: new Set(DEFAULT_DOMAINS), forms: new Set(DEFAULT_FORMS), mounts: new Set, declared: false };
2491
+ }
2492
+ const roles = {
2493
+ entities: read(text2, "Entities", DEFAULT_ENTITIES),
2494
+ domains: read(text2, "Domains", DEFAULT_DOMAINS),
2495
+ forms: read(text2, "Forms", DEFAULT_FORMS),
2496
+ mounts: read(text2, "Mounts", []),
2497
+ declared: true
2498
+ };
2499
+ for (const f of roles.entities) {
2500
+ roles.domains.delete(f);
2501
+ roles.forms.delete(f);
2502
+ roles.mounts.delete(f);
2503
+ }
2504
+ for (const f of roles.mounts) {
2505
+ roles.domains.delete(f);
2506
+ roles.forms.delete(f);
2507
+ }
2508
+ return roles;
2509
+ }
2510
+ var DEFAULT_ENTITIES, DEFAULT_DOMAINS, DEFAULT_FORMS, FOLDER_TOKEN;
2511
+ var init_folders = __esm(() => {
2512
+ DEFAULT_ENTITIES = ["people", "orgs", "holdings"];
2513
+ DEFAULT_DOMAINS = ["identity", "health", "finances", "work", "life"];
2514
+ DEFAULT_FORMS = ["events", "mistakes", "projects"];
2515
+ FOLDER_TOKEN = /^[\p{L}\p{N}_-]+$/u;
2516
+ });
2517
+
2457
2518
  // scripts/check.ts
2458
2519
  var exports_check = {};
2459
- import { readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync8, existsSync as existsSync12, writeFileSync as writeFileSync10 } from "node:fs";
2520
+ import { readdirSync as readdirSync8, readFileSync as readFileSync14, statSync as statSync8, existsSync as existsSync13, writeFileSync as writeFileSync10 } from "node:fs";
2460
2521
  import { spawnSync as spawnSync3 } from "node:child_process";
2461
- import { join as join14, relative as relative6, dirname as dirname3 } from "node:path";
2522
+ import { join as join15, relative as relative6, dirname as dirname3 } from "node:path";
2462
2523
  import { homedir as homedir3 } from "node:os";
2463
2524
  function resolves(target) {
2464
2525
  const t = target.trim().replace(/^\.\//, "").replace(/\.md$/, "");
@@ -2485,9 +2546,9 @@ function hostMemoryStores() {
2485
2546
  const override = process.env.IMPRNT_HOST_MEMORY_DIR;
2486
2547
  if (override)
2487
2548
  return [override];
2488
- const configDir = process.env.CLAUDE_CONFIG_DIR || join14(homedir3(), ".claude");
2489
- const projectsDir = join14(configDir, "projects");
2490
- if (!existsSync12(projectsDir))
2549
+ const configDir = process.env.CLAUDE_CONFIG_DIR || join15(homedir3(), ".claude");
2550
+ const projectsDir = join15(configDir, "projects");
2551
+ if (!existsSync13(projectsDir))
2491
2552
  return [];
2492
2553
  let entries;
2493
2554
  try {
@@ -2495,7 +2556,7 @@ function hostMemoryStores() {
2495
2556
  } catch {
2496
2557
  return [];
2497
2558
  }
2498
- return entries.map((p) => join14(projectsDir, p, "memory")).filter((d) => {
2559
+ return entries.map((p) => join15(projectsDir, p, "memory")).filter((d) => {
2499
2560
  try {
2500
2561
  return statSync8(d).isDirectory();
2501
2562
  } catch {
@@ -2505,17 +2566,17 @@ function hostMemoryStores() {
2505
2566
  }
2506
2567
  function memoryIndexHasContent(dir2) {
2507
2568
  try {
2508
- return readFileSync13(join14(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
2569
+ return readFileSync14(join15(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
2509
2570
  } catch {
2510
2571
  return false;
2511
2572
  }
2512
2573
  }
2513
2574
  function syncNeedsReview(lines2) {
2514
- const p = join14(vault4, "_needs-review.md");
2515
- const exists = existsSync12(p);
2575
+ const p = join15(vault4, "_needs-review.md");
2576
+ const exists = existsSync13(p);
2516
2577
  if (!exists && lines2.length === 0)
2517
2578
  return "none";
2518
- const prev = exists ? readFileSync13(p, "utf8") : `---
2579
+ const prev = exists ? readFileSync14(p, "utf8") : `---
2519
2580
  type: needs-review
2520
2581
  ---
2521
2582
 
@@ -2524,7 +2585,7 @@ type: needs-review
2524
2585
  `;
2525
2586
  const b = prev.indexOf(REVIEW_BEGIN);
2526
2587
  const e = b !== -1 ? prev.indexOf(REVIEW_END, b + REVIEW_BEGIN.length) : -1;
2527
- const section2 = `${REVIEW_BEGIN}
2588
+ const section3 = `${REVIEW_BEGIN}
2528
2589
  ${lines2.join(`
2529
2590
  `)}
2530
2591
  ${REVIEW_END}
@@ -2538,22 +2599,23 @@ ${REVIEW_END}
2538
2599
  } else {
2539
2600
  after = prev.slice(b + REVIEW_BEGIN.length).replace(/^\n/, "");
2540
2601
  }
2541
- next = before + (lines2.length ? section2 : "") + after;
2602
+ next = before + (lines2.length ? section3 : "") + after;
2542
2603
  } else {
2543
2604
  if (lines2.length === 0)
2544
2605
  return "none";
2545
2606
  next = (prev.endsWith(`
2546
2607
  `) ? prev : prev + `
2547
- `) + section2;
2608
+ `) + section3;
2548
2609
  }
2549
2610
  if (next !== prev || !exists)
2550
2611
  writeFileSync10(p, next);
2551
2612
  return lines2.length ? "written" : "cleared";
2552
2613
  }
2553
- 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;
2614
+ var args4, vault4, all = false, roles, ENTITY_FOLDERS, DOMAIN_FOLDERS2, MOUNT_FOLDERS, 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;
2554
2615
  var init_check = __esm(() => {
2555
2616
  init_roots();
2556
2617
  init_moc();
2618
+ init_folders();
2557
2619
  init_tags();
2558
2620
  init_manifest();
2559
2621
  args4 = process.argv.slice(2);
@@ -2569,12 +2631,14 @@ var init_check = __esm(() => {
2569
2631
  } else if (args4[i] === "--all")
2570
2632
  all = true;
2571
2633
  }
2572
- if (!existsSync12(vault4)) {
2634
+ if (!existsSync13(vault4)) {
2573
2635
  console.error(`no vault at ${vault4} — run \`imprnt init\` first`);
2574
2636
  process.exit(1);
2575
2637
  }
2576
- ENTITY_FOLDERS = new Set(["people", "orgs", "holdings"]);
2577
- DOMAIN_FOLDERS2 = new Set(["identity", "health", "finances", "work", "life"]);
2638
+ roles = loadFolders(vault4);
2639
+ ENTITY_FOLDERS = roles.entities;
2640
+ DOMAIN_FOLDERS2 = roles.domains;
2641
+ MOUNT_FOLDERS = roles.mounts;
2578
2642
  LINK = /\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]/g;
2579
2643
  notes = collectNotes(vault4);
2580
2644
  allSlugs = new Set(notes.map((n) => n.slug));
@@ -2592,7 +2656,7 @@ var init_check = __esm(() => {
2592
2656
  referencedRaw = new Set;
2593
2657
  tagVocab = loadTags(vault4);
2594
2658
  for (const n of notes) {
2595
- const raw = readFileSync13(n.path, "utf8");
2659
+ const raw = readFileSync14(n.path, "utf8");
2596
2660
  const fm2 = frontmatter(raw);
2597
2661
  const links = [...stripCode(raw).matchAll(LINK)].map((m) => m[1].trim()).filter((l) => !l.startsWith("raw/"));
2598
2662
  for (const l of links)
@@ -2600,7 +2664,7 @@ var init_check = __esm(() => {
2600
2664
  orphans.push(` ${n.slug} → [[${l}]]`);
2601
2665
  review.push(`- [ ] orphan link [[${l}]] — from [[${n.slug}]], target note missing`);
2602
2666
  }
2603
- if (!ENTITY_FOLDERS.has(n.folder) && !links.some(linksEntity)) {
2667
+ if (!ENTITY_FOLDERS.has(n.folder) && !MOUNT_FOLDERS.has(n.folder) && !links.some(linksEntity)) {
2604
2668
  disconnected.push(` ${n.slug}`);
2605
2669
  review.push(`- [ ] disconnected note [[${n.slug}]] — links no entity`);
2606
2670
  }
@@ -2620,7 +2684,7 @@ var init_check = __esm(() => {
2620
2684
  for (const s of fmList(fm2, "sources"))
2621
2685
  referencedRaw.add(s.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^\.\//, ""));
2622
2686
  }
2623
- hasTagsFile = existsSync12(join14(vault4, "_tags.md"));
2687
+ hasTagsFile = existsSync13(join15(vault4, "_tags.md"));
2624
2688
  addedTags = [];
2625
2689
  dupPairs = [];
2626
2690
  if (hasTagsFile) {
@@ -2692,7 +2756,7 @@ var init_check = __esm(() => {
2692
2756
  strayStores = memoryStores.map((dir2) => {
2693
2757
  let files3 = [];
2694
2758
  try {
2695
- if (existsSync12(dir2))
2759
+ if (existsSync13(dir2))
2696
2760
  files3 = readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
2697
2761
  } catch (e) {
2698
2762
  console.error(`⚠ host auto-memory sweep: could not read ${dir2} (${e instanceof Error ? e.message : String(e)}) - skipping it`);
@@ -2704,8 +2768,14 @@ var init_check = __esm(() => {
2704
2768
  strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
2705
2769
  for (const s of strayStores)
2706
2770
  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`);
2707
- console.log(`imprnt check — ${notes.length} notes in ${vault4}
2708
- `);
2771
+ console.log(`imprnt check — ${notes.length} notes in ${vault4}`);
2772
+ if (roles.declared) {
2773
+ const parts = [`entities: ${[...ENTITY_FOLDERS].join(" ") || "(none)"}`, `domains: ${[...DOMAIN_FOLDERS2].join(" ") || "(none)"}`];
2774
+ if (MOUNT_FOLDERS.size)
2775
+ parts.push(`mounts: ${[...MOUNT_FOLDERS].join(" ")}`);
2776
+ console.log(`folder roles from _folders.md — ${parts.join(", ")}`);
2777
+ }
2778
+ console.log("");
2709
2779
  if (orphans.length) {
2710
2780
  console.log(`⚠ orphan links (${orphans.length}) — target note missing:`);
2711
2781
  console.log(cap(orphans).join(`
@@ -2778,18 +2848,18 @@ var init_check = __esm(() => {
2778
2848
  ${issues} thing(s) to look at above.` : `
2779
2849
  clean.`);
2780
2850
  if (all) {
2781
- const pluginsDir = join14(projectRoot(), "plugins");
2851
+ const pluginsDir = join15(projectRoot(), "plugins");
2782
2852
  const checks = [];
2783
- if (existsSync12(pluginsDir)) {
2853
+ if (existsSync13(pluginsDir)) {
2784
2854
  for (const entry of readdirSync8(pluginsDir)) {
2785
2855
  let isDir2 = false;
2786
2856
  try {
2787
- isDir2 = statSync8(join14(pluginsDir, entry)).isDirectory();
2857
+ isDir2 = statSync8(join15(pluginsDir, entry)).isDirectory();
2788
2858
  } catch {
2789
2859
  continue;
2790
2860
  }
2791
- const p = join14(pluginsDir, entry, "check.js");
2792
- if (isDir2 && existsSync12(p))
2861
+ const p = join15(pluginsDir, entry, "check.js");
2862
+ if (isDir2 && existsSync13(p))
2793
2863
  checks.push(p);
2794
2864
  }
2795
2865
  }
@@ -2824,12 +2894,12 @@ all plugin checks passed.`);
2824
2894
  // scripts/vault.ts
2825
2895
  var exports_vault = {};
2826
2896
  import { spawnSync as spawnSync4 } from "node:child_process";
2827
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
2897
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
2828
2898
  import { homedir as homedir4 } from "node:os";
2829
- import { basename as basename6, dirname as dirname4, join as join15, resolve as resolve6 } from "node:path";
2899
+ import { basename as basename6, dirname as dirname4, join as join16, resolve as resolve6 } from "node:path";
2830
2900
  function noteCount(project) {
2831
- const root = join15(project, "vault");
2832
- if (!existsSync13(root))
2901
+ const root = join16(project, "vault");
2902
+ if (!existsSync14(root))
2833
2903
  return 0;
2834
2904
  let n = 0;
2835
2905
  const walk3 = (dir2, atRoot) => {
@@ -2841,7 +2911,7 @@ function noteCount(project) {
2841
2911
  if (atRoot && CONTROL3.has(e.name))
2842
2912
  continue;
2843
2913
  if (e.isDirectory())
2844
- walk3(join15(dir2, e.name), false);
2914
+ walk3(join16(dir2, e.name), false);
2845
2915
  else if (e.name.endsWith(".md"))
2846
2916
  n++;
2847
2917
  }
@@ -2884,7 +2954,7 @@ function tar(...a) {
2884
2954
  var args5, sub, flag = (name) => {
2885
2955
  const i = args5.indexOf(name);
2886
2956
  return i >= 0 ? args5[i + 1] : undefined;
2887
- }, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join15(homedir4(), p.slice(2)) : p), CONTROL3;
2957
+ }, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join16(homedir4(), p.slice(2)) : p), CONTROL3;
2888
2958
  var init_vault = __esm(() => {
2889
2959
  init_registry();
2890
2960
  args5 = process.argv.slice(2);
@@ -2919,7 +2989,7 @@ var init_vault = __esm(() => {
2919
2989
  const outDir = expand(flag("--out") ?? ".");
2920
2990
  mkdirSync5(outDir, { recursive: true });
2921
2991
  const stamp = new Date().toISOString().slice(0, 10);
2922
- const out = join15(outDir, `${name}-${stamp}.tar.gz`);
2992
+ const out = join16(outDir, `${name}-${stamp}.tar.gz`);
2923
2993
  if (tar("czf", out, "-C", dirname4(path), basename6(path)) !== 0) {
2924
2994
  console.error("archive failed");
2925
2995
  process.exit(1);
@@ -2940,11 +3010,11 @@ var init_vault = __esm(() => {
2940
3010
  console.error("usage: imprnt vault restore <archive.tar.gz> <dir>");
2941
3011
  process.exit(1);
2942
3012
  }
2943
- if (!existsSync13(src3)) {
3013
+ if (!existsSync14(src3)) {
2944
3014
  console.error(`no archive at ${src3}`);
2945
3015
  process.exit(1);
2946
3016
  }
2947
- if (existsSync13(dest2) && readdirSync9(dest2).length) {
3017
+ if (existsSync14(dest2) && readdirSync9(dest2).length) {
2948
3018
  console.error(`${dest2} is not empty — restore into a new directory, then move it`);
2949
3019
  process.exit(1);
2950
3020
  }
@@ -2953,7 +3023,7 @@ var init_vault = __esm(() => {
2953
3023
  console.error("restore failed");
2954
3024
  process.exit(1);
2955
3025
  }
2956
- const inner = readdirSync9(dest2).map((e) => join15(dest2, e)).filter((p) => statSync9(p).isDirectory());
3026
+ const inner = readdirSync9(dest2).map((e) => join16(dest2, e)).filter((p) => statSync9(p).isDirectory());
2957
3027
  const project = inner.find((p) => isVaultProject(p)) ?? (isVaultProject(dest2) ? dest2 : undefined);
2958
3028
  if (!project) {
2959
3029
  console.error(`extracted, but ${dest2} holds no vault project — check the archive`);
@@ -2981,9 +3051,9 @@ pointed at nothing — so the vault outlives whatever reads it.`);
2981
3051
 
2982
3052
  // scripts/cli.ts
2983
3053
  var exports_cli = {};
2984
- import { cpSync as cpSync3, mkdirSync as mkdirSync6, chmodSync, existsSync as existsSync14, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync10, realpathSync as realpathSync2 } from "node:fs";
3054
+ import { cpSync as cpSync3, mkdirSync as mkdirSync6, chmodSync, existsSync as existsSync15, readFileSync as readFileSync15, lstatSync as lstatSync4, statSync as statSync10, realpathSync as realpathSync2 } from "node:fs";
2985
3055
  import { spawnSync as spawnSync5 } from "node:child_process";
2986
- import { join as join16, dirname as dirname5, resolve as resolve7, basename as basename7 } from "node:path";
3056
+ import { join as join17, dirname as dirname5, resolve as resolve7, basename as basename7 } from "node:path";
2987
3057
  import { homedir as homedir5 } from "node:os";
2988
3058
  import { fileURLToPath } from "node:url";
2989
3059
  import { createInterface as createInterface2 } from "node:readline/promises";
@@ -2991,7 +3061,7 @@ function pluginRoot() {
2991
3061
  const local = projectRoot();
2992
3062
  if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
2993
3063
  return local;
2994
- if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
3064
+ if (existsSync15(join17(local, "vault")) || existsSync15(join17(local, "CLAUDE.local.md")))
2995
3065
  return local;
2996
3066
  return registeredRoot() ?? local;
2997
3067
  }
@@ -3020,7 +3090,7 @@ function resolvePath(input, base = process.cwd(), home = homedir5()) {
3020
3090
  if (p === "~")
3021
3091
  p = home;
3022
3092
  else if (p.startsWith("~/"))
3023
- p = join16(home, p.slice(2));
3093
+ p = join17(home, p.slice(2));
3024
3094
  return resolve7(base, p);
3025
3095
  }
3026
3096
  function initPositional(args6) {
@@ -3029,7 +3099,7 @@ function initPositional(args6) {
3029
3099
  function physicalTarget(target) {
3030
3100
  let existing = target;
3031
3101
  const tail = [];
3032
- while (!existsSync14(existing)) {
3102
+ while (!existsSync15(existing)) {
3033
3103
  const parent = dirname5(existing);
3034
3104
  if (parent === existing)
3035
3105
  return target;
@@ -3037,7 +3107,7 @@ function physicalTarget(target) {
3037
3107
  existing = parent;
3038
3108
  }
3039
3109
  try {
3040
- return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
3110
+ return tail.length ? join17(realpathSync2(existing), ...tail) : realpathSync2(existing);
3041
3111
  } catch {
3042
3112
  return target;
3043
3113
  }
@@ -3073,7 +3143,7 @@ var init_cli = __esm(async () => {
3073
3143
  init_registry();
3074
3144
  init_launch();
3075
3145
  here = dirname5(fileURLToPath(import.meta.url));
3076
- pkgRoot = join16(here, "..");
3146
+ pkgRoot = join17(here, "..");
3077
3147
  [cmd, ...rest] = process.argv.slice(2);
3078
3148
  asImp = globalThis.__IMPRNT_IMP__ === true;
3079
3149
  if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
@@ -3103,12 +3173,12 @@ var init_cli = __esm(async () => {
3103
3173
  console.log(r);
3104
3174
  console.log("");
3105
3175
  }
3106
- const p = join16(vault5, "hot.md");
3107
- if (!existsSync14(p)) {
3176
+ const p = join17(vault5, "hot.md");
3177
+ if (!existsSync15(p)) {
3108
3178
  console.error(`no hot.md at ${p} — run \`imprnt init\``);
3109
3179
  process.exit(1);
3110
3180
  }
3111
- console.log(readFileSync14(p, "utf8"));
3181
+ console.log(readFileSync15(p, "utf8"));
3112
3182
  break;
3113
3183
  }
3114
3184
  case "lair": {
@@ -3120,12 +3190,12 @@ var init_cli = __esm(async () => {
3120
3190
  }
3121
3191
  case "context": {
3122
3192
  const home = requireVaultHome();
3123
- const contract = join16(home, "CLAUDE.md");
3124
- if (!existsSync14(contract)) {
3193
+ const contract = join17(home, "CLAUDE.md");
3194
+ if (!existsSync15(contract)) {
3125
3195
  console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
3126
3196
  process.exit(1);
3127
3197
  }
3128
- process.stdout.write(readFileSync14(contract, "utf8"));
3198
+ process.stdout.write(readFileSync15(contract, "utf8"));
3129
3199
  break;
3130
3200
  }
3131
3201
  case "agent": {
@@ -3324,7 +3394,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3324
3394
  process.exit(1);
3325
3395
  }
3326
3396
  case "global": {
3327
- const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
3397
+ const globalDir = process.env.CLAUDE_CONFIG_DIR || join17(homedir5(), ".claude");
3328
3398
  const [sub2, ...gargs] = rest;
3329
3399
  if (sub2 === "list") {
3330
3400
  const enabled = listGlobalModules(globalDir);
@@ -3359,8 +3429,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3359
3429
  }
3360
3430
  const name = names[0];
3361
3431
  const proj = pluginRoot();
3362
- const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
3363
- if (!existsSync14(srcDir)) {
3432
+ const srcDir = from ? resolve7(from) : join17(proj, "plugins", name);
3433
+ if (!existsSync15(srcDir)) {
3364
3434
  console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
3365
3435
  process.exit(1);
3366
3436
  }
@@ -3404,7 +3474,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3404
3474
  } else if (process.stdin.isTTY && process.stdout.isTTY) {
3405
3475
  const inPlace = isVaultProject(process.cwd());
3406
3476
  const display = inPlace ? process.cwd() : "~/imprnt";
3407
- const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
3477
+ const fallback = inPlace ? process.cwd() : join17(homedir5(), "imprnt");
3408
3478
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
3409
3479
  const answer = (await rl.question(`vault location [${display}]: `)).trim();
3410
3480
  rl.close();
@@ -3413,7 +3483,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3413
3483
  target = process.cwd();
3414
3484
  }
3415
3485
  const toCwd = target === process.cwd();
3416
- const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
3486
+ const rel = (sub2) => toCwd ? `./${sub2}` : join17(target, sub2);
3417
3487
  const lexicalEnclosing = projectRoot(target);
3418
3488
  const physical = physicalTarget(target);
3419
3489
  const physicalEnclosing = projectRoot(physical);
@@ -3426,12 +3496,12 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3426
3496
  const domains = ["identity", "health", "finances", "work", "life", "projects"];
3427
3497
  const forms = ["events", "mistakes"];
3428
3498
  const vaultDirs = [...entities, ...domains, ...forms];
3429
- const vaultPath = join16(target, "vault");
3430
- const vaultExisted = existsSync14(vaultPath);
3499
+ const vaultPath = join17(target, "vault");
3500
+ const vaultExisted = existsSync15(vaultPath);
3431
3501
  let createdDirs = 0;
3432
3502
  for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
3433
- const abs = d ? join16(target, d) : target;
3434
- if (!existsSync14(abs))
3503
+ const abs = d ? join17(target, d) : target;
3504
+ if (!existsSync15(abs))
3435
3505
  createdDirs++;
3436
3506
  try {
3437
3507
  mkdirSync6(abs, { recursive: true });
@@ -3446,25 +3516,25 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3446
3516
  };
3447
3517
  const added = [];
3448
3518
  for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
3449
- const dst = join16(vaultPath, f);
3519
+ const dst = join17(vaultPath, f);
3450
3520
  const slot = controlSlot(dst);
3451
3521
  if (slot.startsWith("blocked")) {
3452
3522
  console.error(blockedMsg(`vault/${f}`, slot));
3453
3523
  process.exit(1);
3454
3524
  }
3455
3525
  if (slot === "create") {
3456
- cpSync3(join16(pkgRoot, "templates", f), dst);
3526
+ cpSync3(join17(pkgRoot, "templates", f), dst);
3457
3527
  added.push(`vault/${f}`);
3458
3528
  }
3459
3529
  }
3460
- const claudeMd = join16(target, "CLAUDE.md");
3530
+ const claudeMd = join17(target, "CLAUDE.md");
3461
3531
  const cmSlot = controlSlot(claudeMd);
3462
3532
  if (cmSlot.startsWith("blocked")) {
3463
3533
  console.error(blockedMsg("CLAUDE.md", cmSlot));
3464
3534
  process.exit(1);
3465
3535
  }
3466
- if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
3467
- cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
3536
+ if (cmSlot === "create" && existsSync15(join17(pkgRoot, "CLAUDE.md"))) {
3537
+ cpSync3(join17(pkgRoot, "CLAUDE.md"), claudeMd);
3468
3538
  added.push("CLAUDE.md");
3469
3539
  }
3470
3540
  try {
@@ -3506,8 +3576,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3506
3576
  const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
3507
3577
  const bare = cmd === undefined;
3508
3578
  if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
3509
- const modScript = join16(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3510
- if (existsSync14(modScript)) {
3579
+ const modScript = join17(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3580
+ if (existsSync15(modScript)) {
3511
3581
  const proc = spawnSync5(process.execPath, [modScript, ...rest], { stdio: "inherit" });
3512
3582
  process.exit(proc.status ?? 1);
3513
3583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imprnt",
3
- "version": "0.1.4-edge.88",
3
+ "version": "0.1.4-edge.89",
4
4
  "stable": true,
5
5
  "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.",
6
6
  "type": "module",