@launchsecure/launch-kit 0.0.39 → 0.0.41

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 (113) hide show
  1. package/dist/chart-client/assets/index-Dd6IotOZ.css +1 -0
  2. package/dist/chart-client/index.html +2 -2
  3. package/dist/client/assets/index-DE0uje6k.css +32 -0
  4. package/dist/client/index.html +2 -2
  5. package/dist/council-client/assets/index-CGYusOCK.css +1 -0
  6. package/dist/council-client/assets/{index-jjBWyhry.js → index-DkTFX53U.js} +1 -1
  7. package/dist/council-client/index.html +2 -2
  8. package/dist/deck-client/assets/_baseUniq-mvYvzeEJ.js +1 -0
  9. package/dist/deck-client/assets/arc-CX4ylnp2.js +1 -0
  10. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-BkR-5IRK.js +36 -0
  11. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CwAGy9lU.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.js → c4Diagram-AHTNJAMY-Cbq1rlG8.js} +2 -2
  13. package/dist/deck-client/assets/channel-B9GC-CLn.js +1 -0
  14. package/dist/deck-client/assets/chunk-4BX2VUAB-D58Co4lU.js +1 -0
  15. package/dist/deck-client/assets/{chunk-4TB4RGXK-Bk0FUbxU.js → chunk-4TB4RGXK-BYvhTm3d.js} +1 -1
  16. package/dist/deck-client/assets/chunk-55IACEB6-oWukUhYg.js +1 -0
  17. package/dist/deck-client/assets/chunk-EDXVE4YY-Cm58kVnZ.js +1 -0
  18. package/dist/deck-client/assets/{chunk-FMBD7UC4-DqOvWr1k.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.js → chunk-OYMX7WX6-C72wigPl.js} +1 -1
  20. package/dist/deck-client/assets/chunk-QZHKN3VN-CLgeuAKw.js +1 -0
  21. package/dist/deck-client/assets/chunk-YZCP3GAM-HDDlJ5oI.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +1 -0
  23. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +1 -0
  24. package/dist/deck-client/assets/clone-n-WQlAGe.js +1 -0
  25. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CUXQKg2M.js +1 -0
  26. package/dist/deck-client/assets/dagre-KV5264BT-C5M-fVDc.js +4 -0
  27. package/dist/deck-client/assets/diagram-5BDNPKRD-CcVsQ0S8.js +10 -0
  28. package/dist/deck-client/assets/diagram-G4DWMVQ6-DJswXyep.js +24 -0
  29. package/dist/deck-client/assets/diagram-MMDJMWI5-CGT76fm1.js +43 -0
  30. package/dist/deck-client/assets/diagram-TYMM5635-BBsYUNN6.js +24 -0
  31. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-aiv9GZnL.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.js → flowDiagram-DWJPFMVM-DLuDYIKT.js} +2 -2
  33. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-B19b6Qtj.js +292 -0
  34. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-BYLAfYVS.js +106 -0
  35. package/dist/deck-client/assets/graph-CfzQUfPh.js +1 -0
  36. package/dist/deck-client/assets/index-DlwdTgE_.js +892 -0
  37. package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
  38. package/dist/deck-client/assets/infoDiagram-42DDH7IO-Dp3mUA9c.js +2 -0
  39. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.js → kanban-definition-6JOO6SKY-D9-lmhQf.js} +2 -2
  42. package/dist/deck-client/assets/layout-CfIe_Su8.js +1 -0
  43. package/dist/deck-client/assets/linear-09ZFRoh_.js +1 -0
  44. package/dist/deck-client/assets/mermaid.core-BaQyIOvj.js +309 -0
  45. package/dist/deck-client/assets/min-CYwCzYaL.js +1 -0
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CfXcK1qH.js → mindmap-definition-QFDTVHPH-CouFxf6C.js} +2 -2
  47. package/dist/deck-client/assets/pieDiagram-DEJITSTG-DMB1ufC0.js +30 -0
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.js → requirementDiagram-MS252O5E-BMc3GJkx.js} +2 -2
  50. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-CxACUncm.js +10 -0
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BC1MYBn6.js → sequenceDiagram-FGHM5R23-Ch-P3Mzc.js} +2 -2
  52. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-Cy8n7Yzk.js +1 -0
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DKnITsD4.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
  56. package/dist/deck-client/assets/wardley-RL74JXVD-Bxo5x40D.js +162 -0
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/beacon-monitor-entry.js +106 -24
  61. package/dist/server/chart-serve.js +544 -247
  62. package/dist/server/cli.js +1016 -324
  63. package/dist/server/council-entry.js +23 -4
  64. package/dist/server/council-serve.js +22 -3
  65. package/dist/server/deck-mcp-entry.js +523 -125
  66. package/dist/server/deck-serve.js +326 -40
  67. package/dist/server/graph-mcp-entry.js +1160 -357
  68. package/dist/server/init-entry.js +17 -7
  69. package/dist/server/orbit-entry.js +91 -7
  70. package/dist/server/recall-entry.js +94 -12
  71. package/dist/server/rover-entry.js +1 -1
  72. package/package.json +1 -1
  73. package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
  74. package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
  75. package/scaffolds/statusline/statusline-mcp.sh +68 -19
  76. package/scaffolds/statusline/statusline-wrapper.sh +12 -9
  77. package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
  78. package/dist/client/assets/index-CMN3tlGP.css +0 -32
  79. package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
  80. package/dist/deck-client/assets/_baseUniq-DOrnEQMI.js +0 -1
  81. package/dist/deck-client/assets/arc-DOWK7V3m.js +0 -1
  82. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DPhzvk7q.js +0 -36
  83. package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
  84. package/dist/deck-client/assets/chunk-4BX2VUAB-RKm0LXpu.js +0 -1
  85. package/dist/deck-client/assets/chunk-55IACEB6-Cl3hja-M.js +0 -1
  86. package/dist/deck-client/assets/chunk-EDXVE4YY-CNIMQCV2.js +0 -1
  87. package/dist/deck-client/assets/chunk-QZHKN3VN-6_kraYpP.js +0 -1
  88. package/dist/deck-client/assets/chunk-YZCP3GAM-FgAwIWlo.js +0 -1
  89. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
  90. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
  91. package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
  92. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CigVnnPr.js +0 -1
  93. package/dist/deck-client/assets/dagre-KV5264BT-DHZXTktX.js +0 -4
  94. package/dist/deck-client/assets/diagram-5BDNPKRD-H5k0eauU.js +0 -10
  95. package/dist/deck-client/assets/diagram-G4DWMVQ6-Bg3dFhSY.js +0 -24
  96. package/dist/deck-client/assets/diagram-MMDJMWI5-CQLC410N.js +0 -43
  97. package/dist/deck-client/assets/diagram-TYMM5635-DFTCHVkP.js +0 -24
  98. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DSaGMPM4.js +0 -292
  99. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-DMjL2Vix.js +0 -106
  100. package/dist/deck-client/assets/graph-B7Vn5lkK.js +0 -1
  101. package/dist/deck-client/assets/index-BD36e-tD.js +0 -1196
  102. package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
  103. package/dist/deck-client/assets/infoDiagram-42DDH7IO-mNi4iygG.js +0 -2
  104. package/dist/deck-client/assets/layout-CZTyRhOG.js +0 -1
  105. package/dist/deck-client/assets/linear--7n7iEvd.js +0 -1
  106. package/dist/deck-client/assets/min-Bh130DN8.js +0 -1
  107. package/dist/deck-client/assets/pieDiagram-DEJITSTG-DjVHLAVw.js +0 -30
  108. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BOH9sLyh.js +0 -10
  109. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-kNp9bv8K.js +0 -1
  110. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
  111. package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
  112. /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-CrYM1-ac.js} +0 -0
  113. /package/dist/client/assets/{index-BA7BHBWT.js → index-BoIjawzY.js} +0 -0
@@ -1643,6 +1643,7 @@ var PRESETS = {
1643
1643
  };
1644
1644
  var LAUNCH_KIT_PKG = "@launchsecure/launch-kit";
1645
1645
  var LAUNCH_KIT_PKG_LATEST = `${LAUNCH_KIT_PKG}@latest`;
1646
+ var LAUNCH_KIT_NPX_ENV = { npm_config_prefer_online: "true" };
1646
1647
  var LAUNCH_KIT_TOOLS_GUIDE_STATIC_HEAD = `
1647
1648
  Wired in Claude Code (.mcp.json):
1648
1649
  launch-secure \u2014 LS API: work items, comms, secrets, members, board
@@ -2345,19 +2346,23 @@ function buildLaunchKitMcpEntries(cfg) {
2345
2346
  },
2346
2347
  "launch-chart": {
2347
2348
  command: "npx",
2348
- args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-chart"]
2349
+ args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-chart"],
2350
+ env: { ...LAUNCH_KIT_NPX_ENV }
2349
2351
  },
2350
2352
  "launch-deck": {
2351
2353
  command: "npx",
2352
- args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-deck"]
2354
+ args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-deck"],
2355
+ env: { ...LAUNCH_KIT_NPX_ENV }
2353
2356
  },
2354
2357
  "launch-orbit": {
2355
2358
  command: "npx",
2356
- args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-orbit", "mcp"]
2359
+ args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-orbit", "mcp"],
2360
+ env: { ...LAUNCH_KIT_NPX_ENV }
2357
2361
  },
2358
2362
  "launch-recall": {
2359
2363
  command: "npx",
2360
- args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-recall", "mcp"]
2364
+ args: ["-y", "-p", LAUNCH_KIT_PKG_LATEST, "launch-recall", "mcp"],
2365
+ env: { ...LAUNCH_KIT_NPX_ENV }
2361
2366
  }
2362
2367
  };
2363
2368
  }
@@ -2378,16 +2383,21 @@ function mergeMcpEntry(existing, ours) {
2378
2383
  return merged;
2379
2384
  }
2380
2385
  function pinLaunchKitLatest(servers) {
2381
- const fixed = [];
2386
+ const fixed = /* @__PURE__ */ new Set();
2382
2387
  for (const [name, entry] of Object.entries(servers)) {
2383
2388
  if (entry.command !== "npx" || !Array.isArray(entry.args)) continue;
2384
2389
  const i = entry.args.indexOf(LAUNCH_KIT_PKG);
2385
2390
  if (i !== -1) {
2386
2391
  entry.args[i] = LAUNCH_KIT_PKG_LATEST;
2387
- fixed.push(name);
2392
+ fixed.add(name);
2393
+ }
2394
+ const isLaunchKit = entry.args.some((a) => a === LAUNCH_KIT_PKG || a === LAUNCH_KIT_PKG_LATEST);
2395
+ if (isLaunchKit && entry.env?.npm_config_prefer_online === void 0) {
2396
+ entry.env = { ...LAUNCH_KIT_NPX_ENV, ...entry.env ?? {} };
2397
+ fixed.add(name);
2388
2398
  }
2389
2399
  }
2390
- return fixed;
2400
+ return [...fixed];
2391
2401
  }
2392
2402
  function mergeMcpFile(targetDir, launchKitEntries) {
2393
2403
  const p = path5.join(targetDir, ".mcp.json");
@@ -1385,7 +1385,7 @@ function rewriteEnvFile(filePath, rewrites) {
1385
1385
  const out = lines.map((line) => {
1386
1386
  const m = LINE_RE.exec(line);
1387
1387
  if (!m) return line;
1388
- const [, indent, key, sep, rawValue] = m;
1388
+ const [, indent, key, sep2, rawValue] = m;
1389
1389
  if (!(key in rewrites)) return line;
1390
1390
  touched.add(key);
1391
1391
  const { quote, inner } = stripQuotes2(rawValue);
@@ -1396,7 +1396,7 @@ function rewriteEnvFile(filePath, rewrites) {
1396
1396
  return line;
1397
1397
  }
1398
1398
  result.changed.push(key);
1399
- return `${indent}${key}${sep}${quote}${next2}${quote}`;
1399
+ return `${indent}${key}${sep2}${quote}${next2}${quote}`;
1400
1400
  });
1401
1401
  for (const key of Object.keys(rewrites)) {
1402
1402
  if (!touched.has(key)) result.missing.push(key);
@@ -3060,10 +3060,93 @@ var init_orbit_mcp = __esm({
3060
3060
  });
3061
3061
 
3062
3062
  // src/server/orbit-entry.ts
3063
- var import_node_fs13 = require("node:fs");
3064
- var import_node_path10 = require("node:path");
3063
+ var import_node_fs14 = require("node:fs");
3064
+ var import_node_path11 = require("node:path");
3065
3065
  init_orchestrator();
3066
3066
  init_logger();
3067
+
3068
+ // src/server/prune-npx-cache.ts
3069
+ var import_node_fs13 = require("node:fs");
3070
+ var import_node_os5 = require("node:os");
3071
+ var import_node_path10 = require("node:path");
3072
+ var import_node_util = require("node:util");
3073
+ var PKG = "@launchsecure/launch-kit";
3074
+ var readFileAsync = (0, import_node_util.promisify)(import_node_fs13.readFile);
3075
+ var readdirAsync = (0, import_node_util.promisify)(import_node_fs13.readdir);
3076
+ var rmAsync = (0, import_node_util.promisify)(import_node_fs13.rm);
3077
+ var realpathAsync = (0, import_node_util.promisify)(import_node_fs13.realpath);
3078
+ function npxCacheRoot() {
3079
+ const cache = process.env.npm_config_cache;
3080
+ if (cache && cache.trim()) return (0, import_node_path10.join)(cache, "_npx");
3081
+ if (process.platform === "win32") {
3082
+ const localAppData = process.env.LOCALAPPDATA;
3083
+ if (localAppData) return (0, import_node_path10.join)(localAppData, "npm-cache", "_npx");
3084
+ }
3085
+ return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".npm", "_npx");
3086
+ }
3087
+ function ownVersion() {
3088
+ let dir = __dirname;
3089
+ for (let i = 0; i < 8; i++) {
3090
+ try {
3091
+ const pkg = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(dir, "package.json"), "utf8"));
3092
+ if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
3093
+ } catch {
3094
+ }
3095
+ const parent = (0, import_node_path10.dirname)(dir);
3096
+ if (parent === dir) break;
3097
+ dir = parent;
3098
+ }
3099
+ return null;
3100
+ }
3101
+ async function pruneStaleNpxCache() {
3102
+ try {
3103
+ const version = ownVersion();
3104
+ if (!version) return;
3105
+ const root = npxCacheRoot();
3106
+ let hashes;
3107
+ try {
3108
+ hashes = await readdirAsync(root);
3109
+ } catch {
3110
+ return;
3111
+ }
3112
+ const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
3113
+ let reaped = 0;
3114
+ for (const hash of hashes) {
3115
+ const dir = (0, import_node_path10.join)(root, hash);
3116
+ if (selfPath === dir || selfPath.startsWith(dir + import_node_path10.sep)) continue;
3117
+ let ver;
3118
+ try {
3119
+ const lkPkg = JSON.parse(
3120
+ await readFileAsync((0, import_node_path10.join)(dir, "node_modules", PKG, "package.json"), "utf8")
3121
+ );
3122
+ ver = lkPkg.version;
3123
+ } catch {
3124
+ continue;
3125
+ }
3126
+ if (ver === version) continue;
3127
+ try {
3128
+ const top = JSON.parse(await readFileAsync((0, import_node_path10.join)(dir, "package.json"), "utf8"));
3129
+ const spec = top?.dependencies?.[PKG];
3130
+ if (typeof spec === "string" && spec.startsWith("file:")) continue;
3131
+ } catch {
3132
+ }
3133
+ try {
3134
+ await rmAsync(dir, { recursive: true, force: true });
3135
+ reaped++;
3136
+ } catch {
3137
+ }
3138
+ }
3139
+ if (reaped > 0) {
3140
+ process.stderr.write(
3141
+ `[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
3142
+ `
3143
+ );
3144
+ }
3145
+ } catch {
3146
+ }
3147
+ }
3148
+
3149
+ // src/server/orbit-entry.ts
3067
3150
  function parseFlags(argv) {
3068
3151
  const positional = [];
3069
3152
  const flags = { emitCd: false, noBackup: false, skipGates: [], deep: false, all: false, json: false, force: false };
@@ -3099,6 +3182,7 @@ function parseFlags(argv) {
3099
3182
  return { positional, flags };
3100
3183
  }
3101
3184
  async function main() {
3185
+ void pruneStaleNpxCache();
3102
3186
  const argv = process.argv.slice(2);
3103
3187
  const subcommand = argv[0];
3104
3188
  if (!subcommand || subcommand === "mcp") {
@@ -3328,8 +3412,8 @@ function formatRollup(reports) {
3328
3412
  return out.join("\n") + "\n";
3329
3413
  }
3330
3414
  function runInit(projectRoot) {
3331
- const path = (0, import_node_path10.join)(projectRoot, "orbit.json");
3332
- if ((0, import_node_fs13.existsSync)(path)) {
3415
+ const path = (0, import_node_path11.join)(projectRoot, "orbit.json");
3416
+ if ((0, import_node_fs14.existsSync)(path)) {
3333
3417
  process.stderr.write(`[launch-orbit] orbit.json already exists at ${path}
3334
3418
  `);
3335
3419
  process.exit(1);
@@ -3384,7 +3468,7 @@ function runInit(projectRoot) {
3384
3468
  full: { resources: ["db", "ports"] }
3385
3469
  }
3386
3470
  };
3387
- (0, import_node_fs13.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
3471
+ (0, import_node_fs14.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
3388
3472
  process.stdout.write(`\u2713 wrote ${path}
3389
3473
  `);
3390
3474
  }
@@ -284,14 +284,14 @@ function pidFilePath(recallDir) {
284
284
  function existingWatcherPid() {
285
285
  const { recallDir } = resolveRecallPaths();
286
286
  const pf = pidFilePath(recallDir);
287
- if (!(0, import_node_fs.existsSync)(pf)) return null;
288
- const pid = Number((0, import_node_fs.readFileSync)(pf, "utf-8").trim());
287
+ if (!(0, import_node_fs2.existsSync)(pf)) return null;
288
+ const pid = Number((0, import_node_fs2.readFileSync)(pf, "utf-8").trim());
289
289
  if (!Number.isFinite(pid) || pid <= 0) return null;
290
290
  return isPidAlive(pid) ? pid : null;
291
291
  }
292
292
  function ensureWatcher() {
293
293
  const { gitDir, workTree } = resolveRecallPaths();
294
- if (!(0, import_node_fs.existsSync)(gitDir)) {
294
+ if (!(0, import_node_fs2.existsSync)(gitDir)) {
295
295
  process.stderr.write(
296
296
  `[launch-recall mcp] shadow repo missing at ${gitDir} \u2014 run \`launch-recall init\` first. MCP tools will still serve read-only queries, but nothing will be captured.
297
297
  `
@@ -358,7 +358,7 @@ async function handleTool(name, args) {
358
358
  function doctorTool() {
359
359
  const { gitDir, recallDir } = resolveRecallPaths();
360
360
  const checks = [];
361
- const shadowOk = (0, import_node_fs.existsSync)(gitDir);
361
+ const shadowOk = (0, import_node_fs2.existsSync)(gitDir);
362
362
  checks.push({
363
363
  name: "shadow_repo",
364
364
  ok: shadowOk,
@@ -391,7 +391,7 @@ function doctorTool() {
391
391
  }
392
392
  function reportTool() {
393
393
  const { gitDir, recallDir, workTree } = resolveRecallPaths();
394
- if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised" };
394
+ if (!(0, import_node_fs2.existsSync)(gitDir)) return { error: "shadow repo not initialised" };
395
395
  let totalSnapshots = 0;
396
396
  try {
397
397
  const out = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "rev-list", "--count", "HEAD"], {
@@ -425,9 +425,9 @@ function reportTool() {
425
425
  }
426
426
  let config = null;
427
427
  const cfgPath = `${workTree}/.launch-recall.json`;
428
- if ((0, import_node_fs.existsSync)(cfgPath)) {
428
+ if ((0, import_node_fs2.existsSync)(cfgPath)) {
429
429
  try {
430
- config = JSON.parse((0, import_node_fs.readFileSync)(cfgPath, "utf-8"));
430
+ config = JSON.parse((0, import_node_fs2.readFileSync)(cfgPath, "utf-8"));
431
431
  } catch {
432
432
  }
433
433
  }
@@ -435,7 +435,7 @@ function reportTool() {
435
435
  }
436
436
  function historyTool(path7, limit) {
437
437
  const { gitDir, workTree } = resolveRecallPaths();
438
- if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised", path: path7, snapshots: [] };
438
+ if (!(0, import_node_fs2.existsSync)(gitDir)) return { error: "shadow repo not initialised", path: path7, snapshots: [] };
439
439
  const rel = path7.startsWith("/") ? path7.replace(`${workTree}/`, "").replace(/^\/+/, "") : path7;
440
440
  const snapshots = [];
441
441
  try {
@@ -456,7 +456,7 @@ function statusTool() {
456
456
  const { gitDir, workTree } = resolveRecallPaths();
457
457
  const pid = existingWatcherPid();
458
458
  let lastSnapshotAt = null;
459
- if ((0, import_node_fs.existsSync)(gitDir)) {
459
+ if ((0, import_node_fs2.existsSync)(gitDir)) {
460
460
  try {
461
461
  lastSnapshotAt = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "log", "-1", "--format=%aI"], {
462
462
  encoding: "utf-8",
@@ -551,14 +551,14 @@ function startRecallMcpServer() {
551
551
  process.stderr.write("[launch-recall mcp] stdin closed, exiting\n");
552
552
  teardown();
553
553
  });
554
- void import_node_fs.statSync;
554
+ void import_node_fs2.statSync;
555
555
  }
556
- var import_node_child_process2, import_node_fs, SERVER_INFO, TOOLS, childWatcher;
556
+ var import_node_child_process2, import_node_fs2, SERVER_INFO, TOOLS, childWatcher;
557
557
  var init_recall_mcp = __esm({
558
558
  "src/server/recall-mcp.ts"() {
559
559
  "use strict";
560
560
  import_node_child_process2 = require("node:child_process");
561
- import_node_fs = require("node:fs");
561
+ import_node_fs2 = require("node:fs");
562
562
  init_paths();
563
563
  SERVER_INFO = { name: "launch-recall", version: "0.0.1" };
564
564
  TOOLS = [
@@ -1355,6 +1355,87 @@ var init_uninstall = __esm({
1355
1355
  }
1356
1356
  });
1357
1357
 
1358
+ // src/server/prune-npx-cache.ts
1359
+ var import_node_fs = require("node:fs");
1360
+ var import_node_os = require("node:os");
1361
+ var import_node_path = require("node:path");
1362
+ var import_node_util = require("node:util");
1363
+ var PKG = "@launchsecure/launch-kit";
1364
+ var readFileAsync = (0, import_node_util.promisify)(import_node_fs.readFile);
1365
+ var readdirAsync = (0, import_node_util.promisify)(import_node_fs.readdir);
1366
+ var rmAsync = (0, import_node_util.promisify)(import_node_fs.rm);
1367
+ var realpathAsync = (0, import_node_util.promisify)(import_node_fs.realpath);
1368
+ function npxCacheRoot() {
1369
+ const cache = process.env.npm_config_cache;
1370
+ if (cache && cache.trim()) return (0, import_node_path.join)(cache, "_npx");
1371
+ if (process.platform === "win32") {
1372
+ const localAppData = process.env.LOCALAPPDATA;
1373
+ if (localAppData) return (0, import_node_path.join)(localAppData, "npm-cache", "_npx");
1374
+ }
1375
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".npm", "_npx");
1376
+ }
1377
+ function ownVersion() {
1378
+ let dir = __dirname;
1379
+ for (let i = 0; i < 8; i++) {
1380
+ try {
1381
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "package.json"), "utf8"));
1382
+ if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
1383
+ } catch {
1384
+ }
1385
+ const parent = (0, import_node_path.dirname)(dir);
1386
+ if (parent === dir) break;
1387
+ dir = parent;
1388
+ }
1389
+ return null;
1390
+ }
1391
+ async function pruneStaleNpxCache() {
1392
+ try {
1393
+ const version = ownVersion();
1394
+ if (!version) return;
1395
+ const root = npxCacheRoot();
1396
+ let hashes;
1397
+ try {
1398
+ hashes = await readdirAsync(root);
1399
+ } catch {
1400
+ return;
1401
+ }
1402
+ const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
1403
+ let reaped = 0;
1404
+ for (const hash of hashes) {
1405
+ const dir = (0, import_node_path.join)(root, hash);
1406
+ if (selfPath === dir || selfPath.startsWith(dir + import_node_path.sep)) continue;
1407
+ let ver;
1408
+ try {
1409
+ const lkPkg = JSON.parse(
1410
+ await readFileAsync((0, import_node_path.join)(dir, "node_modules", PKG, "package.json"), "utf8")
1411
+ );
1412
+ ver = lkPkg.version;
1413
+ } catch {
1414
+ continue;
1415
+ }
1416
+ if (ver === version) continue;
1417
+ try {
1418
+ const top = JSON.parse(await readFileAsync((0, import_node_path.join)(dir, "package.json"), "utf8"));
1419
+ const spec = top?.dependencies?.[PKG];
1420
+ if (typeof spec === "string" && spec.startsWith("file:")) continue;
1421
+ } catch {
1422
+ }
1423
+ try {
1424
+ await rmAsync(dir, { recursive: true, force: true });
1425
+ reaped++;
1426
+ } catch {
1427
+ }
1428
+ }
1429
+ if (reaped > 0) {
1430
+ process.stderr.write(
1431
+ `[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
1432
+ `
1433
+ );
1434
+ }
1435
+ } catch {
1436
+ }
1437
+ }
1438
+
1358
1439
  // src/server/recall-entry.ts
1359
1440
  function logStderr(msg) {
1360
1441
  process.stderr.write(`[launch-recall] ${msg}
@@ -1383,6 +1464,7 @@ function printUsage() {
1383
1464
  );
1384
1465
  }
1385
1466
  async function main() {
1467
+ void pruneStaleNpxCache();
1386
1468
  const argv = process.argv.slice(2);
1387
1469
  const subcommand = argv[0];
1388
1470
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
@@ -608,7 +608,7 @@ var require_package = __commonJS({
608
608
  "package.json"(exports2, module2) {
609
609
  module2.exports = {
610
610
  name: "@launchsecure/launch-kit",
611
- version: "0.0.39",
611
+ version: "0.0.41",
612
612
  description: "LaunchSecure toolkit \u2014 launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
613
613
  license: "MIT",
614
614
  author: "LaunchSecure - AutomateWithUs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchsecure/launch-kit",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "description": "LaunchSecure toolkit — launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
5
5
  "license": "MIT",
6
6
  "author": "LaunchSecure - AutomateWithUs",
@@ -47,6 +47,38 @@ Examples:
47
47
 
48
48
  If a search comes back empty, widen *before* concluding it's absent: drop `resource_type`, drop `tag`, try a shorter `q` stem (`"deck"` not `"create deck kit skill"`), try `author` alone. Only after the unfiltered `q`/`author` sweep is empty should you say it isn't there.
49
49
 
50
+ ## Cross-project paste: the "Copy for Claude" payload
51
+
52
+ The CapCom UI has a **Copy for Claude** button on the selected thread. It puts a payload on the clipboard that starts with `/kit:comms reply` followed by a fenced ` ```capcom ` block — so when the user pastes it, this skill fires. The whole point is **cross-project**: the block names the project the thread lives in, which is usually NOT the MCP's default project. Parse it; do not fall back to the default project.
53
+
54
+ The block looks like:
55
+
56
+ ```capcom
57
+ project: duet-pay
58
+ org: automatewithus
59
+ type: discussion
60
+ id: cmc1x9k20003
61
+ author: drew.m.jacobs
62
+ status: OPEN
63
+ url: https://.../communication?comment=cmc1x9k20003
64
+ title: launch-kit init: scaffold .mcp.json with @latest
65
+ ---
66
+ <full thread body>
67
+
68
+ --- replies (2) ---
69
+ [drew.m.jacobs] first reply text...
70
+ [prajyot] second reply text...
71
+ ```
72
+
73
+ How to handle a pasted payload:
74
+
75
+ 1. **Parse the metadata** (everything above the `---` line). `project` → `project_slug`, `org` → `org_slug`. **Pass both on every MCP call** — this is what targets the right project instead of the local default. `id` is the thread's comment id (use as `parent_id` to reply). `type` is the `resource_type`. `title`/`author` help locate it.
76
+ 2. **Get current context.** The body + replies are embedded so you can read and draft immediately, but they're a snapshot from when the user clicked. Before drafting a reply, do one `communication_read(project_slug, org_slug, resource_type:<type>, q:<distinctive title words>, author:<author>)` to confirm nothing new landed (there's no read-by-id; `q`+`author`+`project_slug` pins it). If the read surfaces newer replies than the paste, use those.
77
+ 3. **Draft, preview, gate.** Draft the reply, paste the full body in the terminal, and wait for explicit "post it" — same write gate as everywhere else (the paste is NOT authorization to post).
78
+ 4. **Post** with `communication_write(project_slug, org_slug, parent_id:<id>, body:<reply>)`. Keep `resource_type` off the reply unless threading rules require it — a reply inherits its parent's thread.
79
+
80
+ If the user pastes the block with no instruction beyond the leading command, default to: read for freshness, summarize the thread, and draft a reply for their review.
81
+
50
82
  ## Default system tags
51
83
 
52
84
  These ship on every org (from `tags_list`, `isSystem: true`) — you don't need to call `tags_list` to know them, only to get their **IDs** for a write. Numeric tags (e.g. `247`) are auto-created per work-item and are not in this list.
@@ -84,5 +116,6 @@ To attach tags on a write you need their IDs: call `tags_list` once and map name
84
116
 
85
117
  - **Repo-backed long-form doc?** That's `/kit:brief` — it writes a file and the server syncs the comment; don't hand-post it here.
86
118
  - **Daily standup?** That's `/kit:standup` — it has the voice/format conventions and the "since last push" diffing.
87
- - **Bug/gap in launch-kit itself?** That's `kit_feedback_submit`, not a hand-written comment.
119
+ - **Bug/gap in launch-kit TOOLING?** (launch-chart/deck/orbit/beacon/recall or a kit skill) That's `kit_feedback_submit`, not a hand-written comment.
120
+ - **Bug/feedback about the LaunchSecure PLATFORM itself?** (the LS web app / API / pipeline) That's `ls_feedback_submit` — it routes to the LS project regardless of which project you're in, so an LS bug you hit while working in another project (e.g. DuetPay) lands with the LS team instead of in that project's Comm Hub. It stamps your current org/project into `fields.origin` for context. Pass `severity` (bug/feature/idea/question) to tag it.
88
121
  - **Reading is free; writing is gated.** Default to read/search freely; gate every write on an explicit go-ahead.
@@ -0,0 +1,126 @@
1
+ ---
2
+ description: Generate a runnable Playwright test for a feature by walking launch-chart's context (business-logic) graph. Plans the required snippet chain (requires/produces), scaffolds + fills any missing snippets from the endpoint source, then emits a .spec.ts via generate_spec — auto-filling free-form params with faker, secrets from env, leaving only state-bound values (a real user/org) for you to confirm.
3
+ when_to_use: |
4
+ Auto-fire when the user wants to CREATE/GENERATE an end-to-end test for a feature or action — "write a test for creating a tag", "generate the e2e for the X flow", "scaffold a playwright test for endpoint Y", "I need a test that does login then Z". The deliverable is a real .spec.ts assembled from the context graph + reusable snippets, not a hand-written one-off. Do NOT auto-fire for: running an existing test (just run playwright), unit tests of a pure function, reading how a flow works (use /kit:analyse), or visual/manual verification (use /verify). Requires launch-chart's `context` layer + a snippets dir; if the project has neither, this skill sets them up as it goes.
5
+ allowed-tools:
6
+ - mcp__launch-chart__detect_project_stack
7
+ - mcp__launch-chart__generate_graph
8
+ - mcp__launch-chart__read_graph
9
+ - mcp__launch-chart__test_plan
10
+ - mcp__launch-chart__scaffold_snippet
11
+ - mcp__launch-chart__generate_spec
12
+ - Read
13
+ - Write
14
+ - Edit
15
+ - Bash
16
+ ---
17
+
18
+ # /kit:gen-test
19
+
20
+ Assemble a runnable Playwright test for a target action by walking the context graph. The graph is the grammar (what state each action requires/produces), reusable snippets are the vocabulary, and this skill is the loop that turns them into a `.spec.ts`.
21
+
22
+ Parse `$ARGUMENTS`:
23
+ - **target** (required) — a context action node id (`action:app/api/.../tags/route.ts`), a bare endpoint path, or a plain-English feature ("create a tag"). If a feature phrase, resolve it to a node in step 1. If absent, ask.
24
+ - **--run** — after generating, actually execute the spec with `playwright test`.
25
+ - **--snippets-dir=<dir>** / **--spec-dir=<dir>** — override defaults (`tests/snippets`, `tests/generated`).
26
+
27
+ ## Preflight
28
+
29
+ 1. `mcp__launch-chart__detect_project_stack` — confirm launch-chart is wired AND can actually parse this stack. Read the result and gate in order:
30
+
31
+ - **Chart MCP missing** (the call itself fails) → stop. Tell the user to run `npx @launchsecure/launch-kit refresh`.
32
+ - **Stack unsupported** — the context layer is derived from the **api** graph, which only the TypeScript parser produces. If `available_layers` does **not** include `api`, this skill cannot generate a test for this project. Stop **here**, before resolving any target, and say so plainly — quote `unsupported_hint` if present. Example: a Python/Go/Ruby backend (FastAPI, Django, Flask, Rails, …) has no TS api parser, so no `action:` nodes exist to walk. Do not run `generate_graph` or `read_graph` hoping a node turns up — it won't, and an empty-search miss is a confusing way to report "wrong stack." Output the failure block below and exit.
33
+ - **Supported but `context` not built** — `available_layers` includes `api` but not `context` → run `mcp__launch-chart__generate_graph` once (it builds `context.json` from the api graph), then continue.
34
+ - **Ready** — `context` is in `available_layers` → continue to step 1.
35
+
36
+ Unsupported-stack failure output (terse, no markdown table):
37
+
38
+ ```
39
+ gen-test: cannot generate — stack unsupported
40
+ launch-chart's context parser only builds from a TypeScript api layer.
41
+ this project: <unsupported_hint>
42
+ available layers: <available_layers>
43
+ No action graph to walk, so no .spec.ts can be assembled. Hand-write the Playwright test, or add a TS api surface the chart can parse.
44
+ ```
45
+
46
+ ## 1. Resolve the target
47
+
48
+ If `target` isn't already a node id, `mcp__launch-chart__read_graph` with `layer: "context"`, `search: <phrase>` and pick the matching `action:` node. Confirm the node with the user if ambiguous.
49
+
50
+ ## 2. Plan the chain
51
+
52
+ `mcp__launch-chart__test_plan` with `target: <node>`. Read the result:
53
+ - `steps[]` — the ordered chain (prerequisites first, target last), each bound to a snippet or flagged.
54
+ - `gaps[]` — `no_snippet` (an action with no snippet), `no_producer` (a required state nothing produces), or `cycle`.
55
+ - `runnable` — true when every step has a snippet.
56
+
57
+ If `runnable: true`, skip to step 4.
58
+
59
+ ## 3. Close the gaps
60
+
61
+ For each gap, smallest fix first:
62
+
63
+ - **`no_snippet`** → `mcp__launch-chart__scaffold_snippet` with `target: <action>`. It returns a stub with `requires`/`produces` pre-filled (from the graph) and `params`/`run()` as TODOs. Then **you** complete it — this is the judgment step:
64
+ 1. `Read` the endpoint's route file (and its zod schema) to get the real `params`.
65
+ 2. Write the `run()` body with real Playwright steps. Prefer the **real UI** for the action under test (navigate, fill, click, assert), and the **fast door** (API/`page.request`) for pure setup snippets like login.
66
+ 3. `Write` the finished snippet to the snippets dir.
67
+ - **`no_producer`** → either a prerequisite snippet/producer is genuinely missing (scaffold it, same as above), OR it's a low-confidence derived edge that's a false positive. Check the edge confidence via `read_graph layer:context`; if it's a `guard:notFound` low-confidence requires that doesn't really gate the action, note it and proceed (a curation overlay can reject it later — out of scope here).
68
+ - **`cycle`** → surface it; the snippet graph has a circular dependency that needs a human decision.
69
+
70
+ Re-run `test_plan` until `runnable: true`.
71
+
72
+ ## 4. Generate the spec
73
+
74
+ `mcp__launch-chart__generate_spec` with `target: <node>`. Inspect:
75
+ - `missing_fixtures` — **state-bound** params (a real `email`/`password` user, an `orgSlug`, an id) that faker can't invent. Resolve them: `Read` `prisma/seed.ts` (or the project's seed/fixtures) for a seeded user + org; secrets come from env (the spec already emits `process.env.X`).
76
+ - Re-call `generate_spec` with `fixtures: { email, orgSlug, ... }` until `complete: true`.
77
+
78
+ `Write` the returned `code` to `suggested_path` (default `tests/generated/<target>.spec.ts`).
79
+
80
+ **faker check (required — the emitted code imports it).** The generated spec `import`s `@faker-js/faker`, so it won't run without it. Check `package.json` (`grep '@faker-js/faker' package.json`); if absent, `npm i -D @faker-js/faker` before continuing. Don't defer this to `--run` — a spec that can't resolve its own import is broken on disk even if the user never runs it here.
81
+
82
+ ## 5. Run (only with `--run`)
83
+
84
+ **Precheck before invoking Playwright — fail fast with a punch list, don't half-run.** Only when `--run` is set, verify all three; if any fail, stop and print exactly what's missing rather than letting `playwright test` error cryptically:
85
+
86
+ 1. **Playwright installed** — `npx playwright --version` resolves (or `@playwright/test` is in `package.json`). If missing: `npm i -D @playwright/test && npx playwright install`.
87
+ 2. **Config covers the spec dir** — a `playwright.config.*` exists and its `testDir`/`testMatch` includes `suggested_path` (default `tests/generated`). If there's no config, or it scopes to a different dir, say so — the run would collect zero tests and falsely "pass."
88
+ 3. **Secret env vars set** — every `process.env.X` the spec reads (e.g. the password) is present in the environment. List any unset ones by name; don't invent values.
89
+
90
+ Punch-list output when a precheck fails (terse, no markdown table):
91
+
92
+ ```
93
+ gen-test --run: blocked, prerequisites missing
94
+ playwright: <ok | not installed → npm i -D @playwright/test>
95
+ config: <ok | tests/generated not in testDir of playwright.config.ts>
96
+ env: <ok | unset: TEST_USER_PASSWORD>
97
+ Spec is written at <spec-path>; fix the above and re-run `npx playwright test <spec-path>`.
98
+ ```
99
+
100
+ Only once all three pass:
101
+
102
+ ```
103
+ npx playwright test <spec-path>
104
+ ```
105
+ Report pass/fail with the captured output.
106
+
107
+ ## Output
108
+
109
+ Terse summary:
110
+
111
+ ```
112
+ gen-test for <target>
113
+ plan: <step1> -> <step2> -> <target> (runnable: yes)
114
+ snippets: created <N>, reused <M>
115
+ fixtures resolved: email, orgSlug (faker: name,color,icon; secret: password)
116
+ spec: tests/generated/<x>.spec.ts (complete: yes)
117
+ [--run] result: PASSED | FAILED
118
+ ```
119
+
120
+ ## Constraints
121
+
122
+ - **The graph plans, you fill.** `requires`/`produces` ordering comes from the graph (deterministic); the `run()` body and the state-bound fixtures are your judgment from the real source — never guess selectors, read the page/route.
123
+ - **Setup via the fast door, test via the real door.** Login/seed snippets may hit the API; the action under test drives the real UI.
124
+ - **Don't invent state-bound values.** A faked email/orgSlug fails at runtime — resolve those from the seed, leave faker for free-form inputs only.
125
+ - **Idempotent snippets.** A `createTag` that collides on a duplicate name will fail a second run — use faker/unique values for created entities.
126
+ - **Plain-text summary.** No markdown tables in the human-facing output.