@lifeaitools/rdc-skills 0.31.0 → 0.31.2
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.
|
@@ -10,6 +10,12 @@ if [[ "$repo_root" != "/srv/regen/rdc-skills" ]]; then
|
|
|
10
10
|
exit 1
|
|
11
11
|
fi
|
|
12
12
|
|
|
13
|
+
cd "$repo_root"
|
|
14
|
+
# The MCP imports declared runtime dependencies (including express). A Git
|
|
15
|
+
# checkout alone cannot satisfy those imports after a clean host or dependency
|
|
16
|
+
# cleanup, so install the committed production graph before the unit is enabled.
|
|
17
|
+
npm ci --omit=dev --no-audit --no-fund
|
|
18
|
+
|
|
13
19
|
install -m 0644 "$unit_source" "$unit_target"
|
|
14
20
|
systemctl daemon-reload
|
|
15
21
|
systemctl enable --now rdc-skills-mcp.service
|
package/package.json
CHANGED
|
@@ -38,7 +38,10 @@ const MCP_URL = mcpUrlIdx >= 0 ? args[mcpUrlIdx + 1] : 'https://rdc-skills.regen
|
|
|
38
38
|
const codexRootIdx = args.indexOf('--codex-root');
|
|
39
39
|
const CODEX_ROOT = codexRootIdx >= 0 ? path.resolve(args[codexRootIdx + 1]) : null;
|
|
40
40
|
|
|
41
|
-
const
|
|
41
|
+
const installedRoot = path.resolve(new URL('.', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'), '..');
|
|
42
|
+
// Tests and package consumers can supply an unpacked package root. A global npm
|
|
43
|
+
// installation has no .git directory, so it must never be treated as a checkout.
|
|
44
|
+
const repoRoot = process.env.RDC_SKILLS_ROOT ? path.resolve(process.env.RDC_SKILLS_ROOT) : installedRoot;
|
|
42
45
|
const claudeHome = path.join(os.homedir(), '.claude');
|
|
43
46
|
const PLUGIN_KEY = 'rdc-skills@rdc-skills';
|
|
44
47
|
|
|
@@ -73,11 +76,24 @@ function httpGetJson(url, timeoutMs = 8000) {
|
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
function packagedTruth() {
|
|
80
|
+
const pkg = readJson(path.join(repoRoot, 'package.json'));
|
|
81
|
+
if (!pkg.version) throw new Error(`package.json version missing at ${repoRoot}`);
|
|
82
|
+
const skillsRoot = path.join(repoRoot, 'skills');
|
|
83
|
+
const skillCount = fs.existsSync(skillsRoot)
|
|
84
|
+
? fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
85
|
+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, 'SKILL.md'))).length
|
|
86
|
+
: 0;
|
|
87
|
+
return { sha: null, shortSha: 'package', version: pkg.version, skillCount, authority: 'installed npm package' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Source of truth — origin/master when this is a checkout, otherwise the
|
|
91
|
+
// installed npm package. A global npm install is intentionally not a Git repo;
|
|
92
|
+
// requiring `git fetch` there made every successful install emit a false error.
|
|
93
|
+
// Checkout mode remains fresh-remote authoritative to catch stale source trees.
|
|
94
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
80
95
|
function sourceOfTruth() {
|
|
96
|
+
if (!fs.existsSync(path.join(repoRoot, '.git'))) return packagedTruth();
|
|
81
97
|
execSync('git fetch origin master', { cwd: repoRoot, stdio: 'pipe' });
|
|
82
98
|
const sha = execSync('git rev-parse origin/master', { cwd: repoRoot, encoding: 'utf8' }).trim();
|
|
83
99
|
const pkgRaw = execSync('git show origin/master:package.json', { cwd: repoRoot, encoding: 'utf8' });
|
|
@@ -93,7 +109,12 @@ function sourceOfTruth() {
|
|
|
93
109
|
skillCount++;
|
|
94
110
|
} catch { /* not a real skill dir */ }
|
|
95
111
|
}
|
|
96
|
-
return { sha, shortSha: sha.slice(0, 7), version, skillCount };
|
|
112
|
+
return { sha, shortSha: sha.slice(0, 7), version, skillCount, authority: 'origin/master' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (args.includes('--self-test-source-of-truth')) {
|
|
116
|
+
console.log(JSON.stringify(sourceOfTruth()));
|
|
117
|
+
process.exit(0);
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
// ── Surface 1: Claude CLI ────────────────────────────────────────────────────
|
|
@@ -110,7 +131,7 @@ function checkClaudeCli(truth) {
|
|
|
110
131
|
? fs.readdirSync(cacheSkillsDir, { withFileTypes: true })
|
|
111
132
|
.filter((e) => e.isDirectory() && fs.existsSync(path.join(cacheSkillsDir, e.name, 'SKILL.md'))).length
|
|
112
133
|
: -1;
|
|
113
|
-
const shaMatch = entry.gitCommitSha === truth.sha;
|
|
134
|
+
const shaMatch = !truth.sha || entry.gitCommitSha === truth.sha;
|
|
114
135
|
const versionMatch = entry.version === truth.version;
|
|
115
136
|
const countMatch = cacheCount === truth.skillCount;
|
|
116
137
|
const pass = shaMatch && versionMatch && countMatch;
|
|
@@ -118,7 +139,7 @@ function checkClaudeCli(truth) {
|
|
|
118
139
|
surface: 'Claude CLI',
|
|
119
140
|
pass,
|
|
120
141
|
detail: pass
|
|
121
|
-
? `v${entry.version} @ ${truth.shortSha}, ${cacheCount} skills — matches
|
|
142
|
+
? `v${entry.version} @ ${truth.shortSha}, ${cacheCount} skills — matches ${truth.authority}`
|
|
122
143
|
: `v${entry.version || '?'} @ ${(entry.gitCommitSha || '?').slice(0, 7)}, ${cacheCount} skills`
|
|
123
144
|
+ ` — expected v${truth.version} @ ${truth.shortSha}, ${truth.skillCount} skills`
|
|
124
145
|
+ `${shaMatch ? '' : ' [SHA MISMATCH]'}${versionMatch ? '' : ' [VERSION MISMATCH]'}${countMatch ? '' : ' [COUNT MISMATCH]'}`,
|
|
@@ -171,17 +192,18 @@ async function checkClaudeMcp(truth) {
|
|
|
171
192
|
return { surface: 'Claude MCP', pass: false, detail: `${MCP_URL}/health unreachable — ${res.error}` };
|
|
172
193
|
}
|
|
173
194
|
const { skills, git_sha, version, status } = res.json;
|
|
174
|
-
const shaMatch = git_sha === truth.sha;
|
|
195
|
+
const shaMatch = !truth.sha || git_sha === truth.sha;
|
|
175
196
|
const countMatch = skills === truth.skillCount;
|
|
176
|
-
const
|
|
197
|
+
const versionMatch = version === truth.version;
|
|
198
|
+
const pass = status === 'ok' && shaMatch && versionMatch && countMatch;
|
|
177
199
|
return {
|
|
178
200
|
surface: 'Claude MCP',
|
|
179
201
|
pass,
|
|
180
202
|
detail: pass
|
|
181
|
-
? `${MCP_URL}/health — v${version}, ${skills} skills @ ${truth.shortSha} — matches
|
|
203
|
+
? `${MCP_URL}/health — v${version}, ${skills} skills @ ${truth.shortSha} — matches ${truth.authority}`
|
|
182
204
|
: `${MCP_URL}/health — status=${status} v${version} skills=${skills} sha=${(git_sha || '?').slice(0, 7)}`
|
|
183
|
-
+ ` — expected ${truth.skillCount} skills @ ${truth.shortSha}`
|
|
184
|
-
+ `${shaMatch ? '' : ' [SHA MISMATCH]'}${countMatch ? '' : ' [COUNT MISMATCH]'}`,
|
|
205
|
+
+ ` — expected v${truth.version}, ${truth.skillCount} skills @ ${truth.shortSha}`
|
|
206
|
+
+ `${shaMatch ? '' : ' [SHA MISMATCH]'}${versionMatch ? '' : ' [VERSION MISMATCH]'}${countMatch ? '' : ' [COUNT MISMATCH]'}`,
|
|
185
207
|
};
|
|
186
208
|
}
|
|
187
209
|
|
|
@@ -221,7 +243,7 @@ async function main() {
|
|
|
221
243
|
console.log(JSON.stringify({ truth, rows, allPass }, null, 2));
|
|
222
244
|
} else {
|
|
223
245
|
console.log('');
|
|
224
|
-
console.log(` Source of truth:
|
|
246
|
+
console.log(` Source of truth: ${truth.authority} @ ${truth.shortSha}, v${truth.version}, ${truth.skillCount} skills`);
|
|
225
247
|
console.log('');
|
|
226
248
|
for (const r of rows) {
|
|
227
249
|
const mark = r.pass ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const REPO_ROOT = resolve(__dirname, '..');
|
|
12
12
|
const script = join(REPO_ROOT, 'scripts', 'install-rdc-skills.js');
|
|
13
|
+
const liveVerifier = join(REPO_ROOT, 'scripts', 'verify-live-install.mjs');
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
|
|
15
16
|
const syntax = spawnSync(process.execPath, ['--check', script], { encoding: 'utf8' });
|
|
@@ -30,6 +31,16 @@ assert.equal(
|
|
|
30
31
|
packageJson.version,
|
|
31
32
|
'plugin manifest version must match the published package version',
|
|
32
33
|
);
|
|
34
|
+
const packageLockPath = join(REPO_ROOT, 'package-lock.json');
|
|
35
|
+
assert.equal(existsSync(packageLockPath), true, 'systemd deployment requires a committed package-lock.json for npm ci');
|
|
36
|
+
const packageLock = JSON.parse(readFileSync(packageLockPath, 'utf8'));
|
|
37
|
+
assert.equal(packageLock.version, packageJson.version, 'package-lock version must match package.json');
|
|
38
|
+
const systemdInstaller = readFileSync(join(REPO_ROOT, 'deploy', 'install-systemd.sh'), 'utf8');
|
|
39
|
+
assert.match(
|
|
40
|
+
systemdInstaller,
|
|
41
|
+
/npm ci --omit=dev --no-audit --no-fund/,
|
|
42
|
+
'systemd installer must install the locked production dependency graph before starting the MCP',
|
|
43
|
+
);
|
|
33
44
|
const skillCount = Array.isArray(plugin.skills_meta)
|
|
34
45
|
? plugin.skills_meta.length
|
|
35
46
|
: Object.keys(plugin.skills_meta || {}).length;
|
|
@@ -86,4 +97,29 @@ try {
|
|
|
86
97
|
rmSync(codexSkills, { recursive: true, force: true });
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
const packagedRoot = mkdtempSync(join(tmpdir(), 'rdc-packaged-truth-'));
|
|
101
|
+
try {
|
|
102
|
+
writeFileSync(join(packagedRoot, 'package.json'), JSON.stringify({ version: '9.9.9' }));
|
|
103
|
+
mkdirSync(join(packagedRoot, 'skills', 'first'), { recursive: true });
|
|
104
|
+
writeFileSync(join(packagedRoot, 'skills', 'first', 'SKILL.md'), '---\nname: rdc:first\n---\n');
|
|
105
|
+
mkdirSync(join(packagedRoot, 'skills', 'second'), { recursive: true });
|
|
106
|
+
writeFileSync(join(packagedRoot, 'skills', 'second', 'SKILL.md'), '---\nname: rdc:second\n---\n');
|
|
107
|
+
|
|
108
|
+
const packagedTruth = spawnSync(process.execPath, [liveVerifier, '--self-test-source-of-truth'], {
|
|
109
|
+
cwd: REPO_ROOT,
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
env: { ...process.env, RDC_SKILLS_ROOT: packagedRoot },
|
|
112
|
+
});
|
|
113
|
+
assert.equal(packagedTruth.status, 0, `${packagedTruth.stdout}\n${packagedTruth.stderr}`);
|
|
114
|
+
assert.deepEqual(JSON.parse(packagedTruth.stdout), {
|
|
115
|
+
sha: null,
|
|
116
|
+
shortSha: 'package',
|
|
117
|
+
version: '9.9.9',
|
|
118
|
+
skillCount: 2,
|
|
119
|
+
authority: 'installed npm package',
|
|
120
|
+
});
|
|
121
|
+
} finally {
|
|
122
|
+
rmSync(packagedRoot, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
89
125
|
console.log('install-rdc-skills tests — PASS');
|