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/init.ts
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import print from '../print';
|
|
4
5
|
import { guardPath, refusalMessage } from './pathGuard';
|
|
5
|
-
import { saveVaultConfig, VaultConfig } from './config';
|
|
6
|
+
import { saveVaultConfig, loadVaultConfig, VaultConfig } from './config';
|
|
7
|
+
import { vaultConfigPath } from './paths';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `rig wiki init <scope>`
|
|
11
|
+
*
|
|
12
|
+
* The user runs this from a project root. The CWD is treated as the project
|
|
13
|
+
* (the conceptual "vault"); `<scope>` names a data subdir under it that the
|
|
14
|
+
* wiki should ingest from (e.g. `personal` for `<project>/personal/`).
|
|
15
|
+
*
|
|
16
|
+
* Vault metadata always lives at `<CWD>/rig-wiki/` (fixed name). The scope
|
|
17
|
+
* is recorded in `<CWD>/rig-wiki/.rig/config.yml` as the scan root, so the
|
|
18
|
+
* user's data dir stays untouched.
|
|
19
|
+
*
|
|
20
|
+
* $ cd overmind
|
|
21
|
+
* $ rig wiki init personal
|
|
22
|
+
* ⇒ creates overmind/rig-wiki/ with templates + .rig/config.yml
|
|
23
|
+
* ⇒ config.yml.root = "../personal", config.yml.name = "personal"
|
|
24
|
+
*
|
|
25
|
+
* Idempotent for the same scope. Errors if rig-wiki/ already exists with a
|
|
26
|
+
* different scope (manual `.rig/config.yml` edit required to switch).
|
|
27
|
+
*/
|
|
28
|
+
const VAULT_DIRNAME = 'rig-wiki';
|
|
6
29
|
|
|
7
30
|
const PURPOSE_TMPL = `# Purpose
|
|
8
31
|
|
|
@@ -19,7 +42,8 @@ const SCHEMA_TMPL = `# Schema
|
|
|
19
42
|
|
|
20
43
|
## Layers
|
|
21
44
|
- raw/, purpose.md, schema.md: read-only for LLM
|
|
22
|
-
-
|
|
45
|
+
- index.md, overview.md, log.md, reviews.md, sources/, entities/, concepts/,
|
|
46
|
+
synthesis/, queries/: LLM is sole author
|
|
23
47
|
|
|
24
48
|
## Page types
|
|
25
49
|
- sources/<slug>.md : 1-source summary
|
|
@@ -37,7 +61,7 @@ const SCHEMA_TMPL = `# Schema
|
|
|
37
61
|
- last-updated: <ISO>
|
|
38
62
|
|
|
39
63
|
## Naming
|
|
40
|
-
- kebab-case; no spaces; no dates in
|
|
64
|
+
- kebab-case; no spaces; no dates in page filenames
|
|
41
65
|
- raw/ filenames keep YYYY-MM-DD prefix
|
|
42
66
|
|
|
43
67
|
## Linking
|
|
@@ -56,12 +80,6 @@ const SCHEMA_TMPL = `# Schema
|
|
|
56
80
|
|
|
57
81
|
const SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
|
|
58
82
|
|
|
59
|
-
// What lives inside the wiki dir but must not enter git / Obsidian Sync:
|
|
60
|
-
// - qmd's project-local vector cache (sqlite-vec, non-deterministic, rebuilds
|
|
61
|
-
// locally with `rig wiki index` / `rig wiki rebuild`)
|
|
62
|
-
// - lint reports (auto-regenerated)
|
|
63
|
-
// - daemon proposal diffs (transient, per-machine)
|
|
64
|
-
// - editor scratch
|
|
65
83
|
const GITIGNORE_TMPL = `# rig wiki — local-only artifacts (do not commit)
|
|
66
84
|
# qmd vector cache (sqlite-vec, machine-specific, rebuildable)
|
|
67
85
|
.qmd/index.sqlite*
|
|
@@ -76,59 +94,99 @@ proposals/
|
|
|
76
94
|
*.swp
|
|
77
95
|
`;
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Defaults for a freshly-scoped vault. The user can edit
|
|
99
|
+
* `<vault>/.rig/config.yml` afterwards.
|
|
100
|
+
*
|
|
101
|
+
* Hidden directories (segments starting with `.`) and `.gitignore`'d files
|
|
102
|
+
* are skipped automatically by the scanner — no need to list them here.
|
|
103
|
+
*/
|
|
104
|
+
function defaultVaultConfig(scope: string, rootRel: string): VaultConfig {
|
|
105
|
+
return {
|
|
106
|
+
name: scope,
|
|
107
|
+
root: rootRel,
|
|
108
|
+
include: ['**/*.md'],
|
|
109
|
+
exclude: [],
|
|
110
|
+
schedule: { scan: '0 */6 * * *', lint: '0 3 * * *', ingest: null },
|
|
111
|
+
ingestRules: [{ match: 'raw/**/*.md', mode: 'auto-on-new' }],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default function wikiInit(scope?: string): void {
|
|
116
|
+
if (!scope || !scope.trim()) {
|
|
117
|
+
print.error('rig wiki init requires a scope.');
|
|
118
|
+
print.info('usage: rig wiki init <scope> (e.g. `rig wiki init personal` to ingest from ./personal/)');
|
|
119
|
+
print.info(`<scope> is an existing data subdir of the project. Vault metadata is auto-created at ./${VAULT_DIRNAME}/.`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
const vaultDir = path.join(cwd, VAULT_DIRNAME);
|
|
125
|
+
const scopeAbs = path.resolve(cwd, scope);
|
|
126
|
+
|
|
127
|
+
// The scope must already exist — pointing the wiki at a missing dir would
|
|
128
|
+
// hide what is almost certainly a typo.
|
|
129
|
+
if (!fs.existsSync(scopeAbs) || !fs.statSync(scopeAbs).isDirectory()) {
|
|
130
|
+
print.error(`scope dir not found: ${scope}`);
|
|
131
|
+
print.info(`expected an existing data subdir at ${shortPath(scopeAbs)}`);
|
|
93
132
|
process.exit(1);
|
|
94
133
|
}
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
// The scope can't be (or contain) the vault dir itself.
|
|
135
|
+
if (scopeAbs === vaultDir || vaultDir.startsWith(scopeAbs + path.sep)) {
|
|
136
|
+
print.error(`scope cannot be or contain the vault dir (${VAULT_DIRNAME}/).`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const guard = guardPath(vaultDir, cwd);
|
|
97
141
|
if (!guard.ok) {
|
|
98
|
-
print.error(
|
|
142
|
+
print.error(`refusing to initialize the vault at a hidden or gitignored path.`);
|
|
99
143
|
// eslint-disable-next-line no-console
|
|
100
|
-
console.error(refusalMessage(
|
|
144
|
+
console.error(refusalMessage(vaultDir, guard));
|
|
101
145
|
process.exit(1);
|
|
102
146
|
}
|
|
103
|
-
fs.mkdirSync(root, { recursive: true });
|
|
104
147
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
148
|
+
// If the vault already has a config, it must already be scoped to the
|
|
149
|
+
// same data dir — otherwise the user is trying to re-target an existing
|
|
150
|
+
// vault, which we won't do silently. Manual config edit only.
|
|
151
|
+
const cfgFile = vaultConfigPath(vaultDir);
|
|
152
|
+
if (fs.existsSync(cfgFile)) {
|
|
153
|
+
const existing = loadVaultConfig(vaultDir);
|
|
154
|
+
const existingRootAbs = existing?.root
|
|
155
|
+
? path.resolve(vaultDir, existing.root)
|
|
156
|
+
: path.dirname(vaultDir);
|
|
157
|
+
if (existingRootAbs !== scopeAbs) {
|
|
158
|
+
print.error(`vault already initialized at ${shortPath(vaultDir)} for scope "${existing?.name ?? '?'}" (root: ${existing?.root ?? '..'}).`);
|
|
159
|
+
print.info(`to switch scopes, edit ${shortPath(cfgFile)} (name + root) by hand.`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fs.mkdirSync(vaultDir, { recursive: true });
|
|
165
|
+
writeIfMissing(path.join(vaultDir, 'purpose.md'), PURPOSE_TMPL);
|
|
166
|
+
writeIfMissing(path.join(vaultDir, 'schema.md'), SCHEMA_TMPL);
|
|
167
|
+
writeIfMissing(path.join(vaultDir, 'index.md'), '# Index\n');
|
|
168
|
+
writeIfMissing(path.join(vaultDir, 'overview.md'), '# Overview\n');
|
|
169
|
+
writeIfMissing(path.join(vaultDir, 'log.md'), '# Log\n');
|
|
170
|
+
writeIfMissing(path.join(vaultDir, 'reviews.md'), '# Reviews\n');
|
|
171
|
+
writeIfMissing(path.join(vaultDir, '.gitignore'), GITIGNORE_TMPL);
|
|
112
172
|
|
|
113
|
-
fs.mkdirSync(path.join(
|
|
114
|
-
writeIfMissing(path.join(
|
|
173
|
+
fs.mkdirSync(path.join(vaultDir, 'raw'), { recursive: true });
|
|
174
|
+
writeIfMissing(path.join(vaultDir, 'raw', '.gitkeep'), '');
|
|
115
175
|
|
|
116
176
|
for (const sub of SUBDIRS) {
|
|
117
|
-
const d = path.join(
|
|
177
|
+
const d = path.join(vaultDir, sub);
|
|
118
178
|
fs.mkdirSync(d, { recursive: true });
|
|
119
179
|
writeIfMissing(path.join(d, '.gitkeep'), '');
|
|
120
180
|
}
|
|
121
181
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (!fs.existsSync(vaultCfgFile)) {
|
|
126
|
-
saveVaultConfig(root, DEFAULT_VAULT_CONFIG(path.basename(root)));
|
|
182
|
+
if (!fs.existsSync(cfgFile)) {
|
|
183
|
+
const rootRel = path.relative(vaultDir, scopeAbs);
|
|
184
|
+
saveVaultConfig(vaultDir, defaultVaultConfig(scope, rootRel));
|
|
127
185
|
}
|
|
128
186
|
|
|
129
|
-
print.succeed(`
|
|
130
|
-
print.info(`next: edit
|
|
131
|
-
print.info(
|
|
187
|
+
print.succeed(`vault initialized at ${shortPath(vaultDir)} (scope: ${scope})`);
|
|
188
|
+
print.info(`next: edit ${shortPath(path.join(vaultDir, 'purpose.md'))} to describe what this wiki is for.`);
|
|
189
|
+
print.info(`then run \`rig wiki scan\` from anywhere inside ${shortPath(cwd)} to see what will be ingested.`);
|
|
132
190
|
}
|
|
133
191
|
|
|
134
192
|
function writeIfMissing(file: string, content: string) {
|
|
@@ -136,7 +194,8 @@ function writeIfMissing(file: string, content: string) {
|
|
|
136
194
|
fs.writeFileSync(file, content, 'utf8');
|
|
137
195
|
}
|
|
138
196
|
|
|
139
|
-
function shortPath(p: string) {
|
|
140
|
-
const home =
|
|
141
|
-
|
|
197
|
+
function shortPath(p: string): string {
|
|
198
|
+
const home = os.homedir();
|
|
199
|
+
if (p.startsWith(home + path.sep)) return '~' + p.slice(home.length);
|
|
200
|
+
return p;
|
|
142
201
|
}
|
package/lib/wiki/installSkill.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import print from '../print';
|
|
4
5
|
import { paths } from './paths';
|
|
5
6
|
|
|
@@ -10,8 +11,6 @@ const BUNDLED_SKILLS = [
|
|
|
10
11
|
|
|
11
12
|
/** Find rig's package root by walking up from built/ or lib/. */
|
|
12
13
|
function findRigRoot(): string | undefined {
|
|
13
|
-
// Walk up from `built/index.js` (prod) or `lib/wiki/installSkill.ts` (dev)
|
|
14
|
-
// looking for the rigjs package root.
|
|
15
14
|
let dir = __dirname;
|
|
16
15
|
for (let i = 0; i < 10; i++) {
|
|
17
16
|
const pkg = path.join(dir, 'package.json');
|
|
@@ -28,8 +27,19 @@ function findRigRoot(): string | undefined {
|
|
|
28
27
|
return undefined;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
interface InstallOpts { force?: boolean; }
|
|
30
|
+
interface InstallOpts { force?: boolean; project?: boolean; }
|
|
32
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Install bundled skills (rig-wiki, rig-crew) as symlinks.
|
|
34
|
+
*
|
|
35
|
+
* Default (global): link into `~/.claude/skills/<name>/SKILL.md`. Affects
|
|
36
|
+
* every project that uses Claude Code on this machine.
|
|
37
|
+
*
|
|
38
|
+
* `--project` (project-level override for overmind-style monorepos):
|
|
39
|
+
* link into BOTH `<cwd>/.claude/skills/<name>/SKILL.md` (Claude Code) AND
|
|
40
|
+
* `<cwd>/.agents/skills/<name>/SKILL.md` (Codex). When the user is inside
|
|
41
|
+
* the project, the project-local skill files override the global ones.
|
|
42
|
+
*/
|
|
33
43
|
export default function wikiInstallSkill(opts: InstallOpts): void {
|
|
34
44
|
const root = findRigRoot();
|
|
35
45
|
if (!root) {
|
|
@@ -37,45 +47,77 @@ export default function wikiInstallSkill(opts: InstallOpts): void {
|
|
|
37
47
|
process.exit(1);
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const targetRoots = resolveTargetRoots(opts);
|
|
51
|
+
for (const tr of targetRoots) {
|
|
52
|
+
if (!fs.existsSync(tr.parent)) {
|
|
53
|
+
// Project-level install creates the dirs on demand (parent is <cwd>).
|
|
54
|
+
// Global install requires the dir to already exist — that's how we
|
|
55
|
+
// detect "Claude Code is installed on this machine".
|
|
56
|
+
if (tr.kind === 'global') {
|
|
57
|
+
print.error(`${tr.label} dir not found: ${tr.parent}`);
|
|
58
|
+
print.info(`install ${tr.label} first, then re-run \`rig wiki install-skill\`.`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
for (const skill of BUNDLED_SKILLS) {
|
|
64
|
+
const src = path.join(root, skill.file);
|
|
65
|
+
if (!fs.existsSync(src)) {
|
|
66
|
+
print.warn(`skipping ${skill.name}: ${skill.file} not found inside rigjs install`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
linkSkill(skill.name, src, tr.parent, opts);
|
|
51
70
|
}
|
|
52
|
-
linkSkill(skill.name, src, opts);
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
if (opts.project) {
|
|
74
|
+
print.info('project-local skills will override global ones when this project is the cwd.');
|
|
75
|
+
} else {
|
|
76
|
+
print.info('restart Claude Code to pick up new or updated skills.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface TargetRoot {
|
|
81
|
+
/** `<parent>/<skill-name>/SKILL.md` is the resulting symlink. */
|
|
82
|
+
parent: string;
|
|
83
|
+
kind: 'global' | 'project';
|
|
84
|
+
label: string;
|
|
56
85
|
}
|
|
57
86
|
|
|
58
|
-
function
|
|
59
|
-
|
|
87
|
+
function resolveTargetRoots(opts: InstallOpts): TargetRoot[] {
|
|
88
|
+
if (opts.project) {
|
|
89
|
+
const cwd = process.cwd();
|
|
90
|
+
return [
|
|
91
|
+
{ parent: path.join(cwd, '.claude', 'skills'), kind: 'project', label: 'Claude Code project skills' },
|
|
92
|
+
{ parent: path.join(cwd, '.agents', 'skills'), kind: 'project', label: 'Codex project skills' },
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
{ parent: paths.claudeSkillsDir, kind: 'global', label: 'Claude Code skills' },
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function linkSkill(name: string, src: string, parentDir: string, opts: InstallOpts): void {
|
|
101
|
+
const targetDir = path.join(parentDir, name);
|
|
60
102
|
const target = path.join(targetDir, 'SKILL.md');
|
|
61
103
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
62
104
|
|
|
63
105
|
if (fs.existsSync(target) || isBrokenSymlink(target)) {
|
|
64
106
|
const existing = safeReadlink(target);
|
|
65
107
|
if (existing === src) {
|
|
66
|
-
print.info(`already linked: ${target}`);
|
|
108
|
+
print.info(`already linked: ${shortPath(target)}`);
|
|
67
109
|
return;
|
|
68
110
|
}
|
|
69
111
|
if (!opts.force) {
|
|
70
112
|
const what = existing ? `symlink -> ${existing}` : 'a regular file';
|
|
71
|
-
print.error(`${target} exists as ${what}. Pass --force to replace.`);
|
|
113
|
+
print.error(`${shortPath(target)} exists as ${what}. Pass --force to replace.`);
|
|
72
114
|
process.exit(1);
|
|
73
115
|
}
|
|
74
116
|
fs.rmSync(target, { force: true });
|
|
75
117
|
}
|
|
76
118
|
|
|
77
119
|
fs.symlinkSync(src, target);
|
|
78
|
-
print.succeed(`linked ${target} -> ${src}`);
|
|
120
|
+
print.succeed(`linked ${shortPath(target)} -> ${shortPath(src)}`);
|
|
79
121
|
}
|
|
80
122
|
|
|
81
123
|
function safeReadlink(p: string): string | null {
|
|
@@ -90,3 +132,9 @@ function isBrokenSymlink(p: string): boolean {
|
|
|
90
132
|
try { return Boolean(fs.readlinkSync(p)); } catch { return false; }
|
|
91
133
|
}
|
|
92
134
|
}
|
|
135
|
+
|
|
136
|
+
function shortPath(p: string): string {
|
|
137
|
+
const home = os.homedir();
|
|
138
|
+
if (p.startsWith(home + path.sep)) return '~' + p.slice(home.length);
|
|
139
|
+
return p;
|
|
140
|
+
}
|
package/lib/wiki/lint.ts
CHANGED
|
@@ -16,13 +16,13 @@ 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, WikiEntry } from './config';
|
|
20
20
|
import { recordLastRun } from './db';
|
|
21
21
|
|
|
22
|
-
interface LintOpts {
|
|
22
|
+
interface LintOpts { json?: boolean; }
|
|
23
23
|
|
|
24
24
|
interface PageMeta {
|
|
25
|
-
rel: string; //
|
|
25
|
+
rel: string; // vault-relative path, e.g. "sources/foo.md"
|
|
26
26
|
slug: string; // filename without ext
|
|
27
27
|
sub: string; // "sources" | "entities" | "concepts" | "synthesis" | "queries"
|
|
28
28
|
frontmatter: Record<string, unknown> | null;
|
|
@@ -44,34 +44,24 @@ const SOURCE_EXTRA_KEYS = ['source-sha', 'source-path'] as const;
|
|
|
44
44
|
const WIKI_SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'] as const;
|
|
45
45
|
|
|
46
46
|
export default async function wikiLint(opts: LintOpts): Promise<void> {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (const t of targets) {
|
|
59
|
-
const findings = lintOne(t);
|
|
60
|
-
reports.push({ wiki: t.name, findings });
|
|
61
|
-
const sev =
|
|
62
|
-
findings.missingFrontmatter.length +
|
|
63
|
-
findings.missingRequiredKey.length +
|
|
64
|
-
findings.brokenWikilinks.length +
|
|
65
|
-
findings.missingRawSource.length;
|
|
66
|
-
if (sev > 0) severeFound = true;
|
|
67
|
-
if (!opts.json) printSummary(t.name, findings);
|
|
68
|
-
writeReport(t, findings);
|
|
69
|
-
recordLastRun(t.name, 'lint', sev > 0 ? 11 : 0);
|
|
70
|
-
}
|
|
47
|
+
const target = requireVault();
|
|
48
|
+
const findings = lintOne(target);
|
|
49
|
+
const sev =
|
|
50
|
+
findings.missingFrontmatter.length +
|
|
51
|
+
findings.missingRequiredKey.length +
|
|
52
|
+
findings.brokenWikilinks.length +
|
|
53
|
+
findings.missingRawSource.length;
|
|
54
|
+
const severeFound = sev > 0;
|
|
55
|
+
if (!opts.json) printSummary(target.name, findings);
|
|
56
|
+
writeReport(target, findings);
|
|
57
|
+
recordLastRun(target.name, 'lint', severeFound ? 11 : 0);
|
|
71
58
|
|
|
72
59
|
if (opts.json) {
|
|
73
60
|
// eslint-disable-next-line no-console
|
|
74
|
-
console.log(JSON.stringify({
|
|
61
|
+
console.log(JSON.stringify({
|
|
62
|
+
ok: !severeFound, code: severeFound ? 11 : 0,
|
|
63
|
+
data: [{ wiki: target.name, findings }],
|
|
64
|
+
}, null, 2));
|
|
75
65
|
}
|
|
76
66
|
if (severeFound) process.exit(11);
|
|
77
67
|
}
|
|
@@ -89,7 +79,7 @@ function lintOne(wiki: WikiEntry): Findings {
|
|
|
89
79
|
|
|
90
80
|
const pages: PageMeta[] = [];
|
|
91
81
|
for (const sub of WIKI_SUBDIRS) {
|
|
92
|
-
const dir = path.join(wiki.path,
|
|
82
|
+
const dir = path.join(wiki.path, sub);
|
|
93
83
|
if (!fs.existsSync(dir)) continue;
|
|
94
84
|
for (const name of fs.readdirSync(dir)) {
|
|
95
85
|
if (!name.endsWith('.md') || name === '.gitkeep') continue;
|
package/lib/wiki/paths.ts
CHANGED
|
@@ -6,7 +6,6 @@ export const RIG_HOME = process.env.RIG_HOME || path.join(os.homedir(), '.rig');
|
|
|
6
6
|
export const paths = {
|
|
7
7
|
home: RIG_HOME,
|
|
8
8
|
config: path.join(RIG_HOME, 'config.yml'),
|
|
9
|
-
registry: path.join(RIG_HOME, 'wikis.yml'),
|
|
10
9
|
stateDb: path.join(RIG_HOME, 'state.db'),
|
|
11
10
|
locks: path.join(RIG_HOME, 'locks'),
|
|
12
11
|
logs: path.join(RIG_HOME, 'logs'),
|
package/lib/wiki/query.ts
CHANGED
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import print from '../print';
|
|
13
|
-
import {
|
|
13
|
+
import { requireVault, loadRigConfig, WikiEntry } from './config';
|
|
14
14
|
import { qmdQuery, QmdHit } from './qmd';
|
|
15
15
|
import { adapters } from './agent/registry';
|
|
16
16
|
|
|
17
17
|
interface QueryOpts {
|
|
18
|
-
wiki?: string;
|
|
19
18
|
json?: boolean;
|
|
20
19
|
limit?: number;
|
|
21
20
|
synth?: boolean;
|
|
@@ -28,12 +27,7 @@ export default async function wikiQuery(q: string, opts: QueryOpts): Promise<voi
|
|
|
28
27
|
print.error('empty query.');
|
|
29
28
|
process.exit(1);
|
|
30
29
|
}
|
|
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
|
-
}
|
|
30
|
+
const target = requireVault();
|
|
37
31
|
|
|
38
32
|
const limit = Math.max(1, Math.min(50, opts.limit || 10));
|
|
39
33
|
const hits = await qmdQuery(q, target.name, { limit, rerank: opts.rerank !== false });
|
|
@@ -78,13 +72,15 @@ function printHits(wiki: WikiEntry, q: string, hits: QmdHit[]): void {
|
|
|
78
72
|
console.log('');
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
// "/abs
|
|
82
|
-
// caller falls back to
|
|
75
|
+
// "/abs/<vault>/sources/foo.md" → "foo". For paths outside the page-tree
|
|
76
|
+
// subdirs returns null so the caller falls back to the literal path.
|
|
77
|
+
const PAGE_SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
|
|
83
78
|
function toWikilink(wiki: WikiEntry, filePath: string): string | null {
|
|
84
79
|
try {
|
|
85
80
|
const abs = path.isAbsolute(filePath) ? filePath : path.resolve(wiki.path, filePath);
|
|
86
|
-
const
|
|
87
|
-
|
|
81
|
+
const rel = path.relative(wiki.path, abs);
|
|
82
|
+
const first = rel.split(path.sep)[0];
|
|
83
|
+
if (!PAGE_SUBDIRS.includes(first)) return null;
|
|
88
84
|
return path.basename(abs, path.extname(abs));
|
|
89
85
|
} catch { return null; }
|
|
90
86
|
}
|
package/lib/wiki/rebuild.ts
CHANGED
|
@@ -13,44 +13,33 @@
|
|
|
13
13
|
// - full re-embed (Qwen3-Embedding by default)
|
|
14
14
|
|
|
15
15
|
import print from '../print';
|
|
16
|
-
import {
|
|
16
|
+
import { requireVault } from './config';
|
|
17
17
|
import { getDb, recordLastRun } from './db';
|
|
18
18
|
import { qmdEmbed, qmdResetStore } from './qmd';
|
|
19
19
|
|
|
20
|
-
interface RebuildOpts {
|
|
20
|
+
interface RebuildOpts { skipEmbed?: boolean; }
|
|
21
21
|
|
|
22
22
|
export default async function wikiRebuild(opts: RebuildOpts): Promise<void> {
|
|
23
|
-
const
|
|
24
|
-
const targets: WikiEntry[] = opts.all
|
|
25
|
-
? cfg.wikis
|
|
26
|
-
: [resolveWiki(cfg, opts.wiki)].filter(Boolean) as WikiEntry[];
|
|
27
|
-
if (targets.length === 0) {
|
|
28
|
-
print.error('no wiki resolved. Pass --wiki <name>, --all, or run from inside a registered project.');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
23
|
+
const t = requireVault();
|
|
32
24
|
const db = getDb();
|
|
33
|
-
for (const t of targets) {
|
|
34
|
-
print.start(`rebuild: ${t.name}`);
|
|
35
|
-
const del = db.prepare('DELETE FROM source_sha WHERE wiki = ?').run(t.name);
|
|
36
|
-
print.info(` cleared ${del.changes} source_sha rows for ${t.name}`);
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
print.start(`rebuild: ${t.name}`);
|
|
27
|
+
const del = db.prepare('DELETE FROM source_sha WHERE wiki = ?').run(t.name);
|
|
28
|
+
print.info(` cleared ${del.changes} source_sha rows for ${t.name}`);
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
const res = await qmdEmbed(t.name, t.path, { force: true });
|
|
42
|
-
if (res.ok) print.info(` qmd embed: ${t.name} done`);
|
|
43
|
-
else {
|
|
44
|
-
print.error(` qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
|
|
45
|
-
recordLastRun(t.name, 'rebuild', 1);
|
|
46
|
-
process.exitCode = 1;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
30
|
+
qmdResetStore(t.name);
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
32
|
+
if (!opts.skipEmbed) {
|
|
33
|
+
const res = await qmdEmbed(t.name, t.path, { force: true });
|
|
34
|
+
if (res.ok) print.info(` qmd embed: ${t.name} done`);
|
|
35
|
+
else {
|
|
36
|
+
print.error(` qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
|
|
37
|
+
recordLastRun(t.name, 'rebuild', 1);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
53
40
|
}
|
|
54
41
|
|
|
42
|
+
recordLastRun(t.name, 'rebuild', 0);
|
|
43
|
+
print.succeed(`rebuilt: ${t.name}`);
|
|
55
44
|
print.info('next: run `rig wiki scan` to baseline the new sha index.');
|
|
56
45
|
}
|
package/lib/wiki/scan.ts
CHANGED
|
Binary file
|
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import print from '../print';
|
|
4
5
|
import { paths } from './paths';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
const targetDir = path.join(paths.claudeSkillsDir, 'rig-wiki');
|
|
8
|
-
const target = path.join(targetDir, 'SKILL.md');
|
|
7
|
+
const BUNDLED_SKILLS = ['rig-wiki', 'rig-crew'];
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
interface UninstallOpts { project?: boolean; }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Remove bundled-skill symlinks. Mirrors `installSkill` flags:
|
|
13
|
+
* default — `~/.claude/skills/<name>/SKILL.md`
|
|
14
|
+
* `--project` — both `<cwd>/.claude/skills/<name>/SKILL.md`
|
|
15
|
+
* and `<cwd>/.agents/skills/<name>/SKILL.md`
|
|
16
|
+
*/
|
|
17
|
+
export default function wikiUninstallSkill(opts: UninstallOpts): void {
|
|
18
|
+
const parents = opts.project
|
|
19
|
+
? [
|
|
20
|
+
path.join(process.cwd(), '.claude', 'skills'),
|
|
21
|
+
path.join(process.cwd(), '.agents', 'skills'),
|
|
22
|
+
]
|
|
23
|
+
: [paths.claudeSkillsDir];
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
let removed = 0;
|
|
26
|
+
for (const parent of parents) {
|
|
27
|
+
for (const name of BUNDLED_SKILLS) {
|
|
28
|
+
const targetDir = path.join(parent, name);
|
|
29
|
+
const target = path.join(targetDir, 'SKILL.md');
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(target) || isBrokenSymlink(target)) {
|
|
32
|
+
fs.rmSync(target, { force: true });
|
|
33
|
+
print.succeed(`removed ${shortPath(target)}`);
|
|
34
|
+
removed++;
|
|
23
35
|
}
|
|
24
|
-
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(targetDir)) {
|
|
38
|
+
try {
|
|
39
|
+
if (fs.readdirSync(targetDir).length === 0) {
|
|
40
|
+
fs.rmdirSync(targetDir);
|
|
41
|
+
}
|
|
42
|
+
} catch { /* non-fatal */ }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
if (
|
|
47
|
+
if (removed === 0) print.info('nothing to remove.');
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
function isBrokenSymlink(p: string): boolean {
|
|
@@ -35,3 +55,9 @@ function isBrokenSymlink(p: string): boolean {
|
|
|
35
55
|
try { return Boolean(fs.readlinkSync(p)); } catch { return false; }
|
|
36
56
|
}
|
|
37
57
|
}
|
|
58
|
+
|
|
59
|
+
function shortPath(p: string): string {
|
|
60
|
+
const home = os.homedir();
|
|
61
|
+
if (p.startsWith(home + path.sep)) return '~' + p.slice(home.length);
|
|
62
|
+
return p;
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rigjs",
|
|
3
|
-
"version": "4.0.
|
|
4
|
-
"versionCode":
|
|
3
|
+
"version": "4.0.7",
|
|
4
|
+
"versionCode": 26052414,
|
|
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",
|