@lifeaitools/rdc-skills 0.31.0 → 0.31.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdc",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code — plan, build, review, overnight unattended builds with work-item tracking and TDD enforcement.",
5
5
  "author": {
6
6
  "name": "LIFEAI",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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 repoRoot = path.resolve(new URL('.', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'), '..');
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
- // ── Source of truth — origin/master, FRESHLY FETCHED. Never local disk: a
77
- // checkout can silently sit behind origin (this is the exact failure the
78
- // "canonical fallback" repo had this session 31 commits behind while being
79
- // treated as the source of truth). ──────────────────────────────────────────
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 origin/master`
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 pass = status === 'ok' && shaMatch && countMatch;
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 origin/master`
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: origin/master @ ${truth.shortSha}, v${truth.version}, ${truth.skillCount} skills`);
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';
@@ -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' });
@@ -86,4 +87,29 @@ try {
86
87
  rmSync(codexSkills, { recursive: true, force: true });
87
88
  }
88
89
 
90
+ const packagedRoot = mkdtempSync(join(tmpdir(), 'rdc-packaged-truth-'));
91
+ try {
92
+ writeFileSync(join(packagedRoot, 'package.json'), JSON.stringify({ version: '9.9.9' }));
93
+ mkdirSync(join(packagedRoot, 'skills', 'first'), { recursive: true });
94
+ writeFileSync(join(packagedRoot, 'skills', 'first', 'SKILL.md'), '---\nname: rdc:first\n---\n');
95
+ mkdirSync(join(packagedRoot, 'skills', 'second'), { recursive: true });
96
+ writeFileSync(join(packagedRoot, 'skills', 'second', 'SKILL.md'), '---\nname: rdc:second\n---\n');
97
+
98
+ const packagedTruth = spawnSync(process.execPath, [liveVerifier, '--self-test-source-of-truth'], {
99
+ cwd: REPO_ROOT,
100
+ encoding: 'utf8',
101
+ env: { ...process.env, RDC_SKILLS_ROOT: packagedRoot },
102
+ });
103
+ assert.equal(packagedTruth.status, 0, `${packagedTruth.stdout}\n${packagedTruth.stderr}`);
104
+ assert.deepEqual(JSON.parse(packagedTruth.stdout), {
105
+ sha: null,
106
+ shortSha: 'package',
107
+ version: '9.9.9',
108
+ skillCount: 2,
109
+ authority: 'installed npm package',
110
+ });
111
+ } finally {
112
+ rmSync(packagedRoot, { recursive: true, force: true });
113
+ }
114
+
89
115
  console.log('install-rdc-skills tests — PASS');