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/scripts/publish.mjs
CHANGED
|
@@ -90,4 +90,63 @@ try {
|
|
|
90
90
|
} finally {
|
|
91
91
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
if (status === 0) {
|
|
95
|
+
await syncNpmmirror(pkg.name);
|
|
96
|
+
}
|
|
93
97
|
process.exit(status);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Tell registry.npmmirror.com to pull the newly published version from
|
|
101
|
+
* upstream npmjs. Fire the POST kickoff, then poll the sync log a few
|
|
102
|
+
* times — we don't block release on completion, just surface progress.
|
|
103
|
+
*
|
|
104
|
+
* Failures (network, 5xx, timeout) are warnings, not hard errors: the
|
|
105
|
+
* publish itself succeeded; the mirror will catch up on its own.
|
|
106
|
+
*/
|
|
107
|
+
async function syncNpmmirror(pkgName) {
|
|
108
|
+
const slug = encodeURIComponent(pkgName);
|
|
109
|
+
const kickoffUrl = `https://registry.npmmirror.com/-/package/${slug}/syncs?sync_upstream=true`;
|
|
110
|
+
process.stdout.write(`\nsyncing ${pkgName} to npmmirror.com...\n`);
|
|
111
|
+
|
|
112
|
+
let logId;
|
|
113
|
+
try {
|
|
114
|
+
const r = await fetch(kickoffUrl, { method: 'PUT' });
|
|
115
|
+
if (!r.ok) {
|
|
116
|
+
process.stderr.write(` kickoff failed (HTTP ${r.status}) — mirror will sync on its own schedule\n`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const body = await r.json();
|
|
120
|
+
logId = body.logId || body.id;
|
|
121
|
+
if (!logId) {
|
|
122
|
+
process.stdout.write(` kicked off (no logId returned, treating as fire-and-forget)\n`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
process.stdout.write(` kicked off (logId=${logId})\n`);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
process.stderr.write(` kickoff error: ${e.message} — mirror will sync on its own schedule\n`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Poll the sync log: 3s interval, give up after 60s.
|
|
132
|
+
const statusUrl = `https://registry.npmmirror.com/-/package/${slug}/syncs/${logId}`;
|
|
133
|
+
const start = Date.now();
|
|
134
|
+
const TIMEOUT_MS = 60_000;
|
|
135
|
+
while (Date.now() - start < TIMEOUT_MS) {
|
|
136
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
137
|
+
try {
|
|
138
|
+
const r = await fetch(statusUrl);
|
|
139
|
+
if (!r.ok) continue;
|
|
140
|
+
const s = await r.json();
|
|
141
|
+
if (s.syncDone || s.state === 'success') {
|
|
142
|
+
process.stdout.write(` npmmirror sync complete\n`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (s.state === 'fail') {
|
|
146
|
+
process.stderr.write(` npmmirror sync failed: ${s.error || '(no error message)'}\n`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
} catch { /* transient — keep polling */ }
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write(` still in progress after ${TIMEOUT_MS / 1000}s — npmmirror will finish in the background\n`);
|
|
152
|
+
}
|
package/skills.md
CHANGED
|
@@ -15,22 +15,49 @@ Keep the root `README.md` short: it should link here and to the canonical skill
|
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
### Global install (default — affects every project on the machine)
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
21
|
+
yarn global add rigjs
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The `postinstall` script links bundled skills into `~/.claude/skills/`
|
|
25
|
-
|
|
26
|
-
Security-conscious install:
|
|
24
|
+
The `postinstall` script links bundled skills into `~/.claude/skills/` (Claude Code's user-level skill directory). If you prefer to skip the postinstall:
|
|
27
25
|
|
|
28
26
|
```bash
|
|
29
|
-
|
|
27
|
+
yarn global add rigjs --ignore-scripts
|
|
30
28
|
rig wiki install-skill
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
### Project-level install (per-project override, Claude Code + Codex)
|
|
32
|
+
|
|
33
|
+
For "monorepo of work projects" setups — e.g. overmind — you can install the skills **into the project itself** so they live alongside the code and override the global ones whenever the user is inside that project:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd <project>
|
|
37
|
+
rig wiki install-skill --project
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This creates symlinks at:
|
|
41
|
+
|
|
42
|
+
- `<project>/.claude/skills/rig-wiki/SKILL.md` → `<rigjs-install>/RIG_WIKI_SKILL.md`
|
|
43
|
+
- `<project>/.claude/skills/rig-crew/SKILL.md` → `<rigjs-install>/RIG_CREW_SKILL.md`
|
|
44
|
+
- `<project>/.agents/skills/rig-wiki/SKILL.md` → (same target, for Codex)
|
|
45
|
+
- `<project>/.agents/skills/rig-crew/SKILL.md` → (same target, for Codex)
|
|
46
|
+
|
|
47
|
+
Both Claude Code (`.claude/skills/`) and Codex (`.agents/skills/`) read from project-local skill dirs when invoked inside the project, so a single `--project` install covers both agents.
|
|
48
|
+
|
|
49
|
+
Project-local skills take precedence over `~/.claude/skills/` while the user is in that project. To remove:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd <project>
|
|
53
|
+
rig wiki uninstall-skill --project
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Why project-level over global
|
|
57
|
+
|
|
58
|
+
- Pins the skill version to the rigjs install in `node_modules` (or wherever the global rig lives), so the skill the agent sees matches the CLI it's about to call.
|
|
59
|
+
- Lets the project decide which agent gets which skill — committing `.claude/skills/rig-wiki/` to the repo makes the agent behaviour reproducible across machines.
|
|
60
|
+
- Works in CI / sandboxes where there's no home-dir `~/.claude/skills/` to install into.
|
|
34
61
|
|
|
35
62
|
## Maintenance
|
|
36
63
|
|
package/lib/wiki/list.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import print from '../print';
|
|
4
|
-
import { loadWikiConfig, loadRigConfig } from './config';
|
|
5
|
-
import { getLastRun } from './db';
|
|
6
|
-
import { detectQmd } from './qmd';
|
|
7
|
-
import { adapters } from './agent/registry';
|
|
8
|
-
|
|
9
|
-
export default async function wikiList(): Promise<void> {
|
|
10
|
-
const cfg = loadWikiConfig();
|
|
11
|
-
const rig = loadRigConfig();
|
|
12
|
-
if (cfg.wikis.length === 0) {
|
|
13
|
-
print.info('no wikis registered. Use `rig wiki register [<path>]` to add one.');
|
|
14
|
-
} else {
|
|
15
|
-
const rows = cfg.wikis.map(w => ({
|
|
16
|
-
name: w.name,
|
|
17
|
-
path: shortPath(w.path),
|
|
18
|
-
pages: countPages(w.path),
|
|
19
|
-
lastScan: fmtTs(getLastRun(w.name, 'scan')?.ts),
|
|
20
|
-
lastIngest: fmtTs(getLastRun(w.name, 'ingest')?.ts),
|
|
21
|
-
lastLint: fmtTs(getLastRun(w.name, 'lint')?.ts),
|
|
22
|
-
}));
|
|
23
|
-
const header = ['NAME', 'PATH', 'PAGES', 'LAST SCAN', 'LAST INGEST', 'LAST LINT'];
|
|
24
|
-
printTable(header, rows.map(r => [r.name, r.path, String(r.pages), r.lastScan, r.lastIngest, r.lastLint]));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const qmd = detectQmd();
|
|
28
|
-
const defaultAgent = rig.wiki?.defaultAgent || 'claude';
|
|
29
|
-
const agentDetect = await adapters.find(a => a.name === defaultAgent)?.detect();
|
|
30
|
-
// eslint-disable-next-line no-console
|
|
31
|
-
console.log(`\nagent: ${defaultAgent}${agentDetect?.installed ? ` (${agentDetect.version || 'installed'})` : ' (NOT installed)'}` +
|
|
32
|
-
` qmd: ${qmd.installed ? qmd.version || 'installed' : 'not installed (fallback mode)'}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function countPages(wikiDir: string): number {
|
|
36
|
-
const wiki = path.join(wikiDir, 'wiki');
|
|
37
|
-
if (!fs.existsSync(wiki)) return 0;
|
|
38
|
-
let n = 0;
|
|
39
|
-
for (const sub of ['sources', 'entities', 'concepts', 'synthesis', 'queries']) {
|
|
40
|
-
const d = path.join(wiki, sub);
|
|
41
|
-
if (!fs.existsSync(d)) continue;
|
|
42
|
-
for (const f of fs.readdirSync(d)) if (f.endsWith('.md') && f !== '.gitkeep') n++;
|
|
43
|
-
}
|
|
44
|
-
return n;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function shortPath(p: string): string {
|
|
48
|
-
const home = process.env.HOME || '';
|
|
49
|
-
return home && p.startsWith(home) ? '~' + p.slice(home.length) : p;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function fmtTs(ts?: number): string {
|
|
53
|
-
if (!ts) return '—';
|
|
54
|
-
const d = new Date(ts);
|
|
55
|
-
return d.toISOString().replace('T', ' ').slice(0, 16);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function printTable(header: string[], rows: string[][]): void {
|
|
59
|
-
const widths = header.map((h, i) => Math.max(h.length, ...rows.map(r => (r[i] || '').length)));
|
|
60
|
-
const fmt = (cells: string[]) => cells.map((c, i) => (c || '').padEnd(widths[i])).join(' ');
|
|
61
|
-
// eslint-disable-next-line no-console
|
|
62
|
-
console.log(fmt(header));
|
|
63
|
-
// eslint-disable-next-line no-console
|
|
64
|
-
console.log(widths.map(w => '-'.repeat(w)).join(' '));
|
|
65
|
-
for (const r of rows) {
|
|
66
|
-
// eslint-disable-next-line no-console
|
|
67
|
-
console.log(fmt(r));
|
|
68
|
-
}
|
|
69
|
-
}
|
package/lib/wiki/register.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import print from '../print';
|
|
4
|
-
import {
|
|
5
|
-
loadRegistry,
|
|
6
|
-
saveRegistry,
|
|
7
|
-
loadVaultConfig,
|
|
8
|
-
saveVaultConfig,
|
|
9
|
-
VaultConfig,
|
|
10
|
-
} from './config';
|
|
11
|
-
|
|
12
|
-
interface RegisterOpts {
|
|
13
|
-
as?: string;
|
|
14
|
-
force?: boolean;
|
|
15
|
-
}
|
|
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
|
-
*/
|
|
28
|
-
export default function wikiRegister(givenPath: string | undefined, opts: RegisterOpts): void {
|
|
29
|
-
const vaultPath = path.resolve(givenPath || detectVaultPath(process.cwd()) || process.cwd());
|
|
30
|
-
if (!fs.existsSync(vaultPath)) {
|
|
31
|
-
print.error(`path does not exist: ${vaultPath}`);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
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.`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
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>');
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
|
|
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
|
-
}
|
|
65
|
-
|
|
66
|
-
vault.name = desiredName;
|
|
67
|
-
saveVaultConfig(vaultPath, vault);
|
|
68
|
-
|
|
69
|
-
if (!reg.wikis.includes(vaultPath)) reg.wikis.push(vaultPath);
|
|
70
|
-
saveRegistry(reg);
|
|
71
|
-
|
|
72
|
-
print.succeed(`registered wiki "${desiredName}" -> ${vaultPath}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function detectVaultPath(start: string): string | undefined {
|
|
76
|
-
const candidates = ['harness/llm-wiki', 'wiki'];
|
|
77
|
-
let dir = start;
|
|
78
|
-
while (true) {
|
|
79
|
-
if (fs.existsSync(path.join(dir, 'purpose.md'))) return dir;
|
|
80
|
-
for (const c of candidates) {
|
|
81
|
-
const cand = path.join(dir, c);
|
|
82
|
-
if (fs.existsSync(path.join(cand, 'purpose.md'))) return cand;
|
|
83
|
-
}
|
|
84
|
-
const parent = path.dirname(dir);
|
|
85
|
-
if (parent === dir) return undefined;
|
|
86
|
-
dir = parent;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
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
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function shortPath(p: string): string {
|
|
102
|
-
const home = process.env.HOME || '';
|
|
103
|
-
return home && p.startsWith(home) ? '~' + p.slice(home.length) : p;
|
|
104
|
-
}
|
package/lib/wiki/unregister.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import print from '../print';
|
|
3
|
-
import { loadRegistry, saveRegistry, loadVaultConfig } from './config';
|
|
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
|
-
*/
|
|
10
|
-
export default function wikiUnregister(nameOrPath: string): void {
|
|
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) {
|
|
23
|
-
print.error(`no registered wiki matches "${nameOrPath}"`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
saveRegistry(reg);
|
|
27
|
-
print.succeed(`unregistered "${nameOrPath}" (disk contents untouched)`);
|
|
28
|
-
}
|