rigjs 4.0.14 → 4.0.15

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.
@@ -7,7 +7,7 @@ interface IndexOpts { force?: boolean; }
7
7
  export default async function wikiIndex(opts: IndexOpts): Promise<void> {
8
8
  const t = requireVault();
9
9
  print.start(`qmd embed: ${t.name}`);
10
- const res = await qmdEmbed(t.name, t.path, { force: !!opts.force });
10
+ const res = await qmdEmbed(t.name, t.path, t.root, { force: !!opts.force });
11
11
  if (res.ok) print.succeed(`qmd embed: ${t.name} done`);
12
12
  else {
13
13
  print.error(`qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
@@ -112,7 +112,7 @@ export default async function wikiIngest(source: string, opts: IngestOpts): Prom
112
112
 
113
113
  // Real ingest — trigger incremental embed.
114
114
  print.info(`applied ${applied.length} file change${applied.length === 1 ? '' : 's'}; rejected ${rejected.length}.`);
115
- const embedRes = await qmdEmbed(target.name, target.path);
115
+ const embedRes = await qmdEmbed(target.name, target.path, target.root);
116
116
  if (!embedRes.ok) {
117
117
  print.warn(`qmd embed failed after ingest: ${embedRes.stderr.trim().slice(0, 300)}`);
118
118
  print.warn('your wiki content is committed to disk; only the vector index is stale.');
package/lib/wiki/init.ts CHANGED
@@ -109,10 +109,7 @@ cases.
109
109
  const SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
110
110
 
111
111
  const GITIGNORE_TMPL = `# rig wiki — local-only artifacts (do not commit)
112
- # qmd vector cache (sqlite-vec, machine-specific, rebuildable)
113
- .qmd/index.sqlite*
114
- .qmd/*.sqlite-wal
115
- .qmd/*.sqlite-shm
112
+ # (vector cache lives outside the vault at ~/.rig/<project>/wiki/)
116
113
  # auto-generated reports
117
114
  lint-report-*.md
118
115
  # daemon proposal queue (per-machine)
package/lib/wiki/qmd.ts CHANGED
@@ -9,8 +9,13 @@
9
9
  // - Embed: Qwen3-Embedding-0.6B (~610MB, sets QMD_EMBED_MODEL)
10
10
  // - Rerank: Qwen3-Reranker-0.6B (~610MB, sets QMD_RERANK_MODEL)
11
11
  //
12
- // Per-wiki SQLite DB lives at `~/.rig/cache/qmd/<wiki>.sqlite`. Model GGUFs
13
- // cache in `~/.cache/qmd/models/` (hardcoded inside qmd).
12
+ // Per-wiki SQLite DB lives at `~/.rig/<project>/wiki/<wiki>.sqlite`, where
13
+ // `<project>` is the `name` from the nearest `package.json` walking up from
14
+ // the vault root (fallback: vault-root basename). The extra `wiki/` segment
15
+ // reserves room for other per-project rig artifacts as siblings. Legacy
16
+ // `~/.rig/cache/qmd/<wiki>.sqlite` is migrated lazily on first open.
17
+ //
18
+ // Model GGUFs still cache in `~/.cache/qmd/models/` (hardcoded inside qmd).
14
19
  //
15
20
  // Concurrency note: qmd's `setConfigSource` is module-global; serialize
16
21
  // store lifetimes by opening + closing inside each call.
@@ -71,10 +76,58 @@ function qmdVersion(): string {
71
76
  return 'unknown';
72
77
  }
73
78
 
74
- function dbPathFor(wikiName: string): string {
75
- const dir = path.join(paths.cache, 'qmd');
79
+ /**
80
+ * Resolve the project name for a vault: read the `name` field from the
81
+ * nearest `package.json` walking up from `vaultRoot`. Falls back to
82
+ * `basename(vaultRoot)` if no package.json with a usable name is found.
83
+ * Scoped names (`@scope/foo`) are flattened to `scope_foo`.
84
+ */
85
+ function resolveProjectName(vaultRoot: string): string {
86
+ let dir = path.resolve(vaultRoot);
87
+ while (true) {
88
+ const pkg = path.join(dir, 'package.json');
89
+ if (fs.existsSync(pkg)) {
90
+ try {
91
+ const parsed = JSON.parse(fs.readFileSync(pkg, 'utf8'));
92
+ if (typeof parsed.name === 'string' && parsed.name.trim()) {
93
+ return sanitizeSegment(parsed.name.trim());
94
+ }
95
+ } catch { /* malformed package.json — keep walking */ }
96
+ }
97
+ const parent = path.dirname(dir);
98
+ if (parent === dir) break;
99
+ dir = parent;
100
+ }
101
+ return sanitizeSegment(path.basename(vaultRoot)) || 'unknown';
102
+ }
103
+
104
+ function sanitizeSegment(s: string): string {
105
+ // npm scoped names use `/`; flatten so we don't create unintended nesting.
106
+ // Also defang filesystem-hostile chars and leading dots.
107
+ return s
108
+ .replace(/^@/, '')
109
+ .replace(/[/\\]/g, '_')
110
+ .replace(/[<>:"|?*\x00-\x1f]/g, '_')
111
+ .replace(/^\.+/, '');
112
+ }
113
+
114
+ function dbPathFor(wikiName: string, vaultRoot: string): string {
115
+ const projectName = resolveProjectName(vaultRoot);
116
+ const dir = path.join(paths.home, projectName, 'wiki');
76
117
  fs.mkdirSync(dir, { recursive: true });
77
- return path.join(dir, `${wikiName}.sqlite`);
118
+ const target = path.join(dir, `${wikiName}.sqlite`);
119
+ // One-shot migration from the legacy flat layout.
120
+ const legacy = path.join(paths.cache, 'qmd', `${wikiName}.sqlite`);
121
+ if (!fs.existsSync(target) && fs.existsSync(legacy)) {
122
+ for (const suffix of ['', '-wal', '-shm']) {
123
+ const src = legacy + suffix;
124
+ if (fs.existsSync(src)) {
125
+ try { fs.renameSync(src, target + suffix); }
126
+ catch { /* cross-device? leave the legacy file in place */ }
127
+ }
128
+ }
129
+ }
130
+ return target;
78
131
  }
79
132
 
80
133
  async function loadQmd(): Promise<{ createStore: any }> {
@@ -90,12 +143,13 @@ async function loadQmd(): Promise<{ createStore: any }> {
90
143
  export async function qmdEmbed(
91
144
  wikiName: string,
92
145
  dir: string,
146
+ vaultRoot: string,
93
147
  opts: { force?: boolean } = {}
94
148
  ): Promise<{ ok: boolean; stderr: string }> {
95
149
  try {
96
150
  const { createStore } = await loadQmd();
97
151
  const store = await createStore({
98
- dbPath: dbPathFor(wikiName),
152
+ dbPath: dbPathFor(wikiName, vaultRoot),
99
153
  config: {
100
154
  collections: { [wikiName]: { path: dir, pattern: '**/*.md' } },
101
155
  },
@@ -134,6 +188,7 @@ export interface QmdHit {
134
188
  export async function qmdQuery(
135
189
  q: string,
136
190
  wikiName: string,
191
+ vaultRoot: string,
137
192
  opts: { limit?: number; candidateLimit?: number; rerank?: boolean } = {}
138
193
  ): Promise<QmdHit[] | null> {
139
194
  const limit = opts.limit ?? 10;
@@ -142,7 +197,7 @@ export async function qmdQuery(
142
197
 
143
198
  try {
144
199
  const { createStore } = await loadQmd();
145
- const store = await createStore({ dbPath: dbPathFor(wikiName) });
200
+ const store = await createStore({ dbPath: dbPathFor(wikiName, vaultRoot) });
146
201
  try {
147
202
  const raw = await store.searchVector(q, { limit: candidateLimit, collection: wikiName });
148
203
  const candidates: QmdHit[] = Array.isArray(raw) ? raw.map(normalizeHit) : [];
@@ -191,8 +246,8 @@ function normalizeHit(h: any): QmdHit {
191
246
  }
192
247
 
193
248
  /** Wipe the per-wiki SQLite store on disk. Caller should then qmdEmbed. */
194
- export function qmdResetStore(wikiName: string): void {
195
- const p = dbPathFor(wikiName);
249
+ export function qmdResetStore(wikiName: string, vaultRoot: string): void {
250
+ const p = dbPathFor(wikiName, vaultRoot);
196
251
  for (const suffix of ['', '-wal', '-shm']) {
197
252
  const f = p + suffix;
198
253
  if (fs.existsSync(f)) fs.rmSync(f, { force: true });
package/lib/wiki/query.ts CHANGED
@@ -30,7 +30,7 @@ export default async function wikiQuery(q: string, opts: QueryOpts): Promise<voi
30
30
  const target = requireVault();
31
31
 
32
32
  const limit = Math.max(1, Math.min(50, opts.limit || 10));
33
- const hits = await qmdQuery(q, target.name, { limit, rerank: opts.rerank !== false });
33
+ const hits = await qmdQuery(q, target.name, target.root, { limit, rerank: opts.rerank !== false });
34
34
  if (hits === null) {
35
35
  print.error('qmd query failed. Run `rig wiki index` first to (re)build the vector store.');
36
36
  process.exit(1);
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Use cases:
4
4
  // 1. New device: source markdown is checked out, but ~/.rig/state.db and
5
- // ~/.rig/cache/qmd/<wiki>.sqlite are empty. Rebuild populates both.
5
+ // ~/.rig/<project>/wiki/<wiki>.sqlite are empty. Rebuild populates both.
6
6
  // 2. Switched embedding model: old vectors are now meaningless. Rebuild
7
7
  // re-embeds against the current QMD_EMBED_MODEL.
8
8
  // 3. Local cache corruption: nuke and start over.
@@ -27,10 +27,10 @@ export default async function wikiRebuild(opts: RebuildOpts): Promise<void> {
27
27
  const del = db.prepare('DELETE FROM source_sha WHERE wiki = ?').run(t.name);
28
28
  print.info(` cleared ${del.changes} source_sha rows for ${t.name}`);
29
29
 
30
- qmdResetStore(t.name);
30
+ qmdResetStore(t.name, t.root);
31
31
 
32
32
  if (!opts.skipEmbed) {
33
- const res = await qmdEmbed(t.name, t.path, { force: true });
33
+ const res = await qmdEmbed(t.name, t.path, t.root, { force: true });
34
34
  if (res.ok) print.info(` qmd embed: ${t.name} done`);
35
35
  else {
36
36
  print.error(` qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rigjs",
3
- "version": "4.0.14",
4
- "versionCode": 26052420,
3
+ "version": "4.0.15",
4
+ "versionCode": 26052422,
5
5
  "description": "A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.",
6
6
  "keywords": [
7
7
  "modular",