imprnt 0.1.3 → 0.1.4-edge.88

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/README.md +52 -12
  2. package/dist/cli.js +387 -65
  3. package/dist/imp.js +387 -65
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1879,6 +1879,28 @@ var init_tags = __esm(() => {
1879
1879
  var exports_recall = {};
1880
1880
  import { lstatSync as lstatSync2, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "node:fs";
1881
1881
  import { basename as basename4, join as join12, relative as relative4 } from "node:path";
1882
+ function stemWord(w) {
1883
+ if (w.length <= 3)
1884
+ return w;
1885
+ if (/[a-z]$/.test(w) === false)
1886
+ return w;
1887
+ if (w.endsWith("sses"))
1888
+ return w.slice(0, -2);
1889
+ if (w.endsWith("ies") && w.length > 4)
1890
+ return w.slice(0, -3) + "y";
1891
+ if (w.endsWith("ss") || w.endsWith("us") || w.endsWith("is")) {} else if (w.endsWith("s") && !w.endsWith("ous"))
1892
+ w = w.slice(0, -1);
1893
+ if (w.length > 4 && w.endsWith("ing")) {
1894
+ const base = w.slice(0, -3);
1895
+ if (base.length >= 3)
1896
+ w = /([bdfglmnprt])\1$/.test(base) ? base.slice(0, -1) : base;
1897
+ } else if (w.length > 3 && w.endsWith("ed")) {
1898
+ const base = w.slice(0, -2);
1899
+ if (base.length >= 3)
1900
+ w = /([bdfglmnprt])\1$/.test(base) ? base.slice(0, -1) : base;
1901
+ }
1902
+ return w;
1903
+ }
1882
1904
  function walk2(dir2) {
1883
1905
  const out = [];
1884
1906
  for (const entry of readdirSync6(dir2)) {
@@ -1902,7 +1924,53 @@ function walk2(dir2) {
1902
1924
  }
1903
1925
  return out;
1904
1926
  }
1905
- var args2, vault2, limit = 15, positional2, query, vocab, MAX_SYNONYM_NGRAM, STOPWORDS, tokenize = (text2) => text2.normalize("NFC").toLowerCase().replace(/['’]/gu, "").split(/[^\p{L}\p{N}]+/u).filter(Boolean), phraseSynonymTokens = (q) => {
1927
+ function bestPassages(absPath, n) {
1928
+ let raw = "";
1929
+ try {
1930
+ raw = stripBom(readFileSync11(absPath, "utf8"));
1931
+ } catch {
1932
+ return [];
1933
+ }
1934
+ const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1935
+ const body2 = fmMatch ? raw.slice(fmMatch.index + fmMatch[0].length) : raw;
1936
+ const paras = body2.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
1937
+ if (paras.length <= n)
1938
+ return paras;
1939
+ const scoredParas = paras.map((p) => {
1940
+ const tf = new Map;
1941
+ for (const t of tokenize(p))
1942
+ tf.set(t, (tf.get(t) ?? 0) + 1);
1943
+ let plen = 0;
1944
+ for (const c of tf.values())
1945
+ plen += c;
1946
+ let sc = 0;
1947
+ const used = new Set;
1948
+ for (const group of scoringGroups) {
1949
+ let best = 0, bv = "";
1950
+ for (const v of group) {
1951
+ if (used.has(v))
1952
+ continue;
1953
+ const x = bm25Term(tf.get(v) ?? 0, plen, variantIdf.get(v) ?? 0);
1954
+ if (x > best) {
1955
+ best = x;
1956
+ bv = v;
1957
+ }
1958
+ }
1959
+ if (best > 0) {
1960
+ sc += best;
1961
+ used.add(bv);
1962
+ }
1963
+ }
1964
+ return { p, sc };
1965
+ });
1966
+ const keep = new Set(scoredParas.slice().sort((a, b) => b.sc - a.sc).slice(0, n).filter((x) => x.sc > 0).map((x) => x.p));
1967
+ const out = paras.filter((p) => keep.has(p));
1968
+ return out.length ? out : paras.slice(0, n);
1969
+ }
1970
+ var args2, vault2, limit = 15, gap = 0, passages = 0, proximity = false, coverage = false, stem = false, positional2, query, vocab, MAX_SYNONYM_NGRAM, STOPWORDS, tokenize = (text2) => {
1971
+ const raw = text2.normalize("NFC").toLowerCase().replace(/['’]/gu, "").split(/[^\p{L}\p{N}]+/u).filter(Boolean);
1972
+ return stem ? raw.map(stemWord) : raw;
1973
+ }, phraseSynonymTokens = (q) => {
1906
1974
  const out = [];
1907
1975
  const words = q.normalize("NFC").toLowerCase().split(/[\s-]+/).map((w) => w.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "")).filter(Boolean);
1908
1976
  for (let n = Math.min(words.length, MAX_SYNONYM_NGRAM);n >= 2; n--) {
@@ -1916,14 +1984,14 @@ var args2, vault2, limit = 15, positional2, query, vocab, MAX_SYNONYM_NGRAM, STO
1916
1984
  }
1917
1985
  }
1918
1986
  return out;
1919
- }, rawTerms, contentTerms, baseTerms, queryTerms, CONTROL2, files, TITLE_BOOST = 3, TAG_BOOST = 2, SUMMARY_BOOST = 1, BODY_BOOST = 1, docs, df, N, avgdl, K1 = 1.5, B = 0.75, idf = (term) => {
1987
+ }, rawTerms, contentTerms, baseTerms, queryTerms, queryVariantSet, CONTROL2, files, TITLE_BOOST = 3, TAG_BOOST = 2, SUMMARY_BOOST = 1, BODY_BOOST = 1, docs, df, N, avgdl, K1 = 1.5, B = 0.75, PROX_WINDOW = 25, PROX_WEIGHT = 0.35, idf = (term) => {
1920
1988
  const n = df.get(term) ?? 0;
1921
1989
  return Math.max(0, Math.log(1 + (N - n + 0.5) / (n + 0.5)));
1922
1990
  }, bm25Term = (tf, dl, termIdf) => {
1923
1991
  if (tf <= 0)
1924
1992
  return 0;
1925
1993
  return termIdf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (dl / avgdl)));
1926
- }, variantIdf, hits, scoringGroups, shown, expanded;
1994
+ }, variantIdf, hits, scoringGroups, cut, shown, expanded;
1927
1995
  var init_recall = __esm(() => {
1928
1996
  init_tags();
1929
1997
  init_moc();
@@ -1950,12 +2018,42 @@ var init_recall = __esm(() => {
1950
2018
  process.exit(1);
1951
2019
  }
1952
2020
  limit = n;
2021
+ } else if (args2[i] === "--proximity") {
2022
+ proximity = true;
2023
+ } else if (args2[i] === "--coverage") {
2024
+ coverage = true;
2025
+ } else if (args2[i] === "--stem") {
2026
+ stem = true;
2027
+ } else if (args2[i] === "--passages") {
2028
+ const tok = args2[++i];
2029
+ if (tok === undefined || !/^[0-9]+$/.test(tok)) {
2030
+ console.error("--passages must be a positive integer");
2031
+ process.exit(1);
2032
+ }
2033
+ const n = parseInt(tok, 10);
2034
+ if (!Number.isFinite(n) || n <= 0) {
2035
+ console.error("--passages must be a positive integer");
2036
+ process.exit(1);
2037
+ }
2038
+ passages = n;
2039
+ } else if (args2[i] === "--gap") {
2040
+ const tok = args2[++i];
2041
+ if (tok === undefined || !/^0?\.[0-9]+$/.test(tok)) {
2042
+ console.error("--gap must be a ratio between 0 and 1 (e.g. 0.25)");
2043
+ process.exit(1);
2044
+ }
2045
+ const g = parseFloat(tok);
2046
+ if (!Number.isFinite(g) || g <= 0 || g >= 1) {
2047
+ console.error("--gap must be a ratio between 0 and 1 (e.g. 0.25)");
2048
+ process.exit(1);
2049
+ }
2050
+ gap = g;
1953
2051
  } else
1954
2052
  positional2.push(args2[i]);
1955
2053
  }
1956
2054
  query = positional2.join(" ").trim();
1957
2055
  if (!query) {
1958
- console.error('usage: imprnt recall "<query>" [--vault DIR] [--limit N]');
2056
+ console.error('usage: imprnt recall "<query>" [--vault DIR] [--limit N] [--gap R] [--passages N] [--proximity] [--coverage] [--stem]');
1959
2057
  process.exit(1);
1960
2058
  }
1961
2059
  vocab = loadTags(vault2);
@@ -2063,6 +2161,7 @@ var init_recall = __esm(() => {
2063
2161
  contentTerms = rawTerms.filter((w) => !STOPWORDS.has(w));
2064
2162
  baseTerms = contentTerms.length ? contentTerms : rawTerms;
2065
2163
  queryTerms = baseTerms.map((w) => [...new Set([w, ...tokenize(normalize(vocab, w))])]);
2164
+ queryVariantSet = new Set(queryTerms.flat());
2066
2165
  for (const t of phraseSynonymTokens(query)) {
2067
2166
  if (!STOPWORDS.has(t))
2068
2167
  queryTerms.push([t]);
@@ -2099,11 +2198,26 @@ var init_recall = __esm(() => {
2099
2198
  add(tokenize(aliases), TITLE_BOOST);
2100
2199
  add(tags.flatMap(tokenize), TAG_BOOST);
2101
2200
  add(tokenize(summary), SUMMARY_BOOST);
2102
- add(tokenize(body2), BODY_BOOST);
2201
+ const bodyTokens = tokenize(body2);
2202
+ add(bodyTokens, BODY_BOOST);
2203
+ let pos;
2204
+ if (proximity) {
2205
+ pos = new Map;
2206
+ for (let bi = 0;bi < bodyTokens.length; bi++) {
2207
+ const t = bodyTokens[bi];
2208
+ if (!queryVariantSet.has(t))
2209
+ continue;
2210
+ const arr = pos.get(t);
2211
+ if (arr)
2212
+ arr.push(bi);
2213
+ else
2214
+ pos.set(t, [bi]);
2215
+ }
2216
+ }
2103
2217
  let len = 0;
2104
2218
  for (const c of tf.values())
2105
2219
  len += c;
2106
- docs.push({ path, tf, len });
2220
+ docs.push({ path, tf, len, pos });
2107
2221
  for (const term of tf.keys())
2108
2222
  df.set(term, (df.get(term) ?? 0) + 1);
2109
2223
  }
@@ -2136,23 +2250,69 @@ var init_recall = __esm(() => {
2136
2250
  scored.add(bestVariant);
2137
2251
  }
2138
2252
  }
2139
- if (score > 0)
2253
+ if (score > 0) {
2254
+ if (coverage && scoringGroups.length > 1) {
2255
+ score *= 0.5 + 0.5 * (scored.size / scoringGroups.length);
2256
+ }
2257
+ if (proximity && d.pos && scored.size > 1) {
2258
+ const present = [...scored].filter((t) => d.pos.has(t));
2259
+ let bonus = 0;
2260
+ for (let x = 0;x < present.length; x++) {
2261
+ for (let y = x + 1;y < present.length; y++) {
2262
+ const a = d.pos.get(present[x]), b = d.pos.get(present[y]);
2263
+ let best = Infinity;
2264
+ for (let ia = 0, ib = 0;ia < a.length && ib < b.length; ) {
2265
+ const dist = Math.abs(a[ia] - b[ib]);
2266
+ if (dist < best)
2267
+ best = dist;
2268
+ if (a[ia] < b[ib])
2269
+ ia++;
2270
+ else
2271
+ ib++;
2272
+ }
2273
+ if (best < PROX_WINDOW) {
2274
+ const w = Math.min(variantIdf.get(present[x]) ?? 0, variantIdf.get(present[y]) ?? 0);
2275
+ bonus += PROX_WEIGHT * w * (1 - best / PROX_WINDOW);
2276
+ }
2277
+ }
2278
+ }
2279
+ score += bonus;
2280
+ }
2140
2281
  hits.push({ path: relative4(vault2, d.path), score: Math.round(score * 100) / 100 });
2282
+ }
2141
2283
  }
2142
2284
  hits.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
2143
2285
  if (!hits.length) {
2144
2286
  console.log(`no matches for "${query}" in ${vault2}`);
2145
2287
  process.exit(0);
2146
2288
  }
2147
- shown = hits.slice(0, limit);
2289
+ cut = Math.min(limit, hits.length);
2290
+ if (gap > 0) {
2291
+ for (let i = 1;i < cut; i++) {
2292
+ if (hits[i].score < gap * hits[i - 1].score) {
2293
+ cut = i;
2294
+ break;
2295
+ }
2296
+ }
2297
+ }
2298
+ shown = hits.slice(0, cut);
2148
2299
  expanded = queryTerms.map((g) => g.join("|")).join(" ");
2149
- console.log(`recall "${query}" [${expanded}] — ${hits.length} match(es)${hits.length > shown.length ? `, showing top ${shown.length}` : ""}, BM25-ranked:
2300
+ console.log(`recall "${query}" [${expanded}] - ${hits.length} match(es)${hits.length > shown.length ? `, showing top ${shown.length}` : ""}, BM25-ranked:
2150
2301
  `);
2151
- for (const h of shown)
2302
+ for (const h of shown) {
2152
2303
  console.log(` [${h.score.toFixed(2)}] ${h.path}`);
2304
+ if (passages > 0) {
2305
+ for (const p of bestPassages(join12(vault2, h.path), passages)) {
2306
+ for (const line of p.split(`
2307
+ `))
2308
+ console.log(` ${line}`);
2309
+ console.log("");
2310
+ }
2311
+ }
2312
+ }
2153
2313
  if (hits.length > shown.length) {
2154
2314
  console.log(`
2155
- … ${hits.length - shown.length} lower-ranked hit(s) hidden. Raise with --limit if needed; usually you don't.`);
2315
+ ... ${hits.length - shown.length} lower-ranked hit(s) hidden. Raise with --limit if needed. Usually you do not.`);
2156
2316
  }
2157
2317
  });
2158
2318
 
@@ -2261,11 +2421,11 @@ var init_snapshot = __esm(() => {
2261
2421
  continue;
2262
2422
  }
2263
2423
  const ext = extname2(rel);
2264
- const stem = rel.slice(0, rel.length - ext.length);
2265
- let relD = `${stem}-${hash2.slice(0, 8)}${ext}`;
2424
+ const stem2 = rel.slice(0, rel.length - ext.length);
2425
+ let relD = `${stem2}-${hash2.slice(0, 8)}${ext}`;
2266
2426
  let n = 2;
2267
2427
  while (existsSync11(join13(destRoot, relD)) && Buffer.compare(readFileSync12(join13(destRoot, relD)), srcBytes2) !== 0) {
2268
- relD = `${stem}-${hash2.slice(0, 8)}-${n}${ext}`;
2428
+ relD = `${stem2}-${hash2.slice(0, 8)}-${n}${ext}`;
2269
2429
  n++;
2270
2430
  }
2271
2431
  rawPath2 = join13(destRoot, relD);
@@ -2661,19 +2821,177 @@ all plugin checks passed.`);
2661
2821
  }
2662
2822
  });
2663
2823
 
2664
- // scripts/cli.ts
2665
- var exports_cli = {};
2666
- 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";
2824
+ // scripts/vault.ts
2825
+ var exports_vault = {};
2667
2826
  import { spawnSync as spawnSync4 } from "node:child_process";
2668
- import { join as join15, dirname as dirname4, resolve as resolve6, basename as basename6 } from "node:path";
2827
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
2669
2828
  import { homedir as homedir4 } from "node:os";
2829
+ import { basename as basename6, dirname as dirname4, join as join15, resolve as resolve6 } from "node:path";
2830
+ function noteCount(project) {
2831
+ const root = join15(project, "vault");
2832
+ if (!existsSync13(root))
2833
+ return 0;
2834
+ let n = 0;
2835
+ const walk3 = (dir2, atRoot) => {
2836
+ for (const e of readdirSync9(dir2, { withFileTypes: true })) {
2837
+ if (e.name.startsWith(".") || e.name.startsWith("_"))
2838
+ continue;
2839
+ if (e.isSymbolicLink())
2840
+ continue;
2841
+ if (atRoot && CONTROL3.has(e.name))
2842
+ continue;
2843
+ if (e.isDirectory())
2844
+ walk3(join15(dir2, e.name), false);
2845
+ else if (e.name.endsWith(".md"))
2846
+ n++;
2847
+ }
2848
+ };
2849
+ try {
2850
+ walk3(root, true);
2851
+ } catch {}
2852
+ return n;
2853
+ }
2854
+ function resolveTarget(token) {
2855
+ const reg = readRegistry();
2856
+ if (token) {
2857
+ if (reg.vaults[token])
2858
+ return { name: token, path: reg.vaults[token] };
2859
+ const p = expand(token);
2860
+ if (isVaultProject(p)) {
2861
+ const named = Object.entries(reg.vaults).find(([, v]) => v === p)?.[0];
2862
+ return { name: named ?? basename6(p), path: p };
2863
+ }
2864
+ console.error(`no vault called "${token}", and ${p} is not a vault project`);
2865
+ console.error(`known: ${Object.keys(reg.vaults).join(", ") || "(none registered — run `imprnt init`)"}`);
2866
+ process.exit(1);
2867
+ }
2868
+ const name = reg.default ?? Object.keys(reg.vaults)[0];
2869
+ const path = name ? reg.vaults[name] : undefined;
2870
+ if (!name || !path) {
2871
+ console.error("no vault registered — run `imprnt init`");
2872
+ process.exit(1);
2873
+ }
2874
+ return { name, path };
2875
+ }
2876
+ function tar(...a) {
2877
+ const r = spawnSync4("tar", a, { stdio: ["ignore", "inherit", "inherit"] });
2878
+ if (r.error) {
2879
+ console.error(`tar is not available: ${r.error.message}`);
2880
+ process.exit(1);
2881
+ }
2882
+ return r.status ?? 1;
2883
+ }
2884
+ var args5, sub, flag = (name) => {
2885
+ const i = args5.indexOf(name);
2886
+ return i >= 0 ? args5[i + 1] : undefined;
2887
+ }, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join15(homedir4(), p.slice(2)) : p), CONTROL3;
2888
+ var init_vault = __esm(() => {
2889
+ init_registry();
2890
+ args5 = process.argv.slice(2);
2891
+ sub = args5[0];
2892
+ positionals = args5.slice(1).filter((a, i, all2) => !a.startsWith("--") && !(i > 0 && all2[i - 1]?.startsWith("--")));
2893
+ CONTROL3 = new Set(["index.md", "hot.md", "log.md", "_tags.md"]);
2894
+ switch (sub) {
2895
+ case "list": {
2896
+ const reg = readRegistry();
2897
+ const names = Object.keys(reg.vaults);
2898
+ if (!names.length) {
2899
+ console.log("no vaults registered — run `imprnt init`");
2900
+ break;
2901
+ }
2902
+ const def = reg.default ?? names[0];
2903
+ for (const n of names) {
2904
+ const p = reg.vaults[n];
2905
+ const live = isVaultProject(p);
2906
+ const mark = n === def ? "*" : " ";
2907
+ const detail = live ? `${noteCount(p)} notes` : "MISSING — the path is registered but holds no vault";
2908
+ console.log(`${mark} ${n.padEnd(12)} ${p} (${detail})`);
2909
+ }
2910
+ console.log("\n* = default (what `imp` and `imprnt context` resolve to)");
2911
+ break;
2912
+ }
2913
+ case "archive": {
2914
+ const { name, path } = resolveTarget(positionals[0]);
2915
+ if (!isVaultProject(path)) {
2916
+ console.error(`${path} is not a vault project`);
2917
+ process.exit(1);
2918
+ }
2919
+ const outDir = expand(flag("--out") ?? ".");
2920
+ mkdirSync5(outDir, { recursive: true });
2921
+ const stamp = new Date().toISOString().slice(0, 10);
2922
+ const out = join15(outDir, `${name}-${stamp}.tar.gz`);
2923
+ if (tar("czf", out, "-C", dirname4(path), basename6(path)) !== 0) {
2924
+ console.error("archive failed");
2925
+ process.exit(1);
2926
+ }
2927
+ if (tar("tzf", out) !== 0) {
2928
+ console.error(`${out} does not read back — treat it as no archive at all`);
2929
+ process.exit(1);
2930
+ }
2931
+ const size = (statSync9(out).size / 1024 / 1024).toFixed(1);
2932
+ console.log(`archived ${name} (${noteCount(path)} notes) → ${out} ${size} MB, verified`);
2933
+ console.log(`restore with: imprnt vault restore ${out} <dir>`);
2934
+ break;
2935
+ }
2936
+ case "restore": {
2937
+ const src3 = expand(positionals[0] ?? "");
2938
+ const dest2 = expand(positionals[1] ?? "");
2939
+ if (!positionals[0] || !positionals[1]) {
2940
+ console.error("usage: imprnt vault restore <archive.tar.gz> <dir>");
2941
+ process.exit(1);
2942
+ }
2943
+ if (!existsSync13(src3)) {
2944
+ console.error(`no archive at ${src3}`);
2945
+ process.exit(1);
2946
+ }
2947
+ if (existsSync13(dest2) && readdirSync9(dest2).length) {
2948
+ console.error(`${dest2} is not empty — restore into a new directory, then move it`);
2949
+ process.exit(1);
2950
+ }
2951
+ mkdirSync5(dest2, { recursive: true });
2952
+ if (tar("xzf", src3, "-C", dest2) !== 0) {
2953
+ console.error("restore failed");
2954
+ process.exit(1);
2955
+ }
2956
+ const inner = readdirSync9(dest2).map((e) => join15(dest2, e)).filter((p) => statSync9(p).isDirectory());
2957
+ const project = inner.find((p) => isVaultProject(p)) ?? (isVaultProject(dest2) ? dest2 : undefined);
2958
+ if (!project) {
2959
+ console.error(`extracted, but ${dest2} holds no vault project — check the archive`);
2960
+ process.exit(1);
2961
+ }
2962
+ console.log(`restored ${noteCount(project)} notes → ${project}`);
2963
+ console.log(`register it with: cd ${project} && imprnt init`);
2964
+ break;
2965
+ }
2966
+ default:
2967
+ console.log(`imprnt vault — the vault as its own object
2968
+
2969
+ imprnt vault list vaults registered on this machine
2970
+ imprnt vault archive [name|path] one verified tarball (--out <dir>)
2971
+ imprnt vault restore <archive> <dir> put one back
2972
+
2973
+ Create one with \`imprnt init\`. There is no \`vault delete\`: removing knowledge is a
2974
+ human act with rm and a path, not a subcommand a script or an agent can reach.
2975
+
2976
+ A vault means something with no agent attached. An agent with no vault is a process
2977
+ pointed at nothing — so the vault outlives whatever reads it.`);
2978
+ process.exit(sub && sub !== "help" && sub !== "--help" ? 1 : 0);
2979
+ }
2980
+ });
2981
+
2982
+ // scripts/cli.ts
2983
+ 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";
2985
+ 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";
2987
+ import { homedir as homedir5 } from "node:os";
2670
2988
  import { fileURLToPath } from "node:url";
2671
2989
  import { createInterface as createInterface2 } from "node:readline/promises";
2672
2990
  function pluginRoot() {
2673
2991
  const local = projectRoot();
2674
2992
  if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
2675
2993
  return local;
2676
- if (existsSync13(join15(local, "vault")) || existsSync13(join15(local, "CLAUDE.local.md")))
2994
+ if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
2677
2995
  return local;
2678
2996
  return registeredRoot() ?? local;
2679
2997
  }
@@ -2697,29 +3015,29 @@ function requireVaultHome() {
2697
3015
  }
2698
3016
  return home;
2699
3017
  }
2700
- function resolvePath(input, base = process.cwd(), home = homedir4()) {
3018
+ function resolvePath(input, base = process.cwd(), home = homedir5()) {
2701
3019
  let p = input;
2702
3020
  if (p === "~")
2703
3021
  p = home;
2704
3022
  else if (p.startsWith("~/"))
2705
- p = join15(home, p.slice(2));
2706
- return resolve6(base, p);
3023
+ p = join16(home, p.slice(2));
3024
+ return resolve7(base, p);
2707
3025
  }
2708
- function initPositional(args5) {
2709
- return args5.find((a) => !a.startsWith("-"));
3026
+ function initPositional(args6) {
3027
+ return args6.find((a) => !a.startsWith("-"));
2710
3028
  }
2711
3029
  function physicalTarget(target) {
2712
3030
  let existing = target;
2713
3031
  const tail = [];
2714
- while (!existsSync13(existing)) {
2715
- const parent = dirname4(existing);
3032
+ while (!existsSync14(existing)) {
3033
+ const parent = dirname5(existing);
2716
3034
  if (parent === existing)
2717
3035
  return target;
2718
- tail.unshift(basename6(existing));
3036
+ tail.unshift(basename7(existing));
2719
3037
  existing = parent;
2720
3038
  }
2721
3039
  try {
2722
- return tail.length ? join15(realpathSync2(existing), ...tail) : realpathSync2(existing);
3040
+ return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
2723
3041
  } catch {
2724
3042
  return target;
2725
3043
  }
@@ -2735,7 +3053,7 @@ function controlSlot(dst) {
2735
3053
  return "blocked-directory";
2736
3054
  if (st.isSymbolicLink()) {
2737
3055
  try {
2738
- return statSync9(dst).isFile() ? "keep" : "blocked-symlink";
3056
+ return statSync10(dst).isFile() ? "keep" : "blocked-symlink";
2739
3057
  } catch {
2740
3058
  return "blocked-symlink";
2741
3059
  }
@@ -2754,11 +3072,11 @@ var init_cli = __esm(async () => {
2754
3072
  init_moc();
2755
3073
  init_registry();
2756
3074
  init_launch();
2757
- here = dirname4(fileURLToPath(import.meta.url));
2758
- pkgRoot = join15(here, "..");
3075
+ here = dirname5(fileURLToPath(import.meta.url));
3076
+ pkgRoot = join16(here, "..");
2759
3077
  [cmd, ...rest] = process.argv.slice(2);
2760
3078
  asImp = globalThis.__IMPRNT_IMP__ === true;
2761
- if (["ingest", "recall", "snapshot", "check"].includes(cmd))
3079
+ if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
2762
3080
  process.argv.splice(2, 1);
2763
3081
  switch (cmd) {
2764
3082
  case "ingest":
@@ -2773,6 +3091,9 @@ var init_cli = __esm(async () => {
2773
3091
  case "check":
2774
3092
  await Promise.resolve().then(() => (init_check(), exports_check));
2775
3093
  break;
3094
+ case "vault":
3095
+ await Promise.resolve().then(() => (init_vault(), exports_vault));
3096
+ break;
2776
3097
  case "hot": {
2777
3098
  const vault5 = vaultArg();
2778
3099
  const review2 = openNeedsReview(vault5);
@@ -2782,8 +3103,8 @@ var init_cli = __esm(async () => {
2782
3103
  console.log(r);
2783
3104
  console.log("");
2784
3105
  }
2785
- const p = join15(vault5, "hot.md");
2786
- if (!existsSync13(p)) {
3106
+ const p = join16(vault5, "hot.md");
3107
+ if (!existsSync14(p)) {
2787
3108
  console.error(`no hot.md at ${p} — run \`imprnt init\``);
2788
3109
  process.exit(1);
2789
3110
  }
@@ -2799,8 +3120,8 @@ var init_cli = __esm(async () => {
2799
3120
  }
2800
3121
  case "context": {
2801
3122
  const home = requireVaultHome();
2802
- const contract = join15(home, "CLAUDE.md");
2803
- if (!existsSync13(contract)) {
3123
+ const contract = join16(home, "CLAUDE.md");
3124
+ if (!existsSync14(contract)) {
2804
3125
  console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
2805
3126
  process.exit(1);
2806
3127
  }
@@ -2883,8 +3204,8 @@ var init_cli = __esm(async () => {
2883
3204
  const proj = pluginRoot();
2884
3205
  if (proj !== process.cwd())
2885
3206
  console.error(`(targeting vault project: ${proj})`);
2886
- const [sub, ...specs] = rest;
2887
- if (sub === "list") {
3207
+ const [sub2, ...specs] = rest;
3208
+ if (sub2 === "list") {
2888
3209
  const dirs = listPluginDirs(proj);
2889
3210
  console.log("plugins:");
2890
3211
  if (!dirs.length)
@@ -2899,7 +3220,7 @@ available to add: ${available.join(", ")}`);
2899
3220
  enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
2900
3221
  break;
2901
3222
  }
2902
- if (sub === "add") {
3223
+ if (sub2 === "add") {
2903
3224
  let from;
2904
3225
  let force = false;
2905
3226
  const names = [];
@@ -2963,7 +3284,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
2963
3284
  process.exit(1);
2964
3285
  break;
2965
3286
  }
2966
- if (sub === "rm") {
3287
+ if (sub2 === "rm") {
2967
3288
  let purge = false;
2968
3289
  const names = [];
2969
3290
  for (const s of specs) {
@@ -3003,9 +3324,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3003
3324
  process.exit(1);
3004
3325
  }
3005
3326
  case "global": {
3006
- const globalDir = process.env.CLAUDE_CONFIG_DIR || join15(homedir4(), ".claude");
3007
- const [sub, ...gargs] = rest;
3008
- if (sub === "list") {
3327
+ const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
3328
+ const [sub2, ...gargs] = rest;
3329
+ if (sub2 === "list") {
3009
3330
  const enabled = listGlobalModules(globalDir);
3010
3331
  const onDisk = installedGlobalDirs(globalDir);
3011
3332
  console.log(`global modules (in ${globalDir}/imprnt/):`);
@@ -3019,7 +3340,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3019
3340
  console.log("\nthese load in every imp session (not a plain `claude`). enable: imprnt global add <name> disable: imprnt global rm <name> [--purge]");
3020
3341
  break;
3021
3342
  }
3022
- if (sub === "add") {
3343
+ if (sub2 === "add") {
3023
3344
  let from;
3024
3345
  const names = [];
3025
3346
  for (let i = 0;i < gargs.length; i++) {
@@ -3038,8 +3359,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3038
3359
  }
3039
3360
  const name = names[0];
3040
3361
  const proj = pluginRoot();
3041
- const srcDir = from ? resolve6(from) : join15(proj, "plugins", name);
3042
- if (!existsSync13(srcDir)) {
3362
+ const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
3363
+ if (!existsSync14(srcDir)) {
3043
3364
  console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
3044
3365
  process.exit(1);
3045
3366
  }
@@ -3051,7 +3372,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3051
3372
  console.log(r.changed ? `enabled ${name} globally → loads in every imp session (copied to ${globalDir}/imprnt/${name}/)` : `${name} already enabled globally (refreshed the copy)`);
3052
3373
  break;
3053
3374
  }
3054
- if (sub === "rm") {
3375
+ if (sub2 === "rm") {
3055
3376
  let purge = false;
3056
3377
  const names = [];
3057
3378
  for (const a of gargs) {
@@ -3083,7 +3404,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3083
3404
  } else if (process.stdin.isTTY && process.stdout.isTTY) {
3084
3405
  const inPlace = isVaultProject(process.cwd());
3085
3406
  const display = inPlace ? process.cwd() : "~/imprnt";
3086
- const fallback = inPlace ? process.cwd() : join15(homedir4(), "imprnt");
3407
+ const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
3087
3408
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
3088
3409
  const answer = (await rl.question(`vault location [${display}]: `)).trim();
3089
3410
  rl.close();
@@ -3092,7 +3413,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3092
3413
  target = process.cwd();
3093
3414
  }
3094
3415
  const toCwd = target === process.cwd();
3095
- const rel = (sub) => toCwd ? `./${sub}` : join15(target, sub);
3416
+ const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
3096
3417
  const lexicalEnclosing = projectRoot(target);
3097
3418
  const physical = physicalTarget(target);
3098
3419
  const physicalEnclosing = projectRoot(physical);
@@ -3105,15 +3426,15 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3105
3426
  const domains = ["identity", "health", "finances", "work", "life", "projects"];
3106
3427
  const forms = ["events", "mistakes"];
3107
3428
  const vaultDirs = [...entities, ...domains, ...forms];
3108
- const vaultPath = join15(target, "vault");
3109
- const vaultExisted = existsSync13(vaultPath);
3429
+ const vaultPath = join16(target, "vault");
3430
+ const vaultExisted = existsSync14(vaultPath);
3110
3431
  let createdDirs = 0;
3111
3432
  for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
3112
- const abs = d ? join15(target, d) : target;
3113
- if (!existsSync13(abs))
3433
+ const abs = d ? join16(target, d) : target;
3434
+ if (!existsSync14(abs))
3114
3435
  createdDirs++;
3115
3436
  try {
3116
- mkdirSync5(abs, { recursive: true });
3437
+ mkdirSync6(abs, { recursive: true });
3117
3438
  } catch (e) {
3118
3439
  console.error(`cannot create ${d ? rel(d) : target}: ${e instanceof Error ? e.message : String(e)}`);
3119
3440
  process.exit(1);
@@ -3125,29 +3446,29 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3125
3446
  };
3126
3447
  const added = [];
3127
3448
  for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
3128
- const dst = join15(vaultPath, f);
3449
+ const dst = join16(vaultPath, f);
3129
3450
  const slot = controlSlot(dst);
3130
3451
  if (slot.startsWith("blocked")) {
3131
3452
  console.error(blockedMsg(`vault/${f}`, slot));
3132
3453
  process.exit(1);
3133
3454
  }
3134
3455
  if (slot === "create") {
3135
- cpSync3(join15(pkgRoot, "templates", f), dst);
3456
+ cpSync3(join16(pkgRoot, "templates", f), dst);
3136
3457
  added.push(`vault/${f}`);
3137
3458
  }
3138
3459
  }
3139
- const claudeMd = join15(target, "CLAUDE.md");
3460
+ const claudeMd = join16(target, "CLAUDE.md");
3140
3461
  const cmSlot = controlSlot(claudeMd);
3141
3462
  if (cmSlot.startsWith("blocked")) {
3142
3463
  console.error(blockedMsg("CLAUDE.md", cmSlot));
3143
3464
  process.exit(1);
3144
3465
  }
3145
- if (cmSlot === "create" && existsSync13(join15(pkgRoot, "CLAUDE.md"))) {
3146
- cpSync3(join15(pkgRoot, "CLAUDE.md"), claudeMd);
3466
+ if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
3467
+ cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
3147
3468
  added.push("CLAUDE.md");
3148
3469
  }
3149
3470
  try {
3150
- const prevMode = statSync9(target).mode & 511;
3471
+ const prevMode = statSync10(target).mode & 511;
3151
3472
  chmodSync(target, 448);
3152
3473
  if (prevMode !== 448)
3153
3474
  console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
@@ -3169,8 +3490,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3169
3490
  console.log(` + ${rel("raw")} for immutable by-source snapshots`);
3170
3491
  console.log("next: type `imp` to talk, or ingest a source (`imprnt ingest <file>`), then `imprnt check`.");
3171
3492
  } else {
3172
- const noteCount = collectNotes(vaultPath).length;
3173
- console.log(`found existing vault at ${rel("vault")} — ${noteCount} note${noteCount === 1 ? "" : "s"}, left untouched`);
3493
+ const noteCount2 = collectNotes(vaultPath).length;
3494
+ console.log(`found existing vault at ${rel("vault")} — ${noteCount2} note${noteCount2 === 1 ? "" : "s"}, left untouched`);
3174
3495
  if (added.length)
3175
3496
  console.log(` added missing control file${added.length === 1 ? "" : "s"}: ${added.join(", ")}`);
3176
3497
  else if (createdDirs)
@@ -3185,9 +3506,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
3185
3506
  const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
3186
3507
  const bare = cmd === undefined;
3187
3508
  if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
3188
- const modScript = join15(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3189
- if (existsSync13(modScript)) {
3190
- const proc = spawnSync4(process.execPath, [modScript, ...rest], { stdio: "inherit" });
3509
+ const modScript = join16(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3510
+ if (existsSync14(modScript)) {
3511
+ const proc = spawnSync5(process.execPath, [modScript, ...rest], { stdio: "inherit" });
3191
3512
  process.exit(proc.status ?? 1);
3192
3513
  }
3193
3514
  }
@@ -3230,6 +3551,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
3230
3551
  imprnt global add <name> [--from D] enable a behavior module (e.g. anti-slop) in EVERY imp session (imp injects it; ~/.claude/CLAUDE.md is never touched); bare name promotes a project plugin
3231
3552
  imprnt global rm <name> [--purge] disable a global module; --purge deletes the global copy
3232
3553
  imprnt global list show globally-enabled modules
3554
+ imprnt vault list|archive|restore the vault as its own object — outlives any agent
3233
3555
  imprnt <module> <command> [...] run an installed module's command (e.g. \`imprnt session-host login\`, \`imprnt kleinanzeigen sync\`) — no \`node\` paths
3234
3556
 
3235
3557
  layout: entities (people · orgs · holdings) · domains (identity · health · finances · work · life · projects) · forms (events · mistakes)