rigjs 4.0.3 → 4.0.5

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.
@@ -13,7 +13,7 @@ Convention: **one file per subcommand**, plus a small set of shared infra files
13
13
  | `index.ts` | Commander wiring. Builds the `rig wiki` subtree and attaches every action. Imported once from `lib/rig/index.ts`. |
14
14
  | `paths.ts` | Centralized filesystem paths (`~/.rig/`, launchd plist, Claude skills dir). Override with `RIG_HOME`. Also exports the launchd label. |
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` | JSON5 read/write for `~/.rig/config.json5` (`RigConfig`) and `~/.rig/wiki.config.json5` (`WikiConfig`). `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 `~/.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). |
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,9 +23,9 @@ 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. Idempotent — never overwrites existing files. Does **not** register. |
27
- | `register.ts` | `rig wiki register [path]` | Adds (or `--force`-replaces) an entry in `~/.rig/wiki.config.json5`. Auto-detects path (`harness/llm-wiki` / `wiki`) walking up from CWD. Also writes a `wiki:` block back to the project's `package.rig.json5` for bidirectional consistency. |
28
- | `unregister.ts` | `rig wiki unregister <nameOrPath>` | Removes the entry. Disk wiki content is untouched. |
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
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
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
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. |
@@ -51,7 +51,7 @@ One adapter per agent CLI. Only Claude Code is implemented in v1; others are stu
51
51
  | `codex.ts` | Stub. Detection works; `run()` throws. Open questions on codex's permission flags live in `doc/architecture/agents.md §4`. |
52
52
  | `pi.ts` | Stub. Same shape as codex. Upstream CLI name not yet fixed. |
53
53
  | `list.ts` | `rig wiki agent list` — iterates `adapters`, calls `detect()`, prints a table. Marks the default agent with `*`. |
54
- | `use.ts` | `rig wiki agent use <name>` — writes `~/.rig/config.json5` `wiki.defaultAgent`. Rejects un-implemented adapters with exit code 20. |
54
+ | `use.ts` | `rig wiki agent use <name>` — writes `~/.rig/config.yml` `wiki.defaultAgent`. Rejects un-implemented adapters with exit code 20. |
55
55
 
56
56
  ---
57
57
 
@@ -66,7 +66,7 @@ One adapter per agent CLI. Only Claude Code is implemented in v1; others are stu
66
66
  | `stop.ts` | `launchctl bootout` only. |
67
67
  | `status.ts` | `launchctl print gui/<uid>/<label>`, parses `state=` and `pid=`. |
68
68
  | `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 `wiki.config.json5`. P2 will add cron-based scan/lint scheduling and `auto-on-new` ingest rules. |
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. |
70
70
 
71
71
  ---
72
72
 
@@ -1,7 +1,20 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import JSON5 from 'json5';
4
- import { paths } from './paths';
3
+ import yaml from 'js-yaml';
4
+ import { paths, vaultConfigPath } from './paths';
5
+
6
+ /**
7
+ * Two-layer config model.
8
+ *
9
+ * ~/.rig/config.yml — rig-global preferences (agent / qmd / logRotate).
10
+ * ~/.rig/wikis.yml — registry: list of absolute vault paths only.
11
+ * <vault>/.rig/config.yml — per-vault settings (name, include, exclude,
12
+ * schedule, ingestRules, optional scan root).
13
+ *
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.
17
+ */
5
18
 
6
19
  export interface RigConfig {
7
20
  wiki?: {
@@ -11,89 +24,155 @@ export interface RigConfig {
11
24
  };
12
25
  }
13
26
 
14
- export interface WikiEntry {
27
+ /** Per-vault config persisted at `<vault>/.rig/config.yml`. */
28
+ export interface VaultConfig {
15
29
  name: string;
16
- path: string; // absolute path to wiki dir
17
- project?: string; // absolute path to project root
30
+ /** Scan root relative to the vault. Default: `..` (vault's parent dir). */
31
+ root?: string;
18
32
  include?: string[];
19
33
  exclude?: string[];
20
34
  schedule?: { scan?: string; lint?: string; ingest?: string | null };
21
35
  ingestRules?: { match: string; mode: 'auto-on-new' | 'propose-only' }[];
22
36
  }
23
37
 
38
+ /** Global registry persisted at `~/.rig/wikis.yml`. */
39
+ export interface Registry {
40
+ wikis: string[]; // absolute vault paths
41
+ }
42
+
43
+ /** Composed view used by all wiki commands. */
44
+ export interface WikiEntry {
45
+ name: string;
46
+ /** Absolute path to the vault dir. */
47
+ path: string;
48
+ /** Absolute path to the scan root (resolved from VaultConfig.root). */
49
+ root: string;
50
+ include: string[];
51
+ exclude: string[];
52
+ schedule?: { scan?: string; lint?: string; ingest?: string | null };
53
+ ingestRules?: { match: string; mode: 'auto-on-new' | 'propose-only' }[];
54
+ }
55
+
24
56
  export interface WikiConfig {
25
- defaults?: {
26
- schedule?: { scan?: string; lint?: string; ingest?: string | null };
27
- };
28
57
  wikis: WikiEntry[];
29
58
  }
30
59
 
31
- const DEFAULT_CONFIG: RigConfig = {
60
+ const DEFAULT_RIG_CONFIG: RigConfig = {
32
61
  wiki: { defaultAgent: 'claude', qmd: { enabled: 'auto' }, logRotateMB: 50 },
33
62
  };
34
63
 
35
- const DEFAULT_WIKI_CONFIG: WikiConfig = {
36
- defaults: { schedule: { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null } },
37
- wikis: [],
38
- };
64
+ const DEFAULT_SCHEDULE = { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null };
39
65
 
40
- function ensureHomeDir() {
66
+ function ensureHomeDir(): void {
41
67
  fs.mkdirSync(paths.home, { recursive: true });
42
68
  fs.mkdirSync(paths.locks, { recursive: true });
43
69
  fs.mkdirSync(paths.logs, { recursive: true });
44
70
  fs.mkdirSync(paths.cache, { recursive: true });
45
71
  }
46
72
 
47
- function readJson5<T>(file: string, fallback: T): T {
73
+ function readYaml<T>(file: string, fallback: T): T {
48
74
  if (!fs.existsSync(file)) return fallback;
49
75
  try {
50
- return JSON5.parse(fs.readFileSync(file, 'utf8')) as T;
76
+ const parsed = yaml.load(fs.readFileSync(file, 'utf8'));
77
+ return (parsed ?? fallback) as T;
51
78
  } catch (e: any) {
52
79
  throw new Error(`failed to parse ${file}: ${e.message}`);
53
80
  }
54
81
  }
55
82
 
56
- function writeJson5(file: string, data: unknown) {
57
- fs.writeFileSync(file, JSON5.stringify(data, null, 2) + '\n', 'utf8');
83
+ function writeYaml(file: string, data: unknown): void {
84
+ fs.mkdirSync(path.dirname(file), { recursive: true });
85
+ fs.writeFileSync(file, yaml.dump(data, { lineWidth: 100, noRefs: true }), 'utf8');
58
86
  }
59
87
 
88
+ // ─────────────────────────── rig-global config ───────────────────────────
89
+
60
90
  export function loadRigConfig(): RigConfig {
61
91
  ensureHomeDir();
62
- return { ...DEFAULT_CONFIG, ...readJson5<RigConfig>(paths.config, DEFAULT_CONFIG) };
92
+ return { ...DEFAULT_RIG_CONFIG, ...readYaml<RigConfig>(paths.config, DEFAULT_RIG_CONFIG) };
63
93
  }
64
94
 
65
- export function saveRigConfig(cfg: RigConfig) {
95
+ export function saveRigConfig(cfg: RigConfig): void {
66
96
  ensureHomeDir();
67
- writeJson5(paths.config, cfg);
97
+ writeYaml(paths.config, cfg);
68
98
  }
69
99
 
70
- export function loadWikiConfig(): WikiConfig {
100
+ // ─────────────────────────── registry (paths only) ───────────────────────
101
+
102
+ export function loadRegistry(): Registry {
71
103
  ensureHomeDir();
72
- const cfg = readJson5<WikiConfig>(paths.wikiConfig, DEFAULT_WIKI_CONFIG);
73
- // backwards-compat: ensure wikis array exists
74
- if (!Array.isArray(cfg.wikis)) cfg.wikis = [];
75
- return { ...DEFAULT_WIKI_CONFIG, ...cfg };
104
+ const reg = readYaml<Registry>(paths.registry, { wikis: [] });
105
+ if (!Array.isArray(reg.wikis)) reg.wikis = [];
106
+ return reg;
76
107
  }
77
108
 
78
- export function saveWikiConfig(cfg: WikiConfig) {
109
+ export function saveRegistry(reg: Registry): void {
79
110
  ensureHomeDir();
80
- writeJson5(paths.wikiConfig, cfg);
111
+ writeYaml(paths.registry, { wikis: reg.wikis });
112
+ }
113
+
114
+ // ─────────────────────────── per-vault config ────────────────────────────
115
+
116
+ export function loadVaultConfig(vaultDir: string): VaultConfig | null {
117
+ const file = vaultConfigPath(vaultDir);
118
+ if (!fs.existsSync(file)) return null;
119
+ return readYaml<VaultConfig | null>(file, null);
81
120
  }
82
121
 
83
- /** Find a wiki entry by name OR by absolute path. */
84
- export function findWiki(cfg: WikiConfig, nameOrPath: string): WikiEntry | undefined {
85
- return cfg.wikis.find(w => w.name === nameOrPath || w.path === path.resolve(nameOrPath));
122
+ export function saveVaultConfig(vaultDir: string, cfg: VaultConfig): void {
123
+ writeYaml(vaultConfigPath(vaultDir), cfg);
124
+ }
125
+
126
+ // ───────────────────────────── composed view ─────────────────────────────
127
+
128
+ const HARDCODED_EXCLUDES = ['node_modules/**', '.git/**'];
129
+
130
+ function composeEntry(vaultPath: string): WikiEntry | null {
131
+ const vault = loadVaultConfig(vaultPath);
132
+ if (!vault) return null;
133
+ const rootRel = vault.root ?? '..';
134
+ const root = path.resolve(vaultPath, rootRel);
135
+ const vaultBasename = path.basename(vaultPath);
136
+ return {
137
+ name: vault.name,
138
+ path: vaultPath,
139
+ root,
140
+ include: vault.include ?? ['**/*.md'],
141
+ exclude: vault.exclude ?? [`${vaultBasename}/**`, ...HARDCODED_EXCLUDES],
142
+ schedule: vault.schedule ?? DEFAULT_SCHEDULE,
143
+ ingestRules: vault.ingestRules ?? [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
144
+ };
145
+ }
146
+
147
+ /**
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).
152
+ */
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);
159
+ }
160
+ return { wikis };
86
161
  }
87
162
 
88
163
  /**
89
164
  * Resolve target wiki for a command:
90
- * 1. If `--wiki <name>` provided, look up by name.
91
- * 2. Else walk up from CWD; first match wins (by `project` or `path` prefix).
92
- * 3. Return undefined if nothing matches.
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.
93
168
  */
94
169
  export function resolveWiki(cfg: WikiConfig, wikiFlag?: string): WikiEntry | undefined {
95
170
  if (wikiFlag) return cfg.wikis.find(w => w.name === wikiFlag);
96
171
  const cwd = process.cwd();
97
- return cfg.wikis.find(w => cwd === w.path || cwd.startsWith(w.path + path.sep) ||
98
- (w.project && (cwd === w.project || cwd.startsWith(w.project + path.sep))));
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
+ );
99
178
  }
package/lib/wiki/index.ts CHANGED
@@ -27,13 +27,13 @@ export function registerWikiCommands(program: any): void {
27
27
  .action(wikiInit);
28
28
 
29
29
  wiki.command('register [path]')
30
- .description('register a wiki into ~/.rig/wiki.config.json5')
30
+ .description('register a vault into ~/.rig/wikis.yml (settings live in <vault>/.rig/config.yml)')
31
31
  .option('-n, --as <slug>', 'override the wiki name (`--name` would clash with commander)')
32
32
  .option('-f, --force', 'overwrite an existing entry with the same name')
33
33
  .action(wikiRegister);
34
34
 
35
35
  wiki.command('unregister <nameOrPath>')
36
- .description('remove a wiki from ~/.rig/wiki.config.json5 (disk untouched)')
36
+ .description('remove a vault from ~/.rig/wikis.yml (vault contents on disk untouched)')
37
37
  .action(wikiUnregister);
38
38
 
39
39
  wiki.command('list')
@@ -45,7 +45,7 @@ export default async function wikiIngest(source: string, opts: IngestOpts): Prom
45
45
  print.error(`source not found: ${source}`);
46
46
  process.exit(1);
47
47
  }
48
- const guard = guardPath(absSource, target.project || target.path);
48
+ const guard = guardPath(absSource, target.root || target.path);
49
49
  if (!guard.ok) {
50
50
  print.error('refusing to ingest from a hidden or gitignored path.');
51
51
  // eslint-disable-next-line no-console
package/lib/wiki/init.ts CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import print from '../print';
4
4
  import { guardPath, refusalMessage } from './pathGuard';
5
+ import { saveVaultConfig, VaultConfig } from './config';
5
6
 
6
7
  const PURPOSE_TMPL = `# Purpose
7
8
 
@@ -75,6 +76,15 @@ proposals/
75
76
  *.swp
76
77
  `;
77
78
 
79
+ const DEFAULT_VAULT_CONFIG = (vaultBasename: string): VaultConfig => ({
80
+ name: vaultBasename,
81
+ root: '..',
82
+ include: ['**/*.md'],
83
+ exclude: [`${vaultBasename}/**`, 'node_modules/**', '.git/**'],
84
+ schedule: { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null },
85
+ ingestRules: [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
86
+ });
87
+
78
88
  export default function wikiInit(givenPath?: string): void {
79
89
  if (!givenPath || !givenPath.trim()) {
80
90
  print.error('rig wiki init requires a target subdirectory.');
@@ -109,8 +119,15 @@ export default function wikiInit(givenPath?: string): void {
109
119
  writeIfMissing(path.join(d, '.gitkeep'), '');
110
120
  }
111
121
 
122
+ // Seed `<vault>/.rig/config.yml` with sensible defaults. Idempotent: if the
123
+ // user has already authored one, leave it alone.
124
+ const vaultCfgFile = path.join(root, '.rig', 'config.yml');
125
+ if (!fs.existsSync(vaultCfgFile)) {
126
+ saveVaultConfig(root, DEFAULT_VAULT_CONFIG(path.basename(root)));
127
+ }
128
+
112
129
  print.succeed(`wiki initialized at ${root}`);
113
- print.info(`next: edit purpose.md + schema.md, then \`rig wiki register ${shortPath(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)}\``);
114
131
  print.info('on a new device, after cloning, run `rig wiki rebuild` to refresh local caches.');
115
132
  }
116
133
 
package/lib/wiki/paths.ts CHANGED
@@ -5,8 +5,8 @@ export const RIG_HOME = process.env.RIG_HOME || path.join(os.homedir(), '.rig');
5
5
 
6
6
  export const paths = {
7
7
  home: RIG_HOME,
8
- config: path.join(RIG_HOME, 'config.json5'),
9
- wikiConfig: path.join(RIG_HOME, 'wiki.config.json5'),
8
+ config: path.join(RIG_HOME, 'config.yml'),
9
+ registry: path.join(RIG_HOME, 'wikis.yml'),
10
10
  stateDb: path.join(RIG_HOME, 'state.db'),
11
11
  locks: path.join(RIG_HOME, 'locks'),
12
12
  logs: path.join(RIG_HOME, 'logs'),
@@ -24,6 +24,11 @@ export function wikiLogDir(wikiName: string) {
24
24
  return path.join(paths.logs, 'wikis', wikiName);
25
25
  }
26
26
 
27
+ /** Per-vault config path: `<vault>/.rig/config.yml`. */
28
+ export function vaultConfigPath(vaultDir: string) {
29
+ return path.join(vaultDir, '.rig', 'config.yml');
30
+ }
31
+
27
32
  export function wikiLockFile(wikiName: string) {
28
33
  return path.join(paths.locks, `${wikiName}.lock`);
29
34
  }
@@ -1,83 +1,82 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import JSON5 from 'json5';
4
3
  import print from '../print';
5
- import { loadWikiConfig, saveWikiConfig, WikiEntry } from './config';
4
+ import {
5
+ loadRegistry,
6
+ saveRegistry,
7
+ loadVaultConfig,
8
+ saveVaultConfig,
9
+ VaultConfig,
10
+ } from './config';
6
11
 
7
12
  interface RegisterOpts {
8
13
  as?: string;
9
14
  force?: boolean;
10
15
  }
11
16
 
17
+ /**
18
+ * `rig wiki register [path]`
19
+ *
20
+ * 1. Resolve the vault dir (explicit arg, or walk up from CWD looking for
21
+ * a directory that already has `purpose.md`).
22
+ * 2. Ensure `<vault>/.rig/config.yml` exists. Create it from defaults if
23
+ * missing; otherwise keep whatever the user authored.
24
+ * 3. Apply the optional `--as <slug>` override to the vault config's name.
25
+ * 4. Append the absolute vault path to `~/.rig/wikis.yml` (the
26
+ * discovery-only registry). No-op if already present.
27
+ */
12
28
  export default function wikiRegister(givenPath: string | undefined, opts: RegisterOpts): void {
13
- const wikiPath = path.resolve(givenPath || detectWikiPath(process.cwd()) || process.cwd());
14
- if (!fs.existsSync(wikiPath)) {
15
- print.error(`path does not exist: ${wikiPath}`);
29
+ const vaultPath = path.resolve(givenPath || detectVaultPath(process.cwd()) || process.cwd());
30
+ if (!fs.existsSync(vaultPath)) {
31
+ print.error(`path does not exist: ${vaultPath}`);
16
32
  process.exit(1);
17
33
  }
18
- const project = detectProjectRoot(wikiPath);
19
- const name = (opts.as || detectName(project, wikiPath)).trim();
20
- if (!name) {
21
- print.error('failed to derive a wiki name; pass --name <n>');
34
+ if (!fs.existsSync(path.join(vaultPath, 'purpose.md'))) {
35
+ print.error(`not a wiki vault (no purpose.md): ${vaultPath}`);
36
+ print.info(`run \`rig wiki init ${shortPath(vaultPath)}\` first.`);
22
37
  process.exit(1);
23
38
  }
24
39
 
25
- const cfg = loadWikiConfig();
26
- const existing = cfg.wikis.findIndex(w => w.name === name);
27
- if (existing >= 0 && !opts.force) {
28
- print.error(`wiki "${name}" already registered at ${cfg.wikis[existing].path}; pass --force to overwrite`);
40
+ // Load or seed the per-vault config.
41
+ let vault = loadVaultConfig(vaultPath);
42
+ if (!vault) {
43
+ vault = defaultVaultConfig(path.basename(vaultPath));
44
+ }
45
+ const desiredName = (opts.as || vault.name || path.basename(vaultPath)).trim();
46
+ if (!desiredName) {
47
+ print.error('failed to derive a wiki name; pass --as <slug>');
29
48
  process.exit(1);
30
49
  }
31
50
 
32
- // Read project-local overrides (include / exclude / ingestRules / schedule)
33
- // from package.rig.json5 if present. The user is the authoritative voice for
34
- // what gets scanned. Falls back to safe defaults only when fields are absent.
35
- const projectWiki = project ? readProjectWikiBlock(project) : null;
36
- const wikiBasename = path.basename(wikiPath);
37
- const entry: WikiEntry = {
38
- name,
39
- path: wikiPath,
40
- project: project || undefined,
41
- include: projectWiki?.include ?? ['**/*.md'],
42
- exclude: projectWiki?.exclude ?? [`${wikiBasename}/**`, 'node_modules/**', '.git/**'],
43
- schedule: projectWiki?.schedule ?? { scan: '0 */6 * * *', lint: '0 3 * * *' },
44
- ingestRules: projectWiki?.ingestRules ?? [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
45
- };
46
-
47
- if (existing >= 0) cfg.wikis[existing] = entry;
48
- else cfg.wikis.push(entry);
49
- saveWikiConfig(cfg);
50
-
51
- // bidirectional: write back to project's package.rig.json5 — preserve any
52
- // existing include/exclude/ingestRules/schedule fields the user authored.
53
- if (project) writeProjectWikiBlock(project, name, wikiPath, projectWiki);
51
+ // Name collision in the registry surfaced by composing the registry.
52
+ const reg = loadRegistry();
53
+ for (const existingPath of reg.wikis) {
54
+ if (existingPath === vaultPath) continue;
55
+ const other = loadVaultConfig(existingPath);
56
+ if (other && other.name === desiredName) {
57
+ if (!opts.force) {
58
+ print.error(`wiki "${desiredName}" already registered at ${existingPath}; pass --force to overwrite`);
59
+ process.exit(1);
60
+ }
61
+ // --force: drop the old entry, the new one wins.
62
+ reg.wikis = reg.wikis.filter(p => p !== existingPath);
63
+ }
64
+ }
54
65
 
55
- print.succeed(`registered wiki "${name}" -> ${wikiPath}`);
56
- }
66
+ vault.name = desiredName;
67
+ saveVaultConfig(vaultPath, vault);
57
68
 
58
- interface ProjectWikiBlock {
59
- name?: string;
60
- path?: string;
61
- include?: string[];
62
- exclude?: string[];
63
- schedule?: { scan?: string; lint?: string; ingest?: string | null };
64
- ingestRules?: { match: string; mode: 'auto-on-new' | 'propose-only' }[];
65
- }
69
+ if (!reg.wikis.includes(vaultPath)) reg.wikis.push(vaultPath);
70
+ saveRegistry(reg);
66
71
 
67
- function readProjectWikiBlock(project: string): ProjectWikiBlock | null {
68
- const file = path.join(project, 'package.rig.json5');
69
- if (!fs.existsSync(file)) return null;
70
- try {
71
- const cfg = JSON5.parse(fs.readFileSync(file, 'utf8'));
72
- const wiki = cfg && typeof cfg === 'object' && cfg.wiki;
73
- return wiki && typeof wiki === 'object' ? wiki as ProjectWikiBlock : null;
74
- } catch { return null; }
72
+ print.succeed(`registered wiki "${desiredName}" -> ${vaultPath}`);
75
73
  }
76
74
 
77
- function detectWikiPath(start: string): string | undefined {
75
+ function detectVaultPath(start: string): string | undefined {
78
76
  const candidates = ['harness/llm-wiki', 'wiki'];
79
77
  let dir = start;
80
78
  while (true) {
79
+ if (fs.existsSync(path.join(dir, 'purpose.md'))) return dir;
81
80
  for (const c of candidates) {
82
81
  const cand = path.join(dir, c);
83
82
  if (fs.existsSync(path.join(cand, 'purpose.md'))) return cand;
@@ -88,44 +87,18 @@ function detectWikiPath(start: string): string | undefined {
88
87
  }
89
88
  }
90
89
 
91
- function detectProjectRoot(wikiPath: string): string | undefined {
92
- let dir = wikiPath;
93
- while (true) {
94
- if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
95
- const parent = path.dirname(dir);
96
- if (parent === dir) return undefined;
97
- dir = parent;
98
- }
99
- }
100
-
101
- function detectName(project: string | undefined, wikiPath: string): string {
102
- if (project) {
103
- try {
104
- const pkg = JSON.parse(fs.readFileSync(path.join(project, 'package.json'), 'utf8'));
105
- if (pkg && typeof pkg.name === 'string') return pkg.name.replace(/^@.*\//, '');
106
- } catch { /* fall through */ }
107
- }
108
- return path.basename(path.dirname(wikiPath));
90
+ function defaultVaultConfig(vaultBasename: string): VaultConfig {
91
+ return {
92
+ name: vaultBasename,
93
+ root: '..',
94
+ include: ['**/*.md'],
95
+ exclude: [`${vaultBasename}/**`, 'node_modules/**', '.git/**'],
96
+ schedule: { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null },
97
+ ingestRules: [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
98
+ };
109
99
  }
110
100
 
111
- function writeProjectWikiBlock(
112
- project: string,
113
- name: string,
114
- wikiPath: string,
115
- existingWiki: ProjectWikiBlock | null,
116
- ): void {
117
- const file = path.join(project, 'package.rig.json5');
118
- let cfg: Record<string, unknown> = {};
119
- if (fs.existsSync(file)) {
120
- try { cfg = JSON5.parse(fs.readFileSync(file, 'utf8')); } catch { cfg = {}; }
121
- }
122
- // Always update name + path (those are derived from invocation). Preserve
123
- // every other field the user authored (include / exclude / schedule /
124
- // ingestRules / anything else they put in there).
125
- cfg.wiki = {
126
- ...(existingWiki || {}),
127
- name,
128
- path: path.relative(project, wikiPath),
129
- };
130
- fs.writeFileSync(file, JSON5.stringify(cfg, null, 2) + '\n', 'utf8');
101
+ function shortPath(p: string): string {
102
+ const home = process.env.HOME || '';
103
+ return home && p.startsWith(home) ? '~' + p.slice(home.length) : p;
131
104
  }
package/lib/wiki/scan.ts CHANGED
Binary file
@@ -1,16 +1,28 @@
1
1
  import path from 'path';
2
2
  import print from '../print';
3
- import { loadWikiConfig, saveWikiConfig } from './config';
3
+ import { loadRegistry, saveRegistry, loadVaultConfig } from './config';
4
4
 
5
+ /**
6
+ * `rig wiki unregister <nameOrPath>` — drop a vault path from
7
+ * `~/.rig/wikis.yml`. The vault's own `<vault>/.rig/config.yml` (and the
8
+ * rest of its on-disk contents) is left untouched.
9
+ */
5
10
  export default function wikiUnregister(nameOrPath: string): void {
6
- const cfg = loadWikiConfig();
7
- const target = path.resolve(nameOrPath);
8
- const before = cfg.wikis.length;
9
- cfg.wikis = cfg.wikis.filter(w => w.name !== nameOrPath && w.path !== target);
10
- if (cfg.wikis.length === before) {
11
+ const reg = loadRegistry();
12
+ const targetAbs = path.resolve(nameOrPath);
13
+ const before = reg.wikis.length;
14
+
15
+ reg.wikis = reg.wikis.filter(p => {
16
+ if (p === targetAbs) return false;
17
+ const v = loadVaultConfig(p);
18
+ if (v && v.name === nameOrPath) return false;
19
+ return true;
20
+ });
21
+
22
+ if (reg.wikis.length === before) {
11
23
  print.error(`no registered wiki matches "${nameOrPath}"`);
12
24
  process.exit(1);
13
25
  }
14
- saveWikiConfig(cfg);
26
+ saveRegistry(reg);
15
27
  print.succeed(`unregistered "${nameOrPath}" (disk contents untouched)`);
16
28
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rigjs",
3
- "version": "4.0.3",
4
- "versionCode": 26052410,
3
+ "version": "4.0.5",
4
+ "versionCode": 26052412,
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",
@@ -79,6 +79,7 @@
79
79
  "dayjs": "^1.11.0",
80
80
  "dotenv": "^16.0.3",
81
81
  "inquirer": "7.3.3",
82
+ "js-yaml": "^4.1.1",
82
83
  "json5": "2.1.3",
83
84
  "ora": "^5.1.0",
84
85
  "semver": "^7.3.7",
@@ -87,6 +88,7 @@
87
88
  },
88
89
  "devDependencies": {
89
90
  "@types/jest": "^28.1.1",
91
+ "@types/js-yaml": "^4.0.9",
90
92
  "@types/node": "^17.0.21",
91
93
  "esbuild": "0.28.0",
92
94
  "jest": "^27.5.1",