@lifeaitools/rdc-skills 0.25.10 → 0.26.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.
Files changed (77) hide show
  1. package/.claude-plugin/plugin.json +1608 -1551
  2. package/.github/workflows/self-test.yml +34 -34
  3. package/CHANGELOG.md +332 -319
  4. package/MANIFEST.md +224 -224
  5. package/README.md +368 -367
  6. package/RELEASE.md +50 -45
  7. package/bin/rdc-skills-mcp.mjs +1 -1
  8. package/commands/build.md +181 -181
  9. package/commands/collab.md +180 -180
  10. package/commands/deploy.md +173 -148
  11. package/commands/fixit.md +150 -150
  12. package/commands/handoff.md +173 -173
  13. package/commands/overnight.md +220 -220
  14. package/commands/plan.md +158 -158
  15. package/commands/preplan.md +131 -131
  16. package/commands/prototype.md +145 -145
  17. package/commands/report.md +99 -99
  18. package/commands/review.md +120 -120
  19. package/commands/status.md +86 -86
  20. package/commands/workitems.md +127 -127
  21. package/deploy/install-systemd.sh +16 -0
  22. package/deploy/systemd/rdc-skills-mcp.service +19 -0
  23. package/git-sha.json +1 -1
  24. package/guides/agent-bootstrap.md +195 -195
  25. package/guides/agents/backend.md +102 -102
  26. package/guides/agents/content.md +94 -94
  27. package/guides/agents/cs2.md +56 -56
  28. package/guides/agents/data.md +86 -86
  29. package/guides/agents/design.md +77 -77
  30. package/guides/agents/frontend.md +91 -91
  31. package/guides/agents/infrastructure.md +81 -81
  32. package/guides/agents/setup.md +272 -272
  33. package/guides/agents/verify.md +119 -119
  34. package/guides/agents/viz.md +106 -106
  35. package/guides/orchestration-epic.md +17 -0
  36. package/hooks/check-rdc-environment.js +172 -378
  37. package/hooks/lib/box-lock.js +4 -5
  38. package/package.json +1 -2
  39. package/scripts/install-rdc-skills.js +19 -264
  40. package/scripts/probe-installed-hooks.mjs +2 -2
  41. package/scripts/self-test.mjs +1459 -1459
  42. package/scripts/validate-publish-manifests.js +502 -502
  43. package/skills/build/SKILL.md +662 -578
  44. package/skills/channel-formatter/SKILL.md +538 -538
  45. package/skills/collab/SKILL.md +239 -239
  46. package/skills/convert/SKILL.md +138 -138
  47. package/skills/deploy/SKILL.md +583 -541
  48. package/skills/design/SKILL.md +205 -205
  49. package/skills/env/SKILL.md +146 -139
  50. package/skills/fixit/SKILL.md +203 -203
  51. package/skills/handoff/SKILL.md +236 -236
  52. package/skills/housekeeping/SKILL.md +189 -189
  53. package/skills/onramp/SKILL.md +1459 -1459
  54. package/skills/overnight/SKILL.md +251 -251
  55. package/skills/plan/SKILL.md +345 -345
  56. package/skills/preplan/SKILL.md +90 -90
  57. package/skills/prototype/SKILL.md +150 -150
  58. package/skills/refactor/SKILL.md +51 -0
  59. package/skills/regen-media/SKILL.md +94 -94
  60. package/skills/release/SKILL.md +140 -140
  61. package/skills/report/SKILL.md +100 -100
  62. package/skills/review/SKILL.md +151 -151
  63. package/skills/self-test/SKILL.md +108 -108
  64. package/skills/status/SKILL.md +99 -99
  65. package/skills/tests/MATRIX.md +59 -55
  66. package/skills/tests/README.md +1 -1
  67. package/skills/tests/onramp.test.json +101 -87
  68. package/skills/tests/rdc-env.test.json +12 -0
  69. package/skills/tests/rdc-new-model.test.json +12 -0
  70. package/skills/tests/rdc-refactor.test.json +29 -0
  71. package/skills/tests/rdc-regen-media.test.json +29 -29
  72. package/skills/watch/SKILL.md +84 -84
  73. package/skills/workitems/SKILL.md +151 -151
  74. package/tests/no-local-pm2.test.mjs +45 -0
  75. package/scripts/local-install-with-stop.sh +0 -41
  76. package/scripts/probe-lock-holders.mjs +0 -36
  77. package/scripts/rebuild-mcp.mjs +0 -107
@@ -30,8 +30,6 @@ const fs = require('fs');
30
30
  const path = require('path');
31
31
  const os = require('os');
32
32
  const readline = require('readline');
33
- const http = require('http');
34
- const crypto = require('crypto');
35
33
  const { execSync } = require('child_process');
36
34
 
37
35
  // ── Args ──────────────────────────────────────────────────────────────────────
@@ -90,9 +88,7 @@ const projectRoot = projectRootArg || codexRoot || detectedLifeaiRoot;
90
88
 
91
89
  const PLUGIN_KEY = 'rdc-skills@rdc-skills';
92
90
  const MARKETPLACE = 'rdc-skills';
93
- const NPM_PACKAGE = '@lifeaitools/rdc-skills';
94
- const MCP_NAME = 'rdc-skills-mcp';
95
- const MCP_PORT = '3110';
91
+ const PUBLIC_MCP_URL = 'https://rdc-skills.regendevcorp.com/mcp';
96
92
 
97
93
  // ── Logging ───────────────────────────────────────────────────────────────────
98
94
  const ok = msg => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
@@ -104,12 +100,6 @@ function run(cmd, options = {}) {
104
100
  return execSync(cmd, { encoding: 'utf8', stdio: 'pipe', ...options }).trim();
105
101
  }
106
102
 
107
- function shellQuote(s) {
108
- const raw = String(s);
109
- if (process.platform === 'win32') return `"${raw.replace(/"/g, '\\"')}"`;
110
- return `'${raw.replace(/'/g, `'\\''`)}'`;
111
- }
112
-
113
103
  function updateCodexMcpToml(toml, mcpUrl) {
114
104
  const blockRe = /(^|\n)(\[mcp_servers\.rdc-skills\]\n)([\s\S]*?)(?=\n\[|\s*$)/;
115
105
  const desiredLine = `url = '${mcpUrl}'`;
@@ -218,8 +208,8 @@ function assertHooksLoadable(hooksDst, copiedCount) {
218
208
  if (res.status !== 0) throw new Error(`installed hook fails to parse: ${combined.trim().slice(0, 300)}`);
219
209
 
220
210
  // RESOLVE the dependency graph without EXECUTING it. `require(entry)` ran the
221
- // WHOLE hook during install — a real `npm install -g` plus pm2 stop/restart, and
222
- // on an unhealthy box a recursive install -> repair -> install loop.
211
+ // WHOLE hook during install — including package repair — and on an unhealthy
212
+ // box can create a recursive install -> repair -> install loop.
223
213
  // require.resolve answers the only question that matters here ("can it find
224
214
  // ./lib/box-lock?") with no side effects.
225
215
  const probe = [
@@ -365,174 +355,6 @@ function buildPluginCache(cacheDir, version, gitSha) {
365
355
  }
366
356
  }
367
357
 
368
- function getPm2Process(name) {
369
- try {
370
- const list = JSON.parse(run('pm2 jlist'));
371
- return Array.isArray(list) ? list.find((p) => p.name === name) || null : null;
372
- } catch {
373
- return null;
374
- }
375
- }
376
-
377
- function isGlobalNpmRdcSkillsPath(scriptPath) {
378
- if (!scriptPath) return false;
379
- const normalized = scriptPath.replace(/\\/g, '/').toLowerCase();
380
- return normalized.includes('/node_modules/@lifeaitools/rdc-skills/');
381
- }
382
-
383
- function packageRootFromMcpScript(scriptPath) {
384
- return scriptPath ? path.resolve(path.dirname(scriptPath), '..') : null;
385
- }
386
-
387
- function readInstalledPackageVersion(scriptPath) {
388
- const packageRoot = packageRootFromMcpScript(scriptPath);
389
- if (!packageRoot) return null;
390
- return readJson(path.join(packageRoot, 'package.json'), {}).version || null;
391
- }
392
-
393
- function collectFilesRecursive(baseDir, rel, out) {
394
- const full = path.join(baseDir, rel);
395
- if (!fs.existsSync(full)) return;
396
- const stat = fs.statSync(full);
397
- if (stat.isDirectory()) {
398
- for (const entry of fs.readdirSync(full).sort()) {
399
- collectFilesRecursive(baseDir, path.join(rel, entry), out);
400
- }
401
- return;
402
- }
403
- if (stat.isFile()) out.push(rel.replace(/\\/g, '/'));
404
- }
405
-
406
- function hashInstallSurface(root) {
407
- if (!root || !fs.existsSync(root)) return null;
408
- const rels = [];
409
- for (const rel of ['.claude-plugin', 'commands', 'guides', 'skills', 'package.json', 'README.md']) {
410
- collectFilesRecursive(root, rel, rels);
411
- }
412
- const hash = crypto.createHash('sha256');
413
- for (const rel of rels.sort()) {
414
- hash.update(rel);
415
- hash.update('\0');
416
- hash.update(fs.readFileSync(path.join(root, rel)));
417
- hash.update('\0');
418
- }
419
- return hash.digest('hex');
420
- }
421
-
422
- function syncGlobalMcpInstall(version) {
423
- const proc = getPm2Process(MCP_NAME);
424
- if (!proc) {
425
- info('[2.9] MCP pkg — PM2 process not registered yet; start handled below');
426
- return;
427
- }
428
-
429
- const scriptPath = proc.pm2_env?.pm_exec_path || proc.pm_exec_path || '';
430
- if (!isGlobalNpmRdcSkillsPath(scriptPath)) {
431
- info(`[2.9] MCP pkg — PM2 uses source checkout, not global npm (${scriptPath || 'unknown path'})`);
432
- return;
433
- }
434
-
435
- const installedVersion = readInstalledPackageVersion(scriptPath);
436
- const packageRoot = packageRootFromMcpScript(scriptPath);
437
- const sourceHash = hashInstallSurface(repoRoot);
438
- const installedHash = hashInstallSurface(packageRoot);
439
- if (installedVersion === version && sourceHash && installedHash && sourceHash === installedHash) {
440
- ok(`[2.9] MCP pkg — global ${NPM_PACKAGE}@${version} already matches source`);
441
- return;
442
- }
443
-
444
- let stopped = false;
445
- let installed = false;
446
- try {
447
- const reason = installedVersion === version ? 'same version, source changed' : `${installedVersion || '?'} → ${version}`;
448
- info(`[2.9] MCP pkg — updating global ${NPM_PACKAGE} (${reason}) from local source`);
449
- run(`pm2 stop ${MCP_NAME}`);
450
- stopped = true;
451
- run(`npm install -g ${shellQuote(repoRoot)}`);
452
- installed = true;
453
- ok(`[2.9] MCP pkg — installed global ${NPM_PACKAGE}@${version}`);
454
- } catch (e) {
455
- warn(`[2.9] MCP pkg — global install failed (${String(e.message || e).split('\n')[0]})`);
456
- if (String(e.message || '').includes('EBUSY')) {
457
- info(' PM2 was stopped first; if EBUSY persists, another Node/npm process still holds the package directory.');
458
- }
459
- } finally {
460
- if (stopped) {
461
- try {
462
- run(`pm2 restart ${MCP_NAME} --update-env`, { env: { ...process.env, PORT: MCP_PORT } });
463
- ok(`[2.9] MCP pkg — pm2 restarted ${MCP_NAME}${installed ? '' : ' (previous install restored)'}`);
464
- } catch (e) {
465
- warn(`[2.9] MCP pkg — pm2 restart failed (${String(e.message || e).split('\n')[0]})`);
466
- }
467
- }
468
- }
469
- }
470
-
471
- function callLocalMcpTool(name, args = {}) {
472
- return new Promise((resolve, reject) => {
473
- const payload = JSON.stringify({
474
- jsonrpc: '2.0',
475
- id: 1,
476
- method: 'tools/call',
477
- params: { name, arguments: args },
478
- });
479
- const req = http.request({
480
- hostname: '127.0.0.1',
481
- port: Number(MCP_PORT),
482
- path: '/mcp',
483
- method: 'POST',
484
- headers: {
485
- 'Content-Type': 'application/json',
486
- 'Accept': 'application/json, text/event-stream',
487
- 'Content-Length': Buffer.byteLength(payload),
488
- },
489
- timeout: 10000,
490
- }, (res) => {
491
- let body = '';
492
- res.setEncoding('utf8');
493
- res.on('data', (chunk) => { body += chunk; });
494
- res.on('end', () => {
495
- try {
496
- const dataLine = body.split(/\r?\n/).find((line) => line.startsWith('data: '));
497
- const envelope = JSON.parse(dataLine ? dataLine.slice(6) : body);
498
- const text = envelope?.result?.content?.[0]?.text;
499
- resolve(text ? JSON.parse(text) : envelope);
500
- } catch (e) {
501
- reject(new Error(`invalid MCP response: ${e.message}`));
502
- }
503
- });
504
- });
505
- req.on('timeout', () => req.destroy(new Error('MCP request timed out')));
506
- req.on('error', reject);
507
- req.write(payload);
508
- req.end();
509
- });
510
- }
511
-
512
- async function verifyLiveMcpCatalogFresh() {
513
- const proc = getPm2Process(MCP_NAME);
514
- if (!proc) {
515
- warn('[7.5] MCP catalog — skipped (PM2 process not registered)');
516
- return;
517
- }
518
- const sourceSkills = fs.readdirSync(path.join(repoRoot, 'skills'), { withFileTypes: true })
519
- .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(repoRoot, 'skills', entry.name, 'SKILL.md')))
520
- .map((entry) => entry.name)
521
- .sort();
522
- const catalog = await callLocalMcpTool('rdc_skill_list', {});
523
- const rows = Array.isArray(catalog)
524
- ? catalog
525
- : Array.isArray(catalog?.skills) ? catalog.skills
526
- : Array.isArray(catalog?.results) ? catalog.results
527
- : [];
528
- const liveNames = new Set(rows.map((row) => row.name));
529
- const missing = sourceSkills.filter((name) => !liveNames.has(name));
530
- if (missing.length > 0 || rows.length < sourceSkills.length) {
531
- throw new Error(`live MCP catalog stale: source=${sourceSkills.length}, live=${rows.length}, missing=${missing.join(', ') || '(count mismatch)'}`);
532
- }
533
- ok(`[7.5] MCP catalog — ${rows.length} live skill(s), source catalog fresh`);
534
- }
535
-
536
358
  // ── User-skills cleanup ───────────────────────────────────────────────────────
537
359
  // Older installer versions wrote skill files directly to ~/.claude/skills/user/.
538
360
  // Claude Code loads that directory AND the plugin cache, so any rdc skills left
@@ -814,7 +636,6 @@ function addCodexTarget(targets, label, targetDir) {
814
636
  // existing clauth/codeflow entries exactly. claude.ai web still needs the one-time
815
637
  // connector add in its UI (no programmatic API).
816
638
  function registerMcpEndpoints() {
817
- const MCP_URL = 'https://rdc-skills.regendevcorp.com/mcp';
818
639
  const out = [];
819
640
 
820
641
  // Claude Code — user-level ~/.claude.json mcpServers (covers EVERY project).
@@ -824,8 +645,8 @@ function registerMcpEndpoints() {
824
645
  const data = readJson(claudeJson);
825
646
  if (!data.mcpServers || typeof data.mcpServers !== 'object') data.mcpServers = {};
826
647
  const cur = data.mcpServers['rdc-skills'];
827
- if (!cur || cur.url !== MCP_URL) {
828
- data.mcpServers['rdc-skills'] = { type: 'http', url: MCP_URL };
648
+ if (!cur || cur.url !== PUBLIC_MCP_URL) {
649
+ data.mcpServers['rdc-skills'] = { type: 'http', url: PUBLIC_MCP_URL };
829
650
  writeJson(claudeJson, data, 2);
830
651
  out.push('claude(~/.claude.json)');
831
652
  }
@@ -837,7 +658,7 @@ function registerMcpEndpoints() {
837
658
  const codexToml = path.join(os.homedir(), '.codex', 'config.toml');
838
659
  if (fs.existsSync(codexToml)) {
839
660
  const toml = fs.readFileSync(codexToml, 'utf8');
840
- const next = updateCodexMcpToml(toml, MCP_URL);
661
+ const next = updateCodexMcpToml(toml, PUBLIC_MCP_URL);
841
662
  if (next !== toml) {
842
663
  fs.writeFileSync(codexToml, next);
843
664
  out.push('codex(~/.codex/config.toml)');
@@ -958,19 +779,14 @@ function buildZip(version) {
958
779
  // ── Hook config ───────────────────────────────────────────────────────────────
959
780
 
960
781
  /**
961
- * SessionStart budget for check-rdc-environment.js, DERIVED from the per-step
962
- * timeouts in hooks/check-rdc-environment.js repair() rather than restated, so the
963
- * two cannot drift apart.
782
+ * SessionStart budget for check-rdc-environment.js, derived from its two repair
783
+ * steps: global package install and plugin registration.
964
784
  *
965
- * At the harness default (60s) a genuine cold repair was killed mid-install — the
966
- * leader's own steps alone exceed it — and every follower read that as a crash.
967
- * The +60s headroom matters: a budget equal to the exact sum kills a leader that
968
- * legitimately uses its full per-step allowance right at the boundary.
785
+ * At the harness default (60s) a genuine cold repair can be killed mid-install.
969
786
  */
970
787
  const REPAIR_NPM_SEC = 120; // npm install -g
971
788
  const REPAIR_INSTALL_SEC = 180; // rdc-skills-install
972
- const REPAIR_PM2_SEC = 60; // pm2 stop + restart
973
- const RDC_ENV_HOOK_TIMEOUT_SEC = REPAIR_NPM_SEC + REPAIR_INSTALL_SEC + REPAIR_PM2_SEC + 60;
789
+ const RDC_ENV_HOOK_TIMEOUT_SEC = REPAIR_NPM_SEC + REPAIR_INSTALL_SEC + 60;
974
790
 
975
791
  function buildHooksConfig(hooksDir, profile = 'core') {
976
792
  const base = hooksDir.replace(/\\/g, '/');
@@ -1050,62 +866,12 @@ function buildHooksConfig(hooksDir, profile = 'core') {
1050
866
  return config;
1051
867
  }
1052
868
 
1053
- // ── MCP server registration (non-fatal) ───────────────────────────────────────
1054
- // Ensures the rdc-skills MCP deps are installed, registers/starts the local MCP
1055
- // under PM2 as `rdc-skills-mcp` on PORT=3110, and prints the claude.ai connector
1056
- // line. Every failure here WARNs — it must never abort the installer.
1057
- function registerMcpServer() {
1058
- const binPath = path.join(repoRoot, 'bin', 'rdc-skills-mcp.mjs');
1059
- const connector = 'https://rdc-skills.regendevcorp.com/mcp';
1060
-
1061
- try {
1062
- // (a) ensure MCP runtime deps are present (express, @modelcontextprotocol/sdk, yaml, zod)
1063
- const needDeps = ['express', '@modelcontextprotocol/sdk', 'yaml', 'zod'].some((d) => {
1064
- try { require.resolve(d, { paths: [repoRoot] }); return false; } catch { return true; }
1065
- });
1066
- if (needDeps) {
1067
- try {
1068
- execSync('npm install --no-audit --no-fund', { cwd: repoRoot, stdio: 'pipe' });
1069
- ok('[7/7] MCP deps — installed');
1070
- } catch (e) {
1071
- warn(`[7/7] MCP deps — npm install failed (${e.message.split('\n')[0]}); install manually with \`npm install\``);
1072
- }
1073
- } else {
1074
- ok('[7/7] MCP deps — already present');
1075
- }
1076
-
1077
- // (b) register/start under PM2 (tolerate pm2 missing)
1078
- let pm2Ok = false;
1079
- try { execSync('pm2 -v', { stdio: 'pipe' }); pm2Ok = true; } catch { pm2Ok = false; }
1080
-
1081
- if (!pm2Ok) {
1082
- warn('[7/7] MCP server — pm2 not found; start manually:');
1083
- info(` PORT=${MCP_PORT} pm2 start ${binPath} --name ${MCP_NAME}`);
1084
- } else {
1085
- let registered = false;
1086
- try {
1087
- const jlist = JSON.parse(execSync('pm2 jlist', { encoding: 'utf8', stdio: 'pipe' }));
1088
- registered = Array.isArray(jlist) && jlist.some((p) => p.name === MCP_NAME);
1089
- } catch {}
1090
- try {
1091
- if (registered) {
1092
- execSync(`pm2 restart ${MCP_NAME} --update-env`, { cwd: repoRoot, stdio: 'pipe', env: { ...process.env, PORT: MCP_PORT } });
1093
- ok(`[7/7] MCP server — pm2 restarted ${MCP_NAME} (PORT=${MCP_PORT})`);
1094
- } else {
1095
- execSync(`pm2 start "${binPath}" --name ${MCP_NAME}`, { cwd: repoRoot, stdio: 'pipe', env: { ...process.env, PORT: MCP_PORT } });
1096
- ok(`[7/7] MCP server — pm2 started ${MCP_NAME} (PORT=${MCP_PORT})`);
1097
- }
1098
- } catch (e) {
1099
- warn(`[7/7] MCP server — pm2 start/restart failed (${e.message.split('\n')[0]})`);
1100
- info(` PORT=${MCP_PORT} pm2 start ${binPath} --name ${MCP_NAME}`);
1101
- }
1102
- }
1103
-
1104
- // (c) print the claude.ai connector line
1105
- info(` claude.ai connector: ${connector} (Auth: none — URL is the shared secret)`);
1106
- } catch (e) {
1107
- warn(`[7/7] MCP server — skipped (${e.message.split('\n')[0]})`);
1108
- }
869
+ // ── Public MCP connector ──────────────────────────────────────────────────────
870
+ // The MCP is independently hosted. Installation only registers and reports its
871
+ // standard public URL; it never launches a local server or process manager.
872
+ function reportMcpConnector() {
873
+ ok('[7/7] MCP connector — public stateless endpoint registered');
874
+ info(` claude.ai / MCP clients: ${PUBLIC_MCP_URL} (Auth: none)`);
1109
875
  }
1110
876
 
1111
877
  // ── Preflight ─────────────────────────────────────────────────────────────────
@@ -1310,11 +1076,6 @@ async function main() {
1310
1076
  info('[2.6] MCP — rdc-skills endpoint already registered (claude + codex)');
1311
1077
  }
1312
1078
 
1313
- // If the live MCP is served from the global npm package, update that exact
1314
- // install before restarting PM2. Windows otherwise holds the package tree open
1315
- // and `npm install -g` can fail with EBUSY.
1316
- syncGlobalMcpInstall(version);
1317
-
1318
1079
  // 2.7. Symlinks in regen-root/.claude/skills/ (FS MCP + claude.ai access)
1319
1080
  if (codexRoot) {
1320
1081
  const skillsLinkDir = path.join(codexRoot, '.claude', 'skills');
@@ -1443,16 +1204,10 @@ async function main() {
1443
1204
  process.exit(2);
1444
1205
  }
1445
1206
 
1446
- // 7. MCP server registration (non-fatal — WARNs only, never aborts)
1207
+ // 7. Public MCP connector registration
1447
1208
  console.log('');
1448
- console.log(' \x1b[36mMCP server:\x1b[0m');
1449
- try { registerMcpServer(); } catch (e) { warn(`[7/7] MCP server — unexpected error (${e.message})`); }
1450
- try {
1451
- await verifyLiveMcpCatalogFresh();
1452
- } catch (e) {
1453
- fail(`[7.5] MCP catalog — ${e.message}`);
1454
- process.exit(2);
1455
- }
1209
+ console.log(' \x1b[36mMCP connector:\x1b[0m');
1210
+ reportMcpConnector();
1456
1211
 
1457
1212
  // Done
1458
1213
  console.log('');
@@ -5,8 +5,8 @@
5
5
  * The source was fine, the npm tarball was fine, and only the copied result was
6
6
  * broken: copyHookFiles() was flat, so hooks/lib/ was never created at the
7
7
  * destination and require('./lib/box-lock') died MODULE_NOT_FOUND — a SessionStart
8
- * hook exiting non-zero with EMPTY stdout on every session, before any health check
9
- * could run, so its own repair path could never recover it.
8
+ * hook exiting non-zero with EMPTY stdout on every session, before its package
9
+ * verification could run, so its own repair path could never recover it.
10
10
  *
11
11
  * File-presence checks in the repo would not have caught that. This replays the
12
12
  * real copy into a scratch dir and LOADS the result.