rigjs 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/rig-wiki/SKILL.md +68 -28
- package/RIG_WIKI_SKILL.md +68 -28
- package/built/index.js +181 -181
- package/lib/wiki/README.md +19 -15
- package/lib/wiki/config.ts +58 -61
- package/lib/wiki/daemon/runner.ts +11 -13
- package/lib/wiki/fetch.ts +3 -8
- package/lib/wiki/index.ts +8 -34
- package/lib/wiki/indexCmd.ts +8 -15
- package/lib/wiki/ingest.ts +8 -13
- package/lib/wiki/init.ts +108 -49
- package/lib/wiki/installSkill.ts +68 -20
- package/lib/wiki/lint.ts +19 -29
- package/lib/wiki/paths.ts +0 -1
- package/lib/wiki/query.ts +8 -12
- package/lib/wiki/rebuild.ts +17 -28
- package/lib/wiki/scan.ts +0 -0
- package/lib/wiki/uninstallSkill.ts +43 -17
- package/package.json +2 -2
- package/scripts/publish.mjs +59 -0
- package/skills.md +34 -7
- package/lib/wiki/list.ts +0 -69
- package/lib/wiki/register.ts +0 -104
- package/lib/wiki/unregister.ts +0 -28
package/lib/wiki/README.md
CHANGED
|
@@ -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
|
|
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
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
|
package/lib/wiki/config.ts
CHANGED
|
@@ -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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
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
|
-
//
|
|
127
|
-
|
|
128
|
-
const HARDCODED_EXCLUDES = ['node_modules/**', '.git/**'];
|
|
103
|
+
// ─────────────────────────── vault discovery ─────────────────────────────
|
|
129
104
|
|
|
130
|
-
function composeEntry(vaultPath: string): WikiEntry
|
|
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 ?? [
|
|
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
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
*
|
|
165
|
-
*
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
29
|
-
log(`heartbeat —
|
|
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 {
|
|
18
|
+
import { requireVault, WikiEntry } from './config';
|
|
19
19
|
import { adapters } from './agent/registry';
|
|
20
20
|
|
|
21
|
-
interface FetchOpts {
|
|
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
|
|
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';
|
|
@@ -22,34 +19,17 @@ import { registerDaemonCommands } from './daemon';
|
|
|
22
19
|
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
|
-
wiki.command('init <
|
|
26
|
-
.description('bootstrap a
|
|
22
|
+
wiki.command('init <scope>')
|
|
23
|
+
.description('bootstrap a vault scoped to <scope>/ — an existing data subdir of the project. Metadata is auto-created at ./rig-wiki/.')
|
|
27
24
|
.action(wikiInit);
|
|
28
25
|
|
|
29
|
-
wiki.command('
|
|
30
|
-
.description('
|
|
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
|
|
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
|
|
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);
|
package/lib/wiki/indexCmd.ts
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
import print from '../print';
|
|
2
|
-
import {
|
|
2
|
+
import { requireVault } from './config';
|
|
3
3
|
import { qmdEmbed } from './qmd';
|
|
4
4
|
|
|
5
|
-
interface IndexOpts {
|
|
5
|
+
interface IndexOpts { force?: boolean; }
|
|
6
6
|
|
|
7
7
|
export default async function wikiIndex(opts: IndexOpts): Promise<void> {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
print.error(
|
|
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
|
}
|
package/lib/wiki/ingest.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 = ['
|
|
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
|
|
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 \`
|
|
245
|
-
` - For each new or affected entity / concept / synthesis page, create or UPDATE the corresponding file under \`
|
|
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
|
|
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
|
|
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
|
``,
|