rigjs 4.0.4 → 4.0.6

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.
@@ -11,9 +11,9 @@ Convention: **one file per subcommand**, plus a small set of shared infra files
11
11
  | File | Purpose |
12
12
  |---|---|
13
13
  | `index.ts` | Commander wiring. Builds the `rig wiki` subtree and attaches every action. Imported once from `lib/rig/index.ts`. |
14
- | `paths.ts` | Centralized filesystem paths (`~/.rig/`, launchd plist, Claude skills dir). Override with `RIG_HOME`. Also exports the launchd label. |
14
+ | `paths.ts` | Centralized filesystem paths (`~/.rig/`, launchd plist, Claude skills dir). Override with `RIG_HOME`. Also exports the launchd label and `vaultConfigPath(vaultDir)` for `<vault>/.rig/config.yml`. |
15
15
  | `platform.ts` | `requireMacOS()` — hard-exits with code 32 on non-Darwin platforms. v1 is macOS-only by decision; see roadmap P5. |
16
- | `config.ts` | YAML read/write for `~/.rig/config.yml` (`RigConfig`, rig-global prefs) and `~/.rig/wikis.yml` (`Registry`, discovery-only path list). Per-vault settings live at `<vault>/.rig/config.yml` (`VaultConfig`). `loadWikiConfig()` composes the registry + each vault's config into the consumer-facing `WikiEntry[]`. `resolveWiki()` picks the target wiki for a command (flag CWD walk undefined). |
16
+ | `config.ts` | YAML read/write for `~/.rig/config.yml` (`RigConfig`, rig-global prefs) and `<vault>/.rig/config.yml` (`VaultConfig`). `resolveVault()` walks up from CWD looking for a `.rig/config.yml`; `requireVault()` is the CLI-friendly variant that exits with a clear error on miss. **No global registry** — vault discovery is purely CWD-based. |
17
17
  | `db.ts` | Lazy-loaded `better-sqlite3` singleton. WAL mode. Idempotent migrations on every open. Exposes `getDb()`, `recordLastRun()`, `getLastRun()`. |
18
18
  | `qmd.ts` | Detects `qmd` on PATH, wraps `qmd query --json` and `qmd embed`. All callers must handle `installed=false` gracefully — qmd is optional. |
19
19
 
@@ -23,18 +23,22 @@ Convention: **one file per subcommand**, plus a small set of shared infra files
23
23
 
24
24
  | File | Subcommand | What it does |
25
25
  |---|---|---|
26
- | `init.ts` | `rig wiki init [path]` | Bootstraps a fresh wiki dir: `purpose.md` + `schema.md` from templates, empty `index.md` / `overview.md` / `log.md` / `reviews.md`, `raw/` + five `wiki/<sub>/` dirs, and seeds `<vault>/.rig/config.yml` with default include / exclude / schedule. Idempotent — never overwrites existing files. Does **not** register. |
27
- | `register.ts` | `rig wiki register [path]` | Ensures `<vault>/.rig/config.yml` exists (seeding defaults if absent), applies `--as <slug>` to its `name`, then appends the vault's absolute path to `~/.rig/wikis.yml`. Auto-detects the vault by walking up from CWD looking for `purpose.md`. |
28
- | `unregister.ts` | `rig wiki unregister <nameOrPath>` | Drops the vault path from `~/.rig/wikis.yml`. `<vault>/.rig/config.yml` and disk content are untouched. |
29
- | `list.ts` | `rig wiki list` | Prints a table: name, path, page count, last scan / ingest / lint. Banner row shows detected agent CLI, qmd status. |
30
- | `scan.ts` | `rig wiki scan [path]` | Walks `include` globs + `raw/`, sha256-compares against the `source_sha` table in `state.db`. Emits NEW / MODIFIED / DELETED / RAW DRIFT report. Returns exit code 10 if any RAW DRIFT (raw/ files are immutable). No agent calls. |
31
- | `fetch.ts` | `rig wiki fetch <url>` | (stub P1) Agent-as-fetcher. Will WebFetch the URL via Claude adapter and write `raw/YYYY-MM-DD-<slug>.md` verbatim with frontmatter. **Never** summarizes. |
32
- | `ingest.ts` | `rig wiki ingest <source>` | (stub — P1) Two-step CoT (analysis → generation). Sandbox-copies the wiki dir, runs Claude adapter, then host-diffs sandbox vs original to extract writes. Filters out edits to `raw/` / `purpose.md` / `schema.md`. `--dry-run` prints diff without applying. |
33
- | `query.ts` | `rig wiki query "..."` | (stub P1) Answer a question with `[[wikilink]]` citations. Prefers `qmd query --json` for retrieval; falls back to injecting `index.md` + `overview.md` + heuristic page picks. |
34
- | `lint.ts` | `rig wiki lint` | (stub P1) Walks the wiki for contradictions, orphans, stale claims, broken `sources[]` refs, reviews.md backlog. Writes `lint-report-YYYY-MM-DD.md`. Non-zero exit on severe findings (code 11). |
35
- | `indexCmd.ts` | `rig wiki index` | qmd-only. Ensures the wiki's qmd collection exists, then runs `qmd embed`. When qmd is absent: warns and exits 0 (qmd is optional). Named `indexCmd` to avoid clashing with `index.ts`. |
36
- | `installSkill.ts` | `rig wiki install-skill` | Locates rig's bundled `.claude/skills/rig-wiki/` inside the rigjs install, then symlinks it into `~/.claude/skills/rig-wiki`. Lets every machine with rig installed get the slash command without manual copy. |
37
- | `uninstallSkill.ts` | `rig wiki uninstall-skill` | Removes the symlink. |
26
+ | `init.ts` | `rig wiki init <path>` | Bootstraps a fresh vault: `purpose.md` + `schema.md` from templates, empty `index.md` / `overview.md` / `log.md` / `reviews.md`, `raw/` + five page-tree dirs (`sources/ entities/ concepts/ synthesis/ queries/`) directly at the vault root (no inner `wiki/` subdir), and seeds `<vault>/.rig/config.yml`. Idempotent — never overwrites existing files. |
27
+ | `scan.ts` | `rig wiki scan` | Walks `include` globs from the vault's `root` (default: vault's parent dir), sha256-compares against the `source_sha` table in `state.db`. Auto-skips hidden segments (dot-prefixed) and gitignored paths. Emits NEW / MODIFIED / DELETED / RAW DRIFT report. Returns exit code 10 if any RAW DRIFT. No agent calls. |
28
+ | `fetch.ts` | `rig wiki fetch <url>` | Verbatim download URL into `raw/YYYY-MM-DD-<slug>.md`. Default path uses Node fetch + HTML-strip; `--via-agent` uses Claude WebFetch. Never summarizes — that's `ingest`'s job. |
29
+ | `ingest.ts` | `rig wiki ingest <source>` | Two-step CoT (analysis generation). Spawns Claude in the vault dir, then host-diffs the writable surface (`sources/ entities/ concepts/ synthesis/ queries/` + `index.md` / `overview.md` / `log.md` / `reviews.md`) to extract writes. Filters out edits to `raw/` / `purpose.md` / `schema.md`. `--dry-run` prints diff without applying. |
30
+ | `query.ts` | `rig wiki query "..."` | Vector retrieval via qmd. `--synth` adds a Claude-synthesized paragraph with `[[wikilink]]` citations. |
31
+ | `lint.ts` | `rig wiki lint` | Walks the vault for frontmatter completeness, contradictions, orphans, broken `[[wikilinks]]`, missing `raw/` sources, reviews.md backlog. Writes `lint-report-YYYY-MM-DD.md`. Exit 11 on severe findings. |
32
+ | `indexCmd.ts` | `rig wiki index` | qmd-only. Ensures the vault's qmd collection exists, then runs `qmd embed`. Named `indexCmd` to avoid clashing with `index.ts`. |
33
+ | `rebuild.ts` | `rig wiki rebuild` | Clear `source_sha` rows + drop the per-vault qmd store + full re-embed. Use after switching embed model or onto a new device. |
34
+ | `installSkill.ts` | `rig wiki install-skill [--project]` | Default: symlink bundled `rig-wiki` / `rig-crew` skills into `~/.claude/skills/`. With `--project`: install into `<cwd>/.claude/skills/` AND `<cwd>/.agents/skills/` (per-project override, covers both Claude Code and Codex). |
35
+ | `uninstallSkill.ts` | `rig wiki uninstall-skill [--project]` | Mirror of install-skill. |
36
+
37
+ ### Commands intentionally NOT in this set
38
+
39
+ `register`, `unregister`, `list` — there is no global registry. Vault discovery is by walking up from CWD looking for `<dir>/.rig/config.yml`. If you find yourself wanting to "list all wikis on this machine," that's a deliberate non-feature: each project's vault stands alone.
40
+
41
+ `--wiki <name>` and `--all` flags — gone everywhere. A command operates on whatever vault `resolveVault()` finds from CWD, or errors with a clear message.
38
42
 
39
43
  ---
40
44
 
@@ -66,7 +70,7 @@ One adapter per agent CLI. Only Claude Code is implemented in v1; others are stu
66
70
  | `stop.ts` | `launchctl bootout` only. |
67
71
  | `status.ts` | `launchctl print gui/<uid>/<label>`, parses `state=` and `pid=`. |
68
72
  | `logs.ts` | Tails `~/.rig/logs/wiki-daemon.log` (with optional `-f`). |
69
- | `runner.ts` | **The launchd entry.** `launchctl` invokes `node <rigjs>/built/index.js wiki daemon runner`. v1: heartbeat-only loop that logs every 10 min and reloads `~/.rig/wikis.yml` + each vault's `.rig/config.yml`. P2 will add cron-based scan/lint scheduling and `auto-on-new` ingest rules. |
73
+ | `runner.ts` | **The launchd entry.** `launchctl` invokes `node <rigjs>/built/index.js wiki daemon runner`. v1: heartbeat-only loop that tries to `resolveVault()` from its CWD at startup, logs the result, then ticks every 10 min. P2 will accept a `wiki.watchedVaults` list in `~/.rig/config.yml` and run cron-based scan/lint/ingest per entry. |
70
74
 
71
75
  ---
72
76
 
@@ -7,13 +7,13 @@ import { paths, vaultConfigPath } from './paths';
7
7
  * Two-layer config model.
8
8
  *
9
9
  * ~/.rig/config.yml — rig-global preferences (agent / qmd / logRotate).
10
- * ~/.rig/wikis.yml — registry: list of absolute vault paths only.
11
10
  * <vault>/.rig/config.yml — per-vault settings (name, include, exclude,
12
11
  * schedule, ingestRules, optional scan root).
13
12
  *
14
- * A `WikiEntry` is the composed view returned to consumers (registry path
15
- * + vault config + defaults). Nothing about a wiki's identity or scope lives
16
- * outside the vault itself; the global registry is just a discovery list.
13
+ * There is **no global registry**. A vault is discovered by walking up from
14
+ * the current working directory looking for `.rig/config.yml`. Everything
15
+ * about a vault its identity, its scope, its schedule lives inside the
16
+ * vault dir itself.
17
17
  */
18
18
 
19
19
  export interface RigConfig {
@@ -35,17 +35,12 @@ export interface VaultConfig {
35
35
  ingestRules?: { match: string; mode: 'auto-on-new' | 'propose-only' }[];
36
36
  }
37
37
 
38
- /** Global registry persisted at `~/.rig/wikis.yml`. */
39
- export interface Registry {
40
- wikis: string[]; // absolute vault paths
41
- }
42
-
43
38
  /** Composed view used by all wiki commands. */
44
39
  export interface WikiEntry {
45
40
  name: string;
46
41
  /** Absolute path to the vault dir. */
47
42
  path: string;
48
- /** Absolute path to the scan root (resolved from VaultConfig.root). */
43
+ /** Absolute path to the scan root. */
49
44
  root: string;
50
45
  include: string[];
51
46
  exclude: string[];
@@ -53,10 +48,6 @@ export interface WikiEntry {
53
48
  ingestRules?: { match: string; mode: 'auto-on-new' | 'propose-only' }[];
54
49
  }
55
50
 
56
- export interface WikiConfig {
57
- wikis: WikiEntry[];
58
- }
59
-
60
51
  const DEFAULT_RIG_CONFIG: RigConfig = {
61
52
  wiki: { defaultAgent: 'claude', qmd: { enabled: 'auto' }, logRotateMB: 50 },
62
53
  };
@@ -97,20 +88,6 @@ export function saveRigConfig(cfg: RigConfig): void {
97
88
  writeYaml(paths.config, cfg);
98
89
  }
99
90
 
100
- // ─────────────────────────── registry (paths only) ───────────────────────
101
-
102
- export function loadRegistry(): Registry {
103
- ensureHomeDir();
104
- const reg = readYaml<Registry>(paths.registry, { wikis: [] });
105
- if (!Array.isArray(reg.wikis)) reg.wikis = [];
106
- return reg;
107
- }
108
-
109
- export function saveRegistry(reg: Registry): void {
110
- ensureHomeDir();
111
- writeYaml(paths.registry, { wikis: reg.wikis });
112
- }
113
-
114
91
  // ─────────────────────────── per-vault config ────────────────────────────
115
92
 
116
93
  export function loadVaultConfig(vaultDir: string): VaultConfig | null {
@@ -123,56 +100,76 @@ export function saveVaultConfig(vaultDir: string, cfg: VaultConfig): void {
123
100
  writeYaml(vaultConfigPath(vaultDir), cfg);
124
101
  }
125
102
 
126
- // ───────────────────────────── composed view ─────────────────────────────
127
-
128
- const HARDCODED_EXCLUDES = ['node_modules/**', '.git/**'];
103
+ // ─────────────────────────── vault discovery ─────────────────────────────
129
104
 
130
- function composeEntry(vaultPath: string): WikiEntry | null {
131
- const vault = loadVaultConfig(vaultPath);
132
- if (!vault) return null;
105
+ function composeEntry(vaultPath: string, vault: VaultConfig): WikiEntry {
133
106
  const rootRel = vault.root ?? '..';
134
107
  const root = path.resolve(vaultPath, rootRel);
135
- const vaultBasename = path.basename(vaultPath);
136
108
  return {
137
- name: vault.name,
109
+ name: vault.name || path.basename(vaultPath),
138
110
  path: vaultPath,
139
111
  root,
140
112
  include: vault.include ?? ['**/*.md'],
141
- exclude: vault.exclude ?? [`${vaultBasename}/**`, ...HARDCODED_EXCLUDES],
113
+ exclude: vault.exclude ?? [],
142
114
  schedule: vault.schedule ?? DEFAULT_SCHEDULE,
143
115
  ingestRules: vault.ingestRules ?? [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
144
116
  };
145
117
  }
146
118
 
147
119
  /**
148
- * Compose the list of registered wikis. Reads the registry and, for each
149
- * recorded path, loads the vault's own `.rig/config.yml`. Paths whose vault
150
- * config is missing or malformed are skipped silently (the user can re-run
151
- * `rig wiki register <path>` to repair).
120
+ * Walk up from `start` (default: CWD) looking for a vault. At each ancestor
121
+ * we check two patterns:
122
+ *
123
+ * 1. **The dir itself is the vault** — `<dir>/.rig/config.yml` exists.
124
+ * Triggered when the user is inside the vault or below it.
125
+ * 2. **An immediate child is the vault** — some `<dir>/<child>/.rig/config.yml`
126
+ * exists. Triggered when the user is at the project root (the common
127
+ * case for `cd <project> && rig wiki *` where the vault is
128
+ * `<project>/rig-wiki/`). If multiple children are vaults, the
129
+ * lexicographically first wins.
130
+ *
131
+ * Returns the composed `WikiEntry`, or `undefined` if no vault is found.
132
+ *
133
+ * Callers that need a vault and don't have one must produce a helpful error
134
+ * themselves — `resolveVault` is a pure lookup and stays quiet.
152
135
  */
153
- export function loadWikiConfig(): WikiConfig {
154
- const reg = loadRegistry();
155
- const wikis: WikiEntry[] = [];
156
- for (const p of reg.wikis) {
157
- const entry = composeEntry(p);
158
- if (entry) wikis.push(entry);
136
+ export function resolveVault(start?: string): WikiEntry | undefined {
137
+ let dir = path.resolve(start || process.cwd());
138
+ while (true) {
139
+ // 1. Is `dir` itself a vault?
140
+ if (fs.existsSync(vaultConfigPath(dir))) {
141
+ const v = loadVaultConfig(dir);
142
+ if (v) return composeEntry(dir, v);
143
+ }
144
+ // 2. Does any immediate child of `dir` look like a vault?
145
+ try {
146
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
147
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
148
+ .map(e => e.name)
149
+ .sort();
150
+ for (const name of entries) {
151
+ const child = path.join(dir, name);
152
+ if (fs.existsSync(vaultConfigPath(child))) {
153
+ const v = loadVaultConfig(child);
154
+ if (v) return composeEntry(child, v);
155
+ }
156
+ }
157
+ } catch { /* unreadable dir — fall through to parent */ }
158
+
159
+ const parent = path.dirname(dir);
160
+ if (parent === dir) return undefined;
161
+ dir = parent;
159
162
  }
160
- return { wikis };
161
163
  }
162
164
 
163
165
  /**
164
- * Resolve target wiki for a command:
165
- * 1. If `--wiki <name>` provided, look up by name.
166
- * 2. Otherwise walk up from CWD; first match wins (by vault `path` or `root`).
167
- * 3. Return undefined if nothing matches.
166
+ * Same as `resolveVault` but exits the process with a clear error when no
167
+ * vault is found. Use from CLI commands.
168
168
  */
169
- export function resolveWiki(cfg: WikiConfig, wikiFlag?: string): WikiEntry | undefined {
170
- if (wikiFlag) return cfg.wikis.find(w => w.name === wikiFlag);
171
- const cwd = process.cwd();
172
- return cfg.wikis.find(w =>
173
- cwd === w.path ||
174
- cwd.startsWith(w.path + path.sep) ||
175
- cwd === w.root ||
176
- cwd.startsWith(w.root + path.sep),
177
- );
169
+ export function requireVault(): WikiEntry {
170
+ const v = resolveVault();
171
+ if (v) return v;
172
+ // eslint-disable-next-line no-console
173
+ console.error('No rig wiki vault found. cd into a vault directory (one that contains .rig/config.yml) or run `rig wiki init <path>` first.');
174
+ process.exit(1);
178
175
  }
@@ -1,16 +1,19 @@
1
1
  import print from '../../print';
2
- import { loadWikiConfig } from '../config';
2
+ import { resolveVault } from '../config';
3
3
  import { paths } from '../paths';
4
4
  import fs from 'fs';
5
- import path from 'path';
6
5
 
7
6
  /**
8
7
  * Daemon entry. launchctl invokes:
9
8
  * node <rig-built>/index.js wiki daemon runner
10
9
  *
11
10
  * v1: minimal heartbeat loop that logs to ~/.rig/logs/wiki-daemon.log.
12
- * P2 will add: cron-job registration per wiki for `schedule.scan` / `schedule.lint`,
13
- * plus auto-on-new ingest from `ingestRules`.
11
+ * Without a global registry, the daemon resolves a vault from its CWD at
12
+ * startup (whatever directory it was launched from). If none is found, it
13
+ * still ticks but reports `(no vault)`.
14
+ *
15
+ * P2 will accept a configurable list of vault paths in ~/.rig/config.yml
16
+ * (`wiki.watchedVaults`) and run cron-based scan/lint/ingest per entry.
14
17
  */
15
18
  export default function daemonRunner(): void {
16
19
  fs.mkdirSync(paths.logs, { recursive: true });
@@ -18,15 +21,13 @@ export default function daemonRunner(): void {
18
21
  fs.appendFileSync(paths.daemonLog, `[${new Date().toISOString()}] ${msg}\n`);
19
22
 
20
23
  log('rig wiki daemon: starting (v1 heartbeat-only)');
24
+ const startVault = resolveVault();
25
+ log(`startup vault: ${startVault ? `${startVault.name} (${startVault.path})` : '(none)'}`);
21
26
 
22
- let cfg = loadWikiConfig();
23
- log(`registered wikis: ${cfg.wikis.map(w => w.name).join(', ') || '(none)'}`);
24
-
25
- // Reload config every 10 minutes so daemon doesn't need restart when wikis change.
26
27
  setInterval(() => {
27
28
  try {
28
- cfg = loadWikiConfig();
29
- log(`heartbeat — wikis=${cfg.wikis.length}`);
29
+ const v = resolveVault();
30
+ log(`heartbeat — vault=${v ? v.name : '(none)'}`);
30
31
  } catch (e: any) {
31
32
  log(`heartbeat — config error: ${e.message}`);
32
33
  }
@@ -35,8 +36,5 @@ export default function daemonRunner(): void {
35
36
  process.on('SIGTERM', () => { log('SIGTERM — shutting down'); process.exit(0); });
36
37
  process.on('SIGINT', () => { log('SIGINT — shutting down'); process.exit(0); });
37
38
 
38
- // The interval keeps the event loop alive.
39
39
  print.info(`daemon up; logging to ${paths.daemonLog}`);
40
- // eslint-disable-next-line no-void
41
- void path; // unused-import placeholder, retained for future cron wiring
42
40
  }
package/lib/wiki/fetch.ts CHANGED
@@ -15,10 +15,10 @@ import fs from 'fs';
15
15
  import path from 'path';
16
16
  import crypto from 'crypto';
17
17
  import print from '../print';
18
- import { loadWikiConfig, resolveWiki, WikiEntry } from './config';
18
+ import { requireVault, WikiEntry } from './config';
19
19
  import { adapters } from './agent/registry';
20
20
 
21
- interface FetchOpts { wiki?: string; json?: boolean; viaAgent?: boolean; slug?: string; }
21
+ interface FetchOpts { json?: boolean; viaAgent?: boolean; slug?: string; }
22
22
 
23
23
  const FETCH_TIMEOUT_MS = 60 * 1000;
24
24
  const MAX_BYTES = 20 * 1024 * 1024; // 20MB cap to avoid pulling videos accidentally
@@ -28,12 +28,7 @@ export default async function wikiFetch(url: string, opts: FetchOpts): Promise<v
28
28
  print.error(`unsupported URL scheme: ${url}`);
29
29
  process.exit(1);
30
30
  }
31
- const cfg = loadWikiConfig();
32
- const target = resolveWiki(cfg, opts.wiki);
33
- if (!target) {
34
- print.error('no wiki resolved. Pass --wiki <name> or run from inside a registered project.');
35
- process.exit(1);
36
- }
31
+ const target = requireVault();
37
32
 
38
33
  const slug = opts.slug || urlToSlug(url);
39
34
  const today = new Date().toISOString().slice(0, 10);
package/lib/wiki/index.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  import wikiInit from './init';
2
- import wikiRegister from './register';
3
- import wikiUnregister from './unregister';
4
- import wikiList from './list';
5
2
  import wikiScan from './scan';
6
3
  import wikiFetch from './fetch';
7
4
  import wikiIngest from './ingest';
@@ -23,33 +20,16 @@ export function registerWikiCommands(program: any): void {
23
20
  const wiki = program.command('wiki').description('Karpathy-style LLM Wiki ops (macOS only in v1)');
24
21
 
25
22
  wiki.command('init <path>')
26
- .description('bootstrap a wiki dir at <path> (required; refuses to default to CWD)')
23
+ .description('bootstrap a vault at <path> (recommended: `rig-wiki` at the project root)')
27
24
  .action(wikiInit);
28
25
 
29
- wiki.command('register [path]')
30
- .description('register a vault into ~/.rig/wikis.yml (settings live in <vault>/.rig/config.yml)')
31
- .option('-n, --as <slug>', 'override the wiki name (`--name` would clash with commander)')
32
- .option('-f, --force', 'overwrite an existing entry with the same name')
33
- .action(wikiRegister);
34
-
35
- wiki.command('unregister <nameOrPath>')
36
- .description('remove a vault from ~/.rig/wikis.yml (vault contents on disk untouched)')
37
- .action(wikiUnregister);
38
-
39
- wiki.command('list')
40
- .description('list registered wikis + daemon/agent/qmd status')
41
- .action(wikiList);
42
-
43
- wiki.command('scan [path]')
44
- .description('compute NEW/MODIFIED/DELETED/RAW DRIFT report')
45
- .option('-w, --wiki <name>', 'target wiki name')
46
- .option('-a, --all', 'scan every registered wiki')
26
+ wiki.command('scan')
27
+ .description('compute NEW/MODIFIED/DELETED/RAW DRIFT report for the vault resolved from CWD')
47
28
  .option('--json', 'machine-readable output')
48
29
  .action(wikiScan);
49
30
 
50
31
  wiki.command('fetch <url>')
51
32
  .description('verbatim download URL into raw/YYYY-MM-DD-<slug>.md')
52
- .option('-w, --wiki <name>', 'target wiki name')
53
33
  .option('--slug <slug>', 'override the auto-derived slug')
54
34
  .option('--via-agent', 'use Claude WebFetch for HTML→md conversion')
55
35
  .option('--json', 'machine-readable output')
@@ -57,14 +37,12 @@ export function registerWikiCommands(program: any): void {
57
37
 
58
38
  wiki.command('ingest <source>')
59
39
  .description('two-step CoT ingest of one source (preview diff, then apply)')
60
- .option('-w, --wiki <name>', 'target wiki name')
61
40
  .option('--dry-run', 'print diff but do not apply')
62
41
  .option('--json', 'machine-readable output')
63
42
  .action(wikiIngest);
64
43
 
65
44
  wiki.command('query <q>')
66
45
  .description('semantic search — Qwen3 vector + Qwen3 reranker, cross-lingual CN/EN')
67
- .option('-w, --wiki <name>', 'target wiki name')
68
46
  .option('-l, --limit <n>', 'top-k hits (1-50, default 10)', (v) => parseInt(v, 10))
69
47
  .option('--no-rerank', 'skip the reranker pass (faster, no reranker model load)')
70
48
  .option('-s, --synth', 'use Claude to synthesize a paragraph answer with citations')
@@ -73,32 +51,28 @@ export function registerWikiCommands(program: any): void {
73
51
 
74
52
  wiki.command('lint')
75
53
  .description('contradictions / orphans / stale claims / broken refs')
76
- .option('-w, --wiki <name>', 'target wiki name')
77
- .option('-a, --all', 'lint every registered wiki')
78
54
  .option('--json', 'machine-readable output')
79
55
  .action(wikiLint);
80
56
 
81
57
  wiki.command('index')
82
58
  .description('build/refresh qmd vector index (incremental by default)')
83
- .option('-w, --wiki <name>', 'target wiki name')
84
- .option('-a, --all', 'index every registered wiki')
85
59
  .option('-f, --force', 'force full re-embed (use after switching embed models)')
86
60
  .action(wikiIndex);
87
61
 
88
62
  wiki.command('rebuild')
89
63
  .description('refresh local caches (sha index + qmd vectors) — for new devices or after switching embed models')
90
- .option('-w, --wiki <name>', 'target wiki name')
91
- .option('-a, --all', 'rebuild every registered wiki')
92
64
  .option('--skip-embed', 'only clear ~/.rig/state.db rows, do not touch qmd at all')
93
65
  .action(wikiRebuild);
94
66
 
95
67
  wiki.command('install-skill')
96
- .description('symlink bundled rig-wiki skill into ~/.claude/skills/')
68
+ .description('symlink bundled rig-wiki + rig-crew skills into ~/.claude/skills/ (or --project for the local project)')
97
69
  .option('-f, --force', 'replace an existing symlink')
70
+ .option('-p, --project', 'install into <cwd>/.claude/skills/ and <cwd>/.agents/skills/ (project-level override for Claude Code + Codex)')
98
71
  .action(wikiInstallSkill);
99
72
 
100
73
  wiki.command('uninstall-skill')
101
- .description('remove the symlink from ~/.claude/skills/rig-wiki')
74
+ .description('remove the bundled skill symlinks (default: global; pass --project for the local project)')
75
+ .option('-p, --project', 'uninstall from <cwd>/.claude/skills/ and <cwd>/.agents/skills/')
102
76
  .action(wikiUninstallSkill);
103
77
 
104
78
  registerAgentCommands(wiki);
@@ -1,23 +1,16 @@
1
1
  import print from '../print';
2
- import { loadWikiConfig, resolveWiki, WikiEntry } from './config';
2
+ import { requireVault } from './config';
3
3
  import { qmdEmbed } from './qmd';
4
4
 
5
- interface IndexOpts { wiki?: string; all?: boolean; force?: boolean; }
5
+ interface IndexOpts { force?: boolean; }
6
6
 
7
7
  export default async function wikiIndex(opts: IndexOpts): Promise<void> {
8
- const cfg = loadWikiConfig();
9
- const targets: WikiEntry[] = opts.all
10
- ? cfg.wikis
11
- : [resolveWiki(cfg, opts.wiki)].filter(Boolean) as WikiEntry[];
12
- if (targets.length === 0) {
13
- print.error('no wiki resolved. Pass --wiki <name>, --all, or run from inside a registered project.');
8
+ const t = requireVault();
9
+ print.start(`qmd embed: ${t.name}`);
10
+ const res = await qmdEmbed(t.name, t.path, { force: !!opts.force });
11
+ if (res.ok) print.succeed(`qmd embed: ${t.name} done`);
12
+ else {
13
+ print.error(`qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
14
14
  process.exit(1);
15
15
  }
16
-
17
- for (const t of targets) {
18
- print.start(`qmd embed: ${t.name}`);
19
- const res = await qmdEmbed(t.name, t.path, { force: !!opts.force });
20
- if (res.ok) print.succeed(`qmd embed: ${t.name} done`);
21
- else { print.error(`qmd embed: ${t.name} failed: ${res.stderr.trim()}`); process.exitCode = 1; }
22
- }
23
16
  }
@@ -16,29 +16,24 @@ import fs from 'fs';
16
16
  import path from 'path';
17
17
  import crypto from 'crypto';
18
18
  import print from '../print';
19
- import { loadWikiConfig, resolveWiki, loadRigConfig, WikiEntry } from './config';
19
+ import { requireVault, loadRigConfig, WikiEntry } from './config';
20
20
  import { paths } from './paths';
21
21
  import { recordLastRun } from './db';
22
22
  import { qmdEmbed } from './qmd';
23
23
  import { adapters } from './agent/registry';
24
24
  import { guardPath, refusalMessage } from './pathGuard';
25
25
 
26
- interface IngestOpts { wiki?: string; dryRun?: boolean; json?: boolean; }
26
+ interface IngestOpts { dryRun?: boolean; json?: boolean; }
27
27
 
28
28
  const AGENT_TIMEOUT_MS = 15 * 60 * 1000;
29
29
 
30
30
  // LLM-writable surface — everything outside this set is filtered out of the
31
31
  // diff (raw/, purpose.md, schema.md, .gitignore, lint-report-*, proposals/).
32
32
  const WRITABLE_TOP = new Set(['index.md', 'overview.md', 'log.md', 'reviews.md']);
33
- const WRITABLE_DIRS = ['wiki/sources', 'wiki/entities', 'wiki/concepts', 'wiki/synthesis', 'wiki/queries'];
33
+ const WRITABLE_DIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
34
34
 
35
35
  export default async function wikiIngest(source: string, opts: IngestOpts): Promise<void> {
36
- const cfg = loadWikiConfig();
37
- const target = resolveWiki(cfg, opts.wiki);
38
- if (!target) {
39
- print.error('no wiki resolved. Pass --wiki <name> or run from inside a registered project.');
40
- process.exit(1);
41
- }
36
+ const target = requireVault();
42
37
 
43
38
  const absSource = path.isAbsolute(source) ? source : path.resolve(target.path, source);
44
39
  if (!fs.existsSync(absSource)) {
@@ -241,12 +236,12 @@ function buildPrompt(wiki: WikiEntry, sourceAbs: string): string {
241
236
  ` - In your head, list: entities mentioned, concepts touched, contradictions vs existing pages, items that need human review.`,
242
237
  ``,
243
238
  `Step 2 — GENERATION (write files):`,
244
- ` - Create \`wiki/sources/<slug>.md\` summarizing this source. \`<slug>\` = source basename minus YYYY-MM-DD prefix and extension, kebab-case.`,
245
- ` - For each new or affected entity / concept / synthesis page, create or UPDATE the corresponding file under \`wiki/entities/\`, \`wiki/concepts/\`, \`wiki/synthesis/\`.`,
239
+ ` - Create \`sources/<slug>.md\` summarizing this source. \`<slug>\` = source basename minus YYYY-MM-DD prefix and extension, kebab-case.`,
240
+ ` - For each new or affected entity / concept / synthesis page, create or UPDATE the corresponding file under \`entities/\`, \`concepts/\`, \`synthesis/\` (at the vault root — there is no \`wiki/\` subdir).`,
246
241
  ` - Update \`index.md\` and \`overview.md\` to reflect the new content.`,
247
242
  ` - If anything is unclear or contradictory, append a bullet to \`reviews.md\`. Do NOT silently merge contradictions.`,
248
243
  ``,
249
- `Frontmatter — every wiki/**/*.md MUST have:`,
244
+ `Frontmatter — every page under sources/ entities/ concepts/ synthesis/ queries/ MUST have:`,
250
245
  '```yaml',
251
246
  `type: source | entity | concept | synthesis | query`,
252
247
  `sources: [<source-slug>, ...] # source-slug is the source page slug, not raw filename`,
@@ -261,7 +256,7 @@ function buildPrompt(wiki: WikiEntry, sourceAbs: string): string {
261
256
  ``,
262
257
  `Hard rules — the host will REJECT any patch that violates these:`,
263
258
  ` - DO NOT modify \`raw/\`, \`purpose.md\`, or \`schema.md\`.`,
264
- ` - Use kebab-case slugs; no spaces; no date prefixes inside \`wiki/\` filenames.`,
259
+ ` - Use kebab-case slugs; no spaces; no date prefixes in page filenames.`,
265
260
  ` - Link related pages with [[wikilink]]. Every wiki page should link to ≥1 other page.`,
266
261
  ` - For contradictions, write inline: \`> Contradiction: A vs B (see [[page-A]], [[page-B]])\`.`,
267
262
  ``,
package/lib/wiki/init.ts CHANGED
@@ -19,7 +19,8 @@ const SCHEMA_TMPL = `# Schema
19
19
 
20
20
  ## Layers
21
21
  - raw/, purpose.md, schema.md: read-only for LLM
22
- - wiki/, index.md, overview.md, log.md, reviews.md: LLM is sole author
22
+ - index.md, overview.md, log.md, reviews.md, sources/, entities/, concepts/,
23
+ synthesis/, queries/: LLM is sole author
23
24
 
24
25
  ## Page types
25
26
  - sources/<slug>.md : 1-source summary
@@ -37,7 +38,7 @@ const SCHEMA_TMPL = `# Schema
37
38
  - last-updated: <ISO>
38
39
 
39
40
  ## Naming
40
- - kebab-case; no spaces; no dates in wiki/ filenames
41
+ - kebab-case; no spaces; no dates in page filenames
41
42
  - raw/ filenames keep YYYY-MM-DD prefix
42
43
 
43
44
  ## Linking
@@ -56,7 +57,7 @@ const SCHEMA_TMPL = `# Schema
56
57
 
57
58
  const SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
58
59
 
59
- // What lives inside the wiki dir but must not enter git / Obsidian Sync:
60
+ // What lives inside the vault but must not enter git / Obsidian Sync:
60
61
  // - qmd's project-local vector cache (sqlite-vec, non-deterministic, rebuilds
61
62
  // locally with `rig wiki index` / `rig wiki rebuild`)
62
63
  // - lint reports (auto-regenerated)
@@ -76,11 +77,19 @@ proposals/
76
77
  *.swp
77
78
  `;
78
79
 
80
+ /**
81
+ * Sensible defaults for a fresh vault. The user can edit
82
+ * `<vault>/.rig/config.yml` afterwards.
83
+ *
84
+ * Note: hidden directories (any path segment starting with `.`) and files
85
+ * matched by the project's `.gitignore` are skipped automatically by the
86
+ * scanner — there is no need to add `.git/**` or `node_modules/**` here.
87
+ */
79
88
  const DEFAULT_VAULT_CONFIG = (vaultBasename: string): VaultConfig => ({
80
89
  name: vaultBasename,
81
90
  root: '..',
82
91
  include: ['**/*.md'],
83
- exclude: [`${vaultBasename}/**`, 'node_modules/**', '.git/**'],
92
+ exclude: [`${vaultBasename}/**`],
84
93
  schedule: { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null },
85
94
  ingestRules: [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
86
95
  });
@@ -88,14 +97,14 @@ const DEFAULT_VAULT_CONFIG = (vaultBasename: string): VaultConfig => ({
88
97
  export default function wikiInit(givenPath?: string): void {
89
98
  if (!givenPath || !givenPath.trim()) {
90
99
  print.error('rig wiki init requires a target subdirectory.');
91
- print.info('usage: rig wiki init <subdir> (e.g. `rig wiki init knowledge` / `rig wiki init harness/llm-wiki`)');
92
- print.info('refusing to default to CWD — that would litter the project root with wiki templates.');
100
+ print.info('usage: rig wiki init <subdir> (recommended: `rig wiki init rig-wiki` at the project root)');
101
+ print.info('refusing to default to CWD — that would litter the project root with vault templates.');
93
102
  process.exit(1);
94
103
  }
95
104
  const root = path.resolve(givenPath);
96
105
  const guard = guardPath(root, process.cwd());
97
106
  if (!guard.ok) {
98
- print.error('refusing to initialize a wiki at a hidden or gitignored path.');
107
+ print.error('refusing to initialize a vault at a hidden or gitignored path.');
99
108
  // eslint-disable-next-line no-console
100
109
  console.error(refusalMessage(root, guard));
101
110
  process.exit(1);
@@ -113,8 +122,9 @@ export default function wikiInit(givenPath?: string): void {
113
122
  fs.mkdirSync(path.join(root, 'raw'), { recursive: true });
114
123
  writeIfMissing(path.join(root, 'raw', '.gitkeep'), '');
115
124
 
125
+ // Page tree lives at the vault root — no extra `wiki/` nesting.
116
126
  for (const sub of SUBDIRS) {
117
- const d = path.join(root, 'wiki', sub);
127
+ const d = path.join(root, sub);
118
128
  fs.mkdirSync(d, { recursive: true });
119
129
  writeIfMissing(path.join(d, '.gitkeep'), '');
120
130
  }
@@ -126,8 +136,9 @@ export default function wikiInit(givenPath?: string): void {
126
136
  saveVaultConfig(root, DEFAULT_VAULT_CONFIG(path.basename(root)));
127
137
  }
128
138
 
129
- print.succeed(`wiki initialized at ${root}`);
130
- print.info(`next: edit purpose.md + schema.md (and .rig/config.yml if scope differs from defaults), then \`rig wiki register ${shortPath(root)}\``);
139
+ print.succeed(`vault initialized at ${root}`);
140
+ print.info('next: edit purpose.md + schema.md (and .rig/config.yml if scope differs from defaults).');
141
+ print.info('discovery is automatic — cd into this dir (or any subdir) and run `rig wiki *` commands.');
131
142
  print.info('on a new device, after cloning, run `rig wiki rebuild` to refresh local caches.');
132
143
  }
133
144
 
@@ -135,8 +146,3 @@ function writeIfMissing(file: string, content: string) {
135
146
  if (fs.existsSync(file)) return;
136
147
  fs.writeFileSync(file, content, 'utf8');
137
148
  }
138
-
139
- function shortPath(p: string) {
140
- const home = process.env.HOME || '';
141
- return home && p.startsWith(home) ? '~' + p.slice(home.length) : p;
142
- }