@lifeaitools/rdc-skills 0.15.0 → 0.17.0
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-plugin/plugin.json +1 -1
- package/bin/rdc-skills-mcp.mjs +238 -0
- package/guides/lessons-learned-spec.md +153 -0
- package/lib/catalog.mjs +174 -0
- package/lib/cloud-rewrite.mjs +155 -0
- package/package.json +10 -4
- package/scaffold/templates/brochure-studio-default.html +70 -0
- package/scripts/install-rdc-skills.js +65 -0
- package/scripts/rebuild-mcp.mjs +107 -0
- package/skills/build/SKILL.md +4 -0
- package/skills/collab/SKILL.md +4 -0
- package/skills/deploy/SKILL.md +4 -0
- package/skills/fixit/SKILL.md +4 -0
- package/skills/housekeeping/SKILL.md +10 -0
- package/skills/lifeai-brochure-author/SKILL.md +338 -0
- package/skills/overnight/SKILL.md +4 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/preplan/SKILL.md +4 -0
- package/skills/rdc-brochurify/SKILL.md +238 -0
- package/skills/rdc-extract-verifier-rules/SKILL.md +189 -0
- package/skills/release/SKILL.md +4 -0
- package/skills/review/SKILL.md +4 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/cloud-rewrite.mjs — transform a CLI SKILL.md body into a cloud
|
|
3
|
+
* (claude.ai web) body.
|
|
4
|
+
*
|
|
5
|
+
* The CLI bodies assume a Claude Code session on Dave's Windows host: a local
|
|
6
|
+
* clauth HTTP daemon at 127.0.0.1:52437, a local shell, PM2, git, pnpm, and
|
|
7
|
+
* local node scripts. The claude.ai web client has NONE of those — it reaches
|
|
8
|
+
* credentials through the clauth MCP and the filesystem through the File System
|
|
9
|
+
* MCP, and it cannot run local processes at all.
|
|
10
|
+
*
|
|
11
|
+
* `toCloudBody(markdown)` applies rule-based rewrites:
|
|
12
|
+
* 1. clauth daemon curl → "retrieve `<svc>` via the clauth MCP"
|
|
13
|
+
* 2. local shell/Bash fs ops + local `fs` → File System MCP tools
|
|
14
|
+
* 3. CLI-only mechanics (PM2 reload, git push, local node scripts, daemon
|
|
15
|
+
* restart, pnpm) → annotated with a callout (NOT deleted — the cloud
|
|
16
|
+
* caller needs to know to hand that step to a Claude Code session).
|
|
17
|
+
*
|
|
18
|
+
* The rewriter is intentionally rule-based and conservative: it annotates rather
|
|
19
|
+
* than rips out, so no information is lost. A hand-tuned `SKILL.cloud.md` (handled
|
|
20
|
+
* by the caller in catalog.mjs) bypasses this entirely.
|
|
21
|
+
*
|
|
22
|
+
* Contract: the cloud body for `deploy` MUST NOT contain `127.0.0.1:52437`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const CLI_CALLOUT = '> ⚠ CLI-only step — hand this to a Claude Code session.';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Rule 1 — clauth daemon credential retrieval.
|
|
29
|
+
* Matches `curl ... http://127.0.0.1:52437/v/<svc>` (and /get/<svc>), with or
|
|
30
|
+
* without flags, optionally wrapped in $(...). Collapses to instruction text.
|
|
31
|
+
*/
|
|
32
|
+
function rewriteClauthCurls(md) {
|
|
33
|
+
let out = md;
|
|
34
|
+
|
|
35
|
+
// `_VAR=$(curl ... 127.0.0.1:52437/v/<svc>)` → assignment-style instruction
|
|
36
|
+
out = out.replace(
|
|
37
|
+
/([A-Za-z_][A-Za-z0-9_]*)=\$\(\s*curl[^\n)]*?(?:127\.0\.0\.1|localhost):52437\/(?:v|get)\/([A-Za-z0-9._-]+)[^\n)]*\)/g,
|
|
38
|
+
(_m, varName, svc) => `# ${varName}: retrieve \`${svc}\` via the clauth MCP (clauth_inject / clauth_get)`,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// bare `curl ... 127.0.0.1:52437/v/<svc>` → instruction
|
|
42
|
+
out = out.replace(
|
|
43
|
+
/curl[^\n]*?(?:127\.0\.0\.1|localhost):52437\/(?:v|get)\/([A-Za-z0-9._-]+)[^\n]*/g,
|
|
44
|
+
(_m, svc) => `retrieve \`${svc}\` via the clauth MCP (clauth_inject / clauth_get)`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// any remaining ping/list/other daemon references → MCP instruction
|
|
48
|
+
out = out.replace(
|
|
49
|
+
/curl[^\n]*?(?:127\.0\.0\.1|localhost):52437\/(?:ping|list-services|status|meta)[^\n]*/g,
|
|
50
|
+
'check clauth health via the clauth MCP (clauth_ping / clauth_status)',
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// belt-and-suspenders: kill any stray bare daemon URL still standing
|
|
54
|
+
out = out.replace(
|
|
55
|
+
/https?:\/\/(?:127\.0\.0\.1|localhost):52437\/(?:v|get)\/([A-Za-z0-9._-]+)/g,
|
|
56
|
+
(_m, svc) => `the clauth MCP value for \`${svc}\``,
|
|
57
|
+
);
|
|
58
|
+
out = out.replace(
|
|
59
|
+
/https?:\/\/(?:127\.0\.0\.1|localhost):52437\S*/g,
|
|
60
|
+
'the clauth MCP',
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Rule 2 — local shell/Bash file ops and a local `fs` → File System MCP.
|
|
68
|
+
* We add a one-line note rather than rewriting every cat/ls/grep, since the
|
|
69
|
+
* shapes vary wildly; the note tells the cloud caller which tools to reach for.
|
|
70
|
+
*/
|
|
71
|
+
function rewriteFsOps(md) {
|
|
72
|
+
let out = md;
|
|
73
|
+
// Direct references to a local `fs` module / node fs.
|
|
74
|
+
out = out.replace(
|
|
75
|
+
/\bnode:fs\b|\brequire\(['"]fs['"]\)\b|\bfrom ['"]fs['"]/g,
|
|
76
|
+
'the File System MCP (fs_read/fs_write/fs_glob/fs_grep)',
|
|
77
|
+
);
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Rule 3 — flag CLI-only mechanics with a callout. We annotate fenced code
|
|
83
|
+
* blocks and bullet/numbered lines that contain a CLI-only verb. The callout
|
|
84
|
+
* is inserted ABOVE the offending block/line; the content is preserved.
|
|
85
|
+
*/
|
|
86
|
+
const CLI_ONLY_PATTERNS = [
|
|
87
|
+
/\bpm2\s+(?:start|restart|reload|delete|stop|list|status)\b/i,
|
|
88
|
+
/\bgit\s+push\b/i,
|
|
89
|
+
/\bpnpm\b/i,
|
|
90
|
+
/\bnpm\s+(?:install|run|publish|ci)\b/i,
|
|
91
|
+
/\bnode\s+[A-Za-z0-9_./-]+\.(?:mjs|cjs|js)\b/i,
|
|
92
|
+
/\brestart-clauth(?:\.bat)?\b/i,
|
|
93
|
+
/\bclauth\s+(?:scrub|restart|serve|lock|unlock)\b/i,
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function isCliOnlyLine(line) {
|
|
97
|
+
return CLI_ONLY_PATTERNS.some((re) => re.test(line));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Walk the markdown line by line. Inside fenced ``` blocks, if ANY line trips a
|
|
102
|
+
* CLI-only pattern, emit one callout immediately before the fence. Outside code
|
|
103
|
+
* blocks, annotate individual list/prose lines that trip a pattern.
|
|
104
|
+
*/
|
|
105
|
+
function annotateCliOnly(md) {
|
|
106
|
+
const lines = md.split('\n');
|
|
107
|
+
const out = [];
|
|
108
|
+
let i = 0;
|
|
109
|
+
while (i < lines.length) {
|
|
110
|
+
const line = lines[i];
|
|
111
|
+
const fenceMatch = line.match(/^(\s*)```/);
|
|
112
|
+
if (fenceMatch) {
|
|
113
|
+
// Collect the whole fenced block.
|
|
114
|
+
const block = [line];
|
|
115
|
+
let j = i + 1;
|
|
116
|
+
for (; j < lines.length; j++) {
|
|
117
|
+
block.push(lines[j]);
|
|
118
|
+
if (/^\s*```/.test(lines[j])) {
|
|
119
|
+
j++;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const hasCliOnly = block.some(isCliOnlyLine);
|
|
124
|
+
if (hasCliOnly) out.push(`${fenceMatch[1]}${CLI_CALLOUT}`);
|
|
125
|
+
out.push(...block);
|
|
126
|
+
i = j;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Non-fence line: annotate list/prose lines that trip a pattern, but never
|
|
131
|
+
// double-annotate (skip if previous emitted line is already the callout).
|
|
132
|
+
if (isCliOnlyLine(line) && out[out.length - 1] !== CLI_CALLOUT) {
|
|
133
|
+
const indent = (line.match(/^(\s*)/) || ['', ''])[1];
|
|
134
|
+
out.push(`${indent}${CLI_CALLOUT}`);
|
|
135
|
+
}
|
|
136
|
+
out.push(line);
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
return out.join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Transform a CLI SKILL.md body into the cloud body.
|
|
144
|
+
* Order matters: rewrite clauth curls FIRST (they often live in code blocks that
|
|
145
|
+
* the CLI-only annotator would otherwise also flag), then fs ops, then annotate
|
|
146
|
+
* remaining CLI-only mechanics.
|
|
147
|
+
*/
|
|
148
|
+
export function toCloudBody(markdown) {
|
|
149
|
+
if (typeof markdown !== 'string' || !markdown) return markdown;
|
|
150
|
+
let out = markdown;
|
|
151
|
+
out = rewriteClauthCurls(out);
|
|
152
|
+
out = rewriteFsOps(out);
|
|
153
|
+
out = annotateCliOnly(out);
|
|
154
|
+
return out;
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifeaitools/rdc-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
26
|
"rdc-skills-install": "scripts/install-rdc-skills.js",
|
|
27
|
-
"rdc-skills-self-test": "scripts/self-test.mjs"
|
|
27
|
+
"rdc-skills-self-test": "scripts/self-test.mjs",
|
|
28
|
+
"rdc-skills-mcp": "bin/rdc-skills-mcp.mjs"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"install-rdc-skills": "node scripts/install-rdc-skills.js",
|
|
@@ -33,9 +34,14 @@
|
|
|
33
34
|
"validate": "node tests/validate-skills.js",
|
|
34
35
|
"rdc-design": "node scripts/rdc-design-cli.mjs",
|
|
35
36
|
"test:hooks": "node scripts/test-rdc-hooks.mjs",
|
|
37
|
+
"mcp": "node bin/rdc-skills-mcp.mjs",
|
|
38
|
+
"rebuild-mcp": "node scripts/rebuild-mcp.mjs",
|
|
36
39
|
"prepack": "node scripts/validate-publish-manifests.js --mode warn || true && node scripts/validate-place-histories.js --mode warn || true"
|
|
37
40
|
},
|
|
38
|
-
"
|
|
39
|
-
"
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
+
"express": "^5.0.0",
|
|
44
|
+
"yaml": "^2.9.0",
|
|
45
|
+
"zod": "^3.25.76"
|
|
40
46
|
}
|
|
41
47
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<title>{{TITLE}}</title>
|
|
6
|
+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
|
8
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,300..900;1,8..60,300..900&family=Hanken+Grotesk:ital,wght@0,300..900;1,300..900&display=swap"/>
|
|
9
|
+
<style>
|
|
10
|
+
/* TOKENS_INJECT */
|
|
11
|
+
@page { size: letter; margin: 0.6in 0.7in; }
|
|
12
|
+
:root {
|
|
13
|
+
--loam-800:#2b2218; --loam-700:#3d2f1f; --loam-600:#5c4628;
|
|
14
|
+
--loam-300:#bda079; --loam-200:#d9c4a4; --loam-100:#ece0c8;
|
|
15
|
+
--moss-700:#4f5a37; --ochre-700:#97772c; --ochre-100:#f4e9d4;
|
|
16
|
+
--linen-50:#faf8f5; --linen-100:#f4edde;
|
|
17
|
+
--char-700:#2b2e26; --char-500:#4d5145; --char-400:#6d7164;
|
|
18
|
+
--ff-display:'Source Serif 4', Georgia, serif;
|
|
19
|
+
--ff-sans:'Hanken Grotesk', system-ui, sans-serif;
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
html, body { background: white; }
|
|
23
|
+
body {
|
|
24
|
+
margin: 0; font-family: var(--ff-sans); font-size: 10.5pt; line-height: 1.55;
|
|
25
|
+
color: var(--char-700);
|
|
26
|
+
-webkit-print-color-adjust: exact; print-color-adjust: exact;
|
|
27
|
+
}
|
|
28
|
+
h1, h2, h3, h4 { font-family: var(--ff-display); color: var(--loam-800); font-weight: 600; letter-spacing: -0.005em; }
|
|
29
|
+
h1 { font-size: 28pt; line-height: 1.15; margin: 0 0 0.4em; }
|
|
30
|
+
h2 { font-size: 18pt; line-height: 1.2; margin: 1.8em 0 0.4em; padding-bottom: 0.25em; border-bottom: 1px solid var(--loam-200); }
|
|
31
|
+
h3 { font-size: 13pt; margin: 1.4em 0 0.3em; color: var(--loam-700); }
|
|
32
|
+
p { margin: 0 0 0.7em; }
|
|
33
|
+
ul, ol { margin: 0 0 0.8em 1.1em; padding: 0; }
|
|
34
|
+
li { margin: 0.15em 0; }
|
|
35
|
+
code { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 9pt; background: var(--linen-100); padding: 0.05em 0.3em; border-radius: 2px; }
|
|
36
|
+
pre { background: var(--linen-100); padding: 0.7em 0.9em; border-radius: 4px; overflow-wrap: anywhere; font-size: 8.5pt; }
|
|
37
|
+
img { max-width: 100%; height: auto; display: block; margin: 0.8em auto; }
|
|
38
|
+
strong { color: var(--loam-800); }
|
|
39
|
+
em { color: var(--char-500); }
|
|
40
|
+
|
|
41
|
+
.cover { page-break-after: always; min-height: 9in; display: flex; flex-direction: column; justify-content: space-between; padding: 0.3in 0; }
|
|
42
|
+
.cover-eyebrow { font-family: var(--ff-sans); font-size: 9pt; letter-spacing: 0.15em; text-transform: uppercase; color: var(--ochre-700); }
|
|
43
|
+
.cover-title { font-family: var(--ff-display); font-size: 44pt; line-height: 1.05; color: var(--loam-800); margin: 0.2em 0 0.4em; }
|
|
44
|
+
.cover-sub { font-family: var(--ff-display); font-style: italic; font-size: 14pt; color: var(--loam-600); max-width: 5.5in; }
|
|
45
|
+
.cover-foot { font-size: 8.5pt; color: var(--char-400); border-top: 1px solid var(--loam-200); padding-top: 0.4em; display: flex; justify-content: space-between; }
|
|
46
|
+
|
|
47
|
+
.brochure-section { page-break-before: always; }
|
|
48
|
+
.brochure-section:first-of-type { page-break-before: auto; }
|
|
49
|
+
|
|
50
|
+
@media screen {
|
|
51
|
+
body { max-width: 7.1in; margin: 0.6in auto; padding: 0 0.7in; }
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<header class="cover">
|
|
57
|
+
<div>
|
|
58
|
+
<div class="cover-eyebrow">Brochure</div>
|
|
59
|
+
<h1 class="cover-title">{{TITLE}}</h1>
|
|
60
|
+
<div class="cover-sub">Composed by rdc:brochure</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="cover-foot">
|
|
63
|
+
<span>RDC</span>
|
|
64
|
+
<span>{{TITLE}}</span>
|
|
65
|
+
</div>
|
|
66
|
+
</header>
|
|
67
|
+
|
|
68
|
+
{{SECTIONS}}
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
@@ -675,6 +675,66 @@ function buildHooksConfig(hooksDir, profile = 'core') {
|
|
|
675
675
|
return config;
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
// ── MCP server registration (non-fatal) ───────────────────────────────────────
|
|
679
|
+
// Ensures the rdc-skills MCP deps are installed, registers/starts the local MCP
|
|
680
|
+
// under PM2 as `rdc-skills-mcp` on PORT=3110, and prints the claude.ai connector
|
|
681
|
+
// line. Every failure here WARNs — it must never abort the installer.
|
|
682
|
+
function registerMcpServer() {
|
|
683
|
+
const MCP_NAME = 'rdc-skills-mcp';
|
|
684
|
+
const MCP_PORT = '3110';
|
|
685
|
+
const binPath = path.join(repoRoot, 'bin', 'rdc-skills-mcp.mjs');
|
|
686
|
+
const connector = 'https://rdc-skills.regendevcorp.com/mcp';
|
|
687
|
+
|
|
688
|
+
try {
|
|
689
|
+
// (a) ensure MCP runtime deps are present (express, @modelcontextprotocol/sdk, yaml, zod)
|
|
690
|
+
const needDeps = ['express', '@modelcontextprotocol/sdk', 'yaml', 'zod'].some((d) => {
|
|
691
|
+
try { require.resolve(d, { paths: [repoRoot] }); return false; } catch { return true; }
|
|
692
|
+
});
|
|
693
|
+
if (needDeps) {
|
|
694
|
+
try {
|
|
695
|
+
execSync('npm install --no-audit --no-fund', { cwd: repoRoot, stdio: 'pipe' });
|
|
696
|
+
ok('[7/7] MCP deps — installed');
|
|
697
|
+
} catch (e) {
|
|
698
|
+
warn(`[7/7] MCP deps — npm install failed (${e.message.split('\n')[0]}); install manually with \`npm install\``);
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
ok('[7/7] MCP deps — already present');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// (b) register/start under PM2 (tolerate pm2 missing)
|
|
705
|
+
let pm2Ok = false;
|
|
706
|
+
try { execSync('pm2 -v', { stdio: 'pipe' }); pm2Ok = true; } catch { pm2Ok = false; }
|
|
707
|
+
|
|
708
|
+
if (!pm2Ok) {
|
|
709
|
+
warn('[7/7] MCP server — pm2 not found; start manually:');
|
|
710
|
+
info(` PORT=${MCP_PORT} pm2 start ${binPath} --name ${MCP_NAME}`);
|
|
711
|
+
} else {
|
|
712
|
+
let registered = false;
|
|
713
|
+
try {
|
|
714
|
+
const jlist = JSON.parse(execSync('pm2 jlist', { encoding: 'utf8', stdio: 'pipe' }));
|
|
715
|
+
registered = Array.isArray(jlist) && jlist.some((p) => p.name === MCP_NAME);
|
|
716
|
+
} catch {}
|
|
717
|
+
try {
|
|
718
|
+
if (registered) {
|
|
719
|
+
execSync(`pm2 restart ${MCP_NAME} --update-env`, { cwd: repoRoot, stdio: 'pipe', env: { ...process.env, PORT: MCP_PORT } });
|
|
720
|
+
ok(`[7/7] MCP server — pm2 restarted ${MCP_NAME} (PORT=${MCP_PORT})`);
|
|
721
|
+
} else {
|
|
722
|
+
execSync(`pm2 start "${binPath}" --name ${MCP_NAME}`, { cwd: repoRoot, stdio: 'pipe', env: { ...process.env, PORT: MCP_PORT } });
|
|
723
|
+
ok(`[7/7] MCP server — pm2 started ${MCP_NAME} (PORT=${MCP_PORT})`);
|
|
724
|
+
}
|
|
725
|
+
} catch (e) {
|
|
726
|
+
warn(`[7/7] MCP server — pm2 start/restart failed (${e.message.split('\n')[0]})`);
|
|
727
|
+
info(` PORT=${MCP_PORT} pm2 start ${binPath} --name ${MCP_NAME}`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// (c) print the claude.ai connector line
|
|
732
|
+
info(` claude.ai connector: ${connector} (Auth: none — URL is the shared secret)`);
|
|
733
|
+
} catch (e) {
|
|
734
|
+
warn(`[7/7] MCP server — skipped (${e.message.split('\n')[0]})`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
678
738
|
// ── Preflight ─────────────────────────────────────────────────────────────────
|
|
679
739
|
function runPreflight() {
|
|
680
740
|
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
@@ -982,6 +1042,11 @@ async function main() {
|
|
|
982
1042
|
process.exit(2);
|
|
983
1043
|
}
|
|
984
1044
|
|
|
1045
|
+
// 7. MCP server registration (non-fatal — WARNs only, never aborts)
|
|
1046
|
+
console.log('');
|
|
1047
|
+
console.log(' \x1b[36mMCP server:\x1b[0m');
|
|
1048
|
+
try { registerMcpServer(); } catch (e) { warn(`[7/7] MCP server — unexpected error (${e.message})`); }
|
|
1049
|
+
|
|
985
1050
|
// Done
|
|
986
1051
|
console.log('');
|
|
987
1052
|
console.log(' \x1b[32mDone!\x1b[0m');
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* scripts/rebuild-mcp.mjs — "build hook" for the rdc-skills MCP server.
|
|
4
|
+
*
|
|
5
|
+
* The server reads skills live from disk, so a skill edit is usually picked up
|
|
6
|
+
* without a restart. This script exists to (a) bounce the PM2 process so any
|
|
7
|
+
* in-memory module cache is dropped, and (b) assert the local /health endpoint
|
|
8
|
+
* reports the current on-disk skill count — a fast smoke that the server is up
|
|
9
|
+
* and seeing the same catalog this script does.
|
|
10
|
+
*
|
|
11
|
+
* Tolerant of a missing PM2: if pm2 is not installed/registered, it prints how
|
|
12
|
+
* to start the server and exits 0. It never hard-fails the caller.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { listSkills } from '../lib/catalog.mjs';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
23
|
+
const PM2_NAME = 'rdc-skills-mcp';
|
|
24
|
+
const PORT = parseInt(process.env.PORT || '3110', 10);
|
|
25
|
+
const BIN = path.join(REPO_ROOT, 'bin', 'rdc-skills-mcp.mjs');
|
|
26
|
+
|
|
27
|
+
const ok = (m) => console.log(` \x1b[32m✓\x1b[0m ${m}`);
|
|
28
|
+
const info = (m) => console.log(` \x1b[36m→\x1b[0m ${m}`);
|
|
29
|
+
const warn = (m) => console.log(` \x1b[33m⚠\x1b[0m ${m}`);
|
|
30
|
+
|
|
31
|
+
function sh(cmd) {
|
|
32
|
+
return execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pm2Available() {
|
|
36
|
+
try {
|
|
37
|
+
sh('pm2 -v');
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pm2HasProcess() {
|
|
45
|
+
try {
|
|
46
|
+
const list = JSON.parse(sh('pm2 jlist'));
|
|
47
|
+
return Array.isArray(list) && list.some((p) => p.name === PM2_NAME);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function checkHealth(expectedSkills) {
|
|
54
|
+
// Node 22 has global fetch.
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`http://127.0.0.1:${PORT}/health`, { signal: AbortSignal.timeout(4000) });
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
warn(`/health returned HTTP ${res.status}`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const json = await res.json();
|
|
62
|
+
if (json.skills === expectedSkills) {
|
|
63
|
+
ok(`/health OK — skills=${json.skills} (matches disk), version=${json.version}`);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
warn(`/health skills=${json.skills} but disk has ${expectedSkills} — server may be stale`);
|
|
67
|
+
return false;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
warn(`/health unreachable on port ${PORT}: ${err?.message || err}`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function main() {
|
|
75
|
+
const diskSkills = listSkills().length;
|
|
76
|
+
info(`On-disk catalog: ${diskSkills} skill(s)`);
|
|
77
|
+
|
|
78
|
+
if (!pm2Available()) {
|
|
79
|
+
warn('pm2 not found — cannot bounce the MCP process.');
|
|
80
|
+
info(`Start it manually: PORT=${PORT} pm2 start ${BIN} --name ${PM2_NAME}`);
|
|
81
|
+
info(`Or run directly: PORT=${PORT} node ${BIN}`);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (pm2HasProcess()) {
|
|
86
|
+
try {
|
|
87
|
+
sh(`pm2 restart ${PM2_NAME} --update-env`);
|
|
88
|
+
ok(`pm2 restart ${PM2_NAME}`);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
warn(`pm2 restart failed: ${err?.message || err}`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
info(`pm2 process '${PM2_NAME}' not registered.`);
|
|
94
|
+
info(`Start it: PORT=${PORT} pm2 start ${BIN} --name ${PM2_NAME}`);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Give the process a beat to bind, then probe /health (no long sleeps).
|
|
99
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
100
|
+
await checkHealth(diskSkills);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main().catch((err) => {
|
|
105
|
+
warn(`rebuild-mcp error (non-fatal): ${err?.message || err}`);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
package/skills/build/SKILL.md
CHANGED
|
@@ -401,3 +401,7 @@ NEVER run pnpm build or pnpm turbo. Use npx vitest run only.
|
|
|
401
401
|
- Unattended: max 2 retries per task before escalating to advisor
|
|
402
402
|
- Every Agent() dispatch: `model: <routed>` + `max_turns: 70` + `isolation: "worktree"` — non-negotiable. Model is chosen per task per the routing table in Step 7: Sonnet 4.6 for updates/edits, Opus 4.6 for harder coding, Opus 4.8 for design/innovative thought (CS 2.0, brand/UX, architecture). Supervisor logs `model=<chosen> reason=<phrase>` per agent. Exception: validator agent in Step 10 omits isolation; validator model stays `claude-sonnet-4-6` (verification, not generation).
|
|
403
403
|
- Finding an existing file is NOT task completion — verify it satisfies the spec
|
|
404
|
+
|
|
405
|
+
## Capture lessons (exit step)
|
|
406
|
+
|
|
407
|
+
Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — write one `.rdc/lessons/<YYYY-MM-DD>-build-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `status` (`open`, or `applied` if you shipped the fix in this same run, with the commit linked). Commit the lesson file(s) on `develop` alongside the run's other commits, and note "N lessons captured" in your verdict/summary. A run that taught nothing writes nothing — absence is the default.
|
package/skills/collab/SKILL.md
CHANGED
|
@@ -215,3 +215,7 @@ If Dave types in this terminal during a turn:
|
|
|
215
215
|
- Treat it as an override injected into the current task
|
|
216
216
|
- Acknowledge it in your `chitchat_reply` response
|
|
217
217
|
- If it changes direction mid-task, note what you stopped and why
|
|
218
|
+
|
|
219
|
+
## Capture lessons (exit step)
|
|
220
|
+
|
|
221
|
+
Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — write one `.rdc/lessons/<YYYY-MM-DD>-collab-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `status` (`open`, or `applied` if you shipped the fix in this same run, with the commit linked). Commit the lesson file(s) on `develop` alongside the run's other commits, and note "N lessons captured" in your verdict/summary. A run that taught nothing writes nothing — absence is the default.
|
package/skills/deploy/SKILL.md
CHANGED
|
@@ -372,6 +372,10 @@ Fields:
|
|
|
372
372
|
|
|
373
373
|
When diagnosing a broken deploy in Mode 3: query `coolify_events` FIRST before re-running the deploy — the most recent event row tells you whether the previous deploy actually triggered, whether it failed, and how long it ran. Faster than checking the Coolify UI.
|
|
374
374
|
|
|
375
|
+
## Capture lessons (exit step)
|
|
376
|
+
|
|
377
|
+
Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — write one `.rdc/lessons/<YYYY-MM-DD>-deploy-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `status` (`open`, or `applied` if you shipped the fix in this same run, with the commit linked). Commit the lesson file(s) on `develop` alongside the run's other commits, and note "N lessons captured" in your verdict/summary. A run that taught nothing writes nothing — absence is the default.
|
|
378
|
+
|
|
375
379
|
## References
|
|
376
380
|
|
|
377
381
|
- Type-specific checklists + DNS tree + gate commands: `docs/runbooks/coolify-deploy-checklist.md`
|
package/skills/fixit/SKILL.md
CHANGED
|
@@ -159,3 +159,7 @@ Report: what was fixed, file(s) changed, commit hash. One sentence.
|
|
|
159
159
|
- Never run `pnpm build` — not needed for a fixit
|
|
160
160
|
- If scope expands mid-fix: stop, escalate to rdc:build, don't finish under fixit
|
|
161
161
|
- Marker file must be cleaned up whether fix succeeds or escalates
|
|
162
|
+
|
|
163
|
+
## Capture lessons (exit step)
|
|
164
|
+
|
|
165
|
+
Before the final verdict line, follow `.rdc/guides/lessons-learned-spec.md` § Capture procedure. If this run taught something non-obvious — a first root-cause theory that turned out wrong, the documented/standard path not working, a missing gate or check that cost a round, or a surprising tool/infra behavior — write one `.rdc/lessons/<YYYY-MM-DD>-fixit-<short-slug>.md` per lesson using the schema in that spec. Set `scope` (`simple` | `architectural`) and `status` (`open`, or `applied` if you shipped the fix in this same run, with the commit linked). Commit the lesson file(s) on `develop` alongside the run's other commits, and note "N lessons captured" in your verdict/summary. A run that taught nothing writes nothing — absence is the default.
|
|
@@ -158,6 +158,16 @@ Write to `.rdc/reports/YYYY-MM-DD-housekeeping.md`:
|
|
|
158
158
|
## Verdict: CLEAN / HAS_ISSUES
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
## Lessons triage (weekly)
|
|
162
|
+
|
|
163
|
+
Read all `.rdc/lessons/*.md` with `status: open` (schema + procedure: `.rdc/guides/lessons-learned-spec.md` § Triage procedure). Cluster by `area` + root-cause similarity (dedupe repeats into one fix). For each cluster:
|
|
164
|
+
|
|
165
|
+
- `scope: simple` → apply the fix directly (rule line, skill-doc edit, config, guard), commit it, set the lesson(s) `status: applied` with the commit linked.
|
|
166
|
+
- `scope: architectural` → do NOT edit. Present the issue + options via `AskUserQuestion` (per `.claude/rules/architectural-change-approval.md`). On approval, apply via the correct lifecycle (rdc-skills tag/push for skills; cited commit for rules) and set `status: applied`. If deferred, set `status: triaged` and spawn a `work_item`.
|
|
167
|
+
- Not worth fixing → `status: wont-fix` with a one-line reason.
|
|
168
|
+
|
|
169
|
+
Never delete lesson files — `applied` and `wont-fix` stay as the audit trail. Report captured / applied / escalated / deferred counts in the housekeeping report.
|
|
170
|
+
|
|
161
171
|
## Rules
|
|
162
172
|
- Never run `pnpm build` — not needed for this audit
|
|
163
173
|
- Never modify app_deployments — the DB is the source of truth; PUBLISH.md conforms to it
|