@launchsecure/launch-kit 0.0.40 → 0.0.42

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 (106) 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-B1v46vTE.js → index-DkTFX53U.js} +1 -1
  7. package/dist/council-client/index.html +2 -2
  8. package/dist/deck-client/assets/_baseUniq-BrhDuG3C.js +1 -0
  9. package/dist/deck-client/assets/arc-DXtPHMhw.js +1 -0
  10. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-Cs9IdLQQ.js +36 -0
  11. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CUdblaWk.js → blockDiagram-DXYQGD6D--wpvoBAt.js} +2 -2
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.js → c4Diagram-AHTNJAMY-BNIKE8-Z.js} +2 -2
  13. package/dist/deck-client/assets/channel-Bb5wIjTD.js +1 -0
  14. package/dist/deck-client/assets/chunk-4BX2VUAB-Bz3EWmUo.js +1 -0
  15. package/dist/deck-client/assets/{chunk-4TB4RGXK-BUJtZ7jO.js → chunk-4TB4RGXK-D55BBvVZ.js} +1 -1
  16. package/dist/deck-client/assets/chunk-55IACEB6-BzjEcoTi.js +1 -0
  17. package/dist/deck-client/assets/chunk-EDXVE4YY-CH1vs4Lu.js +1 -0
  18. package/dist/deck-client/assets/{chunk-FMBD7UC4-PnZ9v6ey.js → chunk-FMBD7UC4-CVRnaGM0.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.js → chunk-OYMX7WX6-BCTV_aEZ.js} +1 -1
  20. package/dist/deck-client/assets/chunk-QZHKN3VN-BqM8r4ee.js +1 -0
  21. package/dist/deck-client/assets/chunk-YZCP3GAM-Rhbb691A.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CN-ZYMbT.js +1 -0
  23. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CN-ZYMbT.js +1 -0
  24. package/dist/deck-client/assets/clone-D6e7poKG.js +1 -0
  25. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-fWyBR9Nn.js +1 -0
  26. package/dist/deck-client/assets/dagre-KV5264BT-Ds0Sqext.js +4 -0
  27. package/dist/deck-client/assets/diagram-5BDNPKRD-BGz_X007.js +10 -0
  28. package/dist/deck-client/assets/diagram-G4DWMVQ6-C78jeb-r.js +24 -0
  29. package/dist/deck-client/assets/diagram-MMDJMWI5-BdoHW8mF.js +43 -0
  30. package/dist/deck-client/assets/diagram-TYMM5635-C8i9zc-U.js +24 -0
  31. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-56pn_93p.js → erDiagram-SMLLAGMA-DplAZ-yG.js} +2 -2
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.js → flowDiagram-DWJPFMVM-F3N9yTAB.js} +2 -2
  33. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DebdM4S5.js +292 -0
  34. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-htCk0oPz.js +106 -0
  35. package/dist/deck-client/assets/graph-Dtxc2PT4.js +1 -0
  36. package/dist/deck-client/assets/index-CwAiam97.js +892 -0
  37. package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
  38. package/dist/deck-client/assets/infoDiagram-42DDH7IO-gTxegviJ.js +2 -0
  39. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-DYSEsfUK.js} +5 -5
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-DgVc1q4D.js} +2 -2
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.js → kanban-definition-6JOO6SKY-DcOf3i9N.js} +2 -2
  42. package/dist/deck-client/assets/layout-CHP9HIw4.js +1 -0
  43. package/dist/deck-client/assets/linear-XjVb7x4Q.js +1 -0
  44. package/dist/deck-client/assets/mermaid.core-bouKOnsR.js +309 -0
  45. package/dist/deck-client/assets/min-Jl4GV9DB.js +1 -0
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-oAybLedr.js → mindmap-definition-QFDTVHPH-FXXf4cJx.js} +2 -2
  47. package/dist/deck-client/assets/pieDiagram-DEJITSTG-Dl4plCTi.js +30 -0
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-dtluDZXs.js → quadrantDiagram-34T5L4WZ-BeOEeelg.js} +2 -2
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.js → requirementDiagram-MS252O5E-CBh1jchM.js} +2 -2
  50. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BK6gG-ub.js +10 -0
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CYkd7oQK.js → sequenceDiagram-FGHM5R23-BtQwzsF5.js} +2 -2
  52. package/dist/deck-client/assets/stateDiagram-FHFEXIEX--kLQOi8R.js +1 -0
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CHEPjgB0.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DZIxSyd1.js → timeline-definition-GMOUNBTQ-CQ_-CLh7.js} +2 -2
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-DBhKYAFT.js} +2 -2
  56. package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-DLKe29q9.js} +3 -3
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-Brb8ezsV.js} +2 -2
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-LogZ0Az9.js} +2 -2
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/cli.js +488 -215
  61. package/dist/server/council-entry.js +7 -1
  62. package/dist/server/council-serve.js +7 -1
  63. package/dist/server/deck-mcp-entry.js +423 -50
  64. package/dist/server/deck-serve.js +350 -48
  65. package/dist/server/graph-mcp-entry.js +331 -37
  66. package/dist/server/init-entry.js +17 -7
  67. package/dist/server/rover-entry.js +1 -1
  68. package/package.json +1 -1
  69. package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
  70. package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
  71. package/dist/chart-client/assets/index-CWJFFDPu.css +0 -1
  72. package/dist/client/assets/index-CTzFcfGV.css +0 -32
  73. package/dist/council-client/assets/index-ArgRc5mN.css +0 -1
  74. package/dist/deck-client/assets/_baseUniq-BZP7n41F.js +0 -1
  75. package/dist/deck-client/assets/arc-31biU3Az.js +0 -1
  76. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DHg6Ss--.js +0 -36
  77. package/dist/deck-client/assets/channel-BBkRLdnC.js +0 -1
  78. package/dist/deck-client/assets/chunk-4BX2VUAB-DQ1MrGgN.js +0 -1
  79. package/dist/deck-client/assets/chunk-55IACEB6-BdSnXB6g.js +0 -1
  80. package/dist/deck-client/assets/chunk-EDXVE4YY-94yZIUI8.js +0 -1
  81. package/dist/deck-client/assets/chunk-QZHKN3VN-CsIGIDKX.js +0 -1
  82. package/dist/deck-client/assets/chunk-YZCP3GAM-DVkBO9tn.js +0 -1
  83. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-DFCaeF-7.js +0 -1
  84. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-DFCaeF-7.js +0 -1
  85. package/dist/deck-client/assets/clone-GCEVRScB.js +0 -1
  86. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-m126Oh3b.js +0 -1
  87. package/dist/deck-client/assets/dagre-KV5264BT-C2aig8U5.js +0 -4
  88. package/dist/deck-client/assets/diagram-5BDNPKRD-CKpoRfGn.js +0 -10
  89. package/dist/deck-client/assets/diagram-G4DWMVQ6-Cjh115Ep.js +0 -24
  90. package/dist/deck-client/assets/diagram-MMDJMWI5-DKlBv_2L.js +0 -43
  91. package/dist/deck-client/assets/diagram-TYMM5635-CdBh4cEn.js +0 -24
  92. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DTIsC6Zg.js +0 -292
  93. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-CJYeyCLe.js +0 -106
  94. package/dist/deck-client/assets/graph-BDvMu1Ss.js +0 -1
  95. package/dist/deck-client/assets/index-D4eSxcBn.css +0 -1
  96. package/dist/deck-client/assets/index-QnGVE9PZ.js +0 -1196
  97. package/dist/deck-client/assets/infoDiagram-42DDH7IO-BWyKJnpW.js +0 -2
  98. package/dist/deck-client/assets/layout-vOnxnCQU.js +0 -1
  99. package/dist/deck-client/assets/linear-B0J0WCGz.js +0 -1
  100. package/dist/deck-client/assets/min-B0AXlT9L.js +0 -1
  101. package/dist/deck-client/assets/pieDiagram-DEJITSTG-BjHyHxGk.js +0 -30
  102. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-C1Vih91z.js +0 -10
  103. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-CtyG8wBK.js +0 -1
  104. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BLyKWfcN.js +0 -1
  105. /package/dist/chart-client/assets/{index-Dzlj-oCj.js → index-CrYM1-ac.js} +0 -0
  106. /package/dist/client/assets/{index-tTg_ezUF.js → index-BoIjawzY.js} +0 -0
@@ -8174,13 +8174,209 @@ var init_snippet = __esm({
8174
8174
  }
8175
8175
  });
8176
8176
 
8177
+ // src/server/graph/core/context/snippet-registry.ts
8178
+ function parseStringArray(src, key) {
8179
+ const m = src.match(new RegExp(`${key}\\s*:\\s*\\[([^\\]]*)\\]`));
8180
+ if (!m) return [];
8181
+ return [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
8182
+ }
8183
+ function parseSnippetFile(content, file) {
8184
+ const idM = content.match(/\bid\s*:\s*['"]([^'"]+)['"]/);
8185
+ if (!idM) return null;
8186
+ const exportM = content.match(/export\s+const\s+(\w+)/);
8187
+ return {
8188
+ id: idM[1],
8189
+ exportName: exportM ? exportM[1] : "default",
8190
+ file,
8191
+ requires: parseStringArray(content, "requires"),
8192
+ produces: parseStringArray(content, "produces"),
8193
+ params: parseStringArray(content, "params")
8194
+ };
8195
+ }
8196
+ function loadSnippetIndex(rootDir, snippetsDir = DEFAULT_SNIPPETS_DIR) {
8197
+ const dir = (0, import_node_path32.join)(rootDir, snippetsDir);
8198
+ const index = /* @__PURE__ */ new Map();
8199
+ if (!(0, import_node_fs25.existsSync)(dir)) return index;
8200
+ for (const f of (0, import_node_fs25.readdirSync)(dir)) {
8201
+ if (!f.endsWith(".ts") && !f.endsWith(".tsx")) continue;
8202
+ const full = (0, import_node_path32.join)(dir, f);
8203
+ try {
8204
+ if (!(0, import_node_fs25.statSync)(full).isFile()) continue;
8205
+ const entry = parseSnippetFile((0, import_node_fs25.readFileSync)(full, "utf-8"), (0, import_node_path32.join)(snippetsDir, f));
8206
+ if (entry) index.set(entry.id, entry);
8207
+ } catch {
8208
+ }
8209
+ }
8210
+ return index;
8211
+ }
8212
+ var import_node_fs25, import_node_path32, DEFAULT_SNIPPETS_DIR;
8213
+ var init_snippet_registry = __esm({
8214
+ "src/server/graph/core/context/snippet-registry.ts"() {
8215
+ "use strict";
8216
+ import_node_fs25 = require("node:fs");
8217
+ import_node_path32 = require("node:path");
8218
+ DEFAULT_SNIPPETS_DIR = "tests/snippets";
8219
+ }
8220
+ });
8221
+
8222
+ // src/server/graph/core/context/test-plan.ts
8223
+ function buildTestPlan(graph, targetId, snippets) {
8224
+ const nodesById = /* @__PURE__ */ new Map();
8225
+ for (const n of graph.nodes) nodesById.set(n.id, n);
8226
+ const requiresOf = /* @__PURE__ */ new Map();
8227
+ const producesOf = /* @__PURE__ */ new Map();
8228
+ const producersOf = /* @__PURE__ */ new Map();
8229
+ for (const e of graph.edges) {
8230
+ if (e.type === "requires") {
8231
+ (requiresOf.get(e.source) ?? requiresOf.set(e.source, /* @__PURE__ */ new Set()).get(e.source)).add(e.target);
8232
+ } else if (e.type === "produces") {
8233
+ (producesOf.get(e.source) ?? producesOf.set(e.source, /* @__PURE__ */ new Set()).get(e.source)).add(e.target);
8234
+ (producersOf.get(e.target) ?? producersOf.set(e.target, /* @__PURE__ */ new Set()).get(e.target)).add(e.source);
8235
+ }
8236
+ }
8237
+ const pickProducer = (producers) => {
8238
+ const withSnippet = producers.filter((p) => snippets.has(p)).sort();
8239
+ return withSnippet.length ? withSnippet[0] : [...producers].sort()[0];
8240
+ };
8241
+ const order = [];
8242
+ const done = /* @__PURE__ */ new Set();
8243
+ const visiting = /* @__PURE__ */ new Set();
8244
+ const gaps = [];
8245
+ const allRequired = /* @__PURE__ */ new Set();
8246
+ const visit = (actionId2) => {
8247
+ if (done.has(actionId2)) return;
8248
+ if (visiting.has(actionId2)) {
8249
+ gaps.push({ type: "cycle", action: actionId2, detail: `dependency cycle through ${actionId2}` });
8250
+ return;
8251
+ }
8252
+ visiting.add(actionId2);
8253
+ for (const state of requiresOf.get(actionId2) ?? []) {
8254
+ allRequired.add(state);
8255
+ const producers = [...producersOf.get(state) ?? []];
8256
+ if (producers.length === 0) {
8257
+ gaps.push({
8258
+ type: "no_producer",
8259
+ state: stripState(state),
8260
+ action: actionId2,
8261
+ detail: `${actionId2} requires "${stripState(state)}" but no action produces it`
8262
+ });
8263
+ continue;
8264
+ }
8265
+ visit(pickProducer(producers));
8266
+ }
8267
+ visiting.delete(actionId2);
8268
+ done.add(actionId2);
8269
+ order.push(actionId2);
8270
+ };
8271
+ visit(targetId);
8272
+ const steps = order.map((actionId2) => {
8273
+ const node = nodesById.get(actionId2);
8274
+ const entry = snippets.get(actionId2) ?? null;
8275
+ if (!entry) {
8276
+ gaps.push({
8277
+ type: "no_snippet",
8278
+ action: actionId2,
8279
+ detail: `no snippet bound to ${actionId2} \u2014 scaffold one with scaffold_snippet`
8280
+ });
8281
+ }
8282
+ const satisfies = [...producesOf.get(actionId2) ?? []].filter((s) => allRequired.has(s)).map(stripState).sort();
8283
+ return {
8284
+ action: actionId2,
8285
+ name: String(node?.name ?? actionId2),
8286
+ satisfies,
8287
+ snippet: entry ? { id: entry.id, exportName: entry.exportName, file: entry.file, params: entry.params } : null
8288
+ };
8289
+ });
8290
+ return { target: targetId, steps, gaps, runnable: gaps.length === 0 };
8291
+ }
8292
+ var stripState;
8293
+ var init_test_plan = __esm({
8294
+ "src/server/graph/core/context/test-plan.ts"() {
8295
+ "use strict";
8296
+ stripState = (s) => s.replace(/^state:/, "");
8297
+ }
8298
+ });
8299
+
8300
+ // src/server/graph/core/context/spec-gen.ts
8301
+ function toEnvRef(name) {
8302
+ return `process.env.${name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase()}`;
8303
+ }
8304
+ function classify(name) {
8305
+ const n = name.toLowerCase();
8306
+ if (/pass|secret|token|credential|apikey|api_key|_key$|^key$/.test(n)) return { kind: "secret" };
8307
+ if (/email|slug|(^|_)id$|uuid|userid|orgid|projectid/.test(n)) return { kind: "real" };
8308
+ if (/desc|summary|body|message|content|note/.test(n)) return { kind: "faker", faker: "faker.lorem.sentence()" };
8309
+ if (/colou?r/.test(n)) return { kind: "faker", faker: "faker.color.rgb({ format: 'hex' })" };
8310
+ if (/icon/.test(n)) return { kind: "faker", faker: "faker.helpers.arrayElement(['rocket','bug','star','flag','zap','heart'])" };
8311
+ if (/name|title|label/.test(n)) return { kind: "faker", faker: "faker.word.noun()" };
8312
+ if (/url|link|href/.test(n)) return { kind: "faker", faker: "faker.internet.url()" };
8313
+ if (/count|qty|quantity|amount|number|price/.test(n)) return { kind: "faker", faker: "String(faker.number.int({ min: 1, max: 100 }))" };
8314
+ return { kind: "faker", faker: "faker.lorem.word()" };
8315
+ }
8316
+ function relImport(specDir, file) {
8317
+ let r = (0, import_node_path33.relative)(specDir, file).replace(/\.tsx?$/, "");
8318
+ if (!r.startsWith(".")) r = `./${r}`;
8319
+ return r;
8320
+ }
8321
+ function generateSpec(plan, opts = {}) {
8322
+ const specDir = opts.specDir ?? "tests/generated";
8323
+ const fixtures = opts.fixtures ?? {};
8324
+ const missing = /* @__PURE__ */ new Set();
8325
+ let usesFaker = false;
8326
+ const bound = plan.steps.filter((s) => s.snippet);
8327
+ const unbound = plan.steps.filter((s) => !s.snippet);
8328
+ const importLines = /* @__PURE__ */ new Set();
8329
+ for (const s of bound) {
8330
+ importLines.add(`import { ${s.snippet.exportName} } from '${relImport(specDir, s.snippet.file)}';`);
8331
+ }
8332
+ const callLines = bound.map((s) => {
8333
+ const params = s.snippet.params.map((p) => {
8334
+ if (Object.prototype.hasOwnProperty.call(fixtures, p)) {
8335
+ return ` ${p}: ${JSON.stringify(fixtures[p])},`;
8336
+ }
8337
+ const c = classify(p);
8338
+ if (c.kind === "secret") return ` ${p}: ${toEnvRef(p)},`;
8339
+ if (c.kind === "faker") {
8340
+ usesFaker = true;
8341
+ return ` ${p}: ${c.faker},`;
8342
+ }
8343
+ missing.add(p);
8344
+ return ` ${p}: undefined, // TODO: real value (seeded ${p})`;
8345
+ });
8346
+ const block = params.length ? `{
8347
+ ${params.join("\n")}
8348
+ }` : "{}";
8349
+ return ` await ${s.snippet.exportName}.run(page, ${block});`;
8350
+ });
8351
+ const warnings = unbound.map(
8352
+ (s) => ` // \u26A0 GAP: no snippet for ${s.action} \u2014 scaffold_snippet then re-plan.`
8353
+ );
8354
+ const header = [`import { test } from '@playwright/test';`];
8355
+ if (usesFaker) header.push(`import { faker } from '@faker-js/faker';`);
8356
+ const code = `${header.join("\n")}
8357
+ ${[...importLines].join("\n")}
8358
+
8359
+ test(${JSON.stringify(opts.title ?? `plan: ${plan.target}`)}, async ({ page }) => {
8360
+ ${[...warnings, ...callLines].join("\n")}
8361
+ });
8362
+ `;
8363
+ return { code, missingFixtures: [...missing], complete: plan.runnable && missing.size === 0 };
8364
+ }
8365
+ var import_node_path33;
8366
+ var init_spec_gen = __esm({
8367
+ "src/server/graph/core/context/spec-gen.ts"() {
8368
+ "use strict";
8369
+ import_node_path33 = require("node:path");
8370
+ }
8371
+ });
8372
+
8177
8373
  // src/server/graph/core/language-detection.ts
8178
8374
  function walkForExtensions(dir, extCounts, depth = 0) {
8179
8375
  if (depth > 10) return;
8180
- if (!(0, import_node_fs25.existsSync)(dir)) return;
8376
+ if (!(0, import_node_fs26.existsSync)(dir)) return;
8181
8377
  let entries;
8182
8378
  try {
8183
- entries = (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true });
8379
+ entries = (0, import_node_fs26.readdirSync)(dir, { withFileTypes: true });
8184
8380
  } catch {
8185
8381
  return;
8186
8382
  }
@@ -8188,9 +8384,9 @@ function walkForExtensions(dir, extCounts, depth = 0) {
8188
8384
  if (entry.name.startsWith(".") && entry.isDirectory()) continue;
8189
8385
  if (entry.isDirectory()) {
8190
8386
  if (IGNORE_DIRS.has(entry.name)) continue;
8191
- walkForExtensions((0, import_node_path32.join)(dir, entry.name), extCounts, depth + 1);
8387
+ walkForExtensions((0, import_node_path34.join)(dir, entry.name), extCounts, depth + 1);
8192
8388
  } else {
8193
- const ext = (0, import_node_path32.extname)(entry.name).toLowerCase();
8389
+ const ext = (0, import_node_path34.extname)(entry.name).toLowerCase();
8194
8390
  if (ext && EXTENSION_TO_LANGUAGE[ext]) {
8195
8391
  extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
8196
8392
  }
@@ -8229,12 +8425,12 @@ function detectLanguages(rootDir, supportedLanguages) {
8229
8425
  });
8230
8426
  return results;
8231
8427
  }
8232
- var import_node_fs25, import_node_path32, EXTENSION_TO_LANGUAGE, IGNORE_DIRS, AUXILIARY_LANGUAGES;
8428
+ var import_node_fs26, import_node_path34, EXTENSION_TO_LANGUAGE, IGNORE_DIRS, AUXILIARY_LANGUAGES;
8233
8429
  var init_language_detection = __esm({
8234
8430
  "src/server/graph/core/language-detection.ts"() {
8235
8431
  "use strict";
8236
- import_node_fs25 = require("node:fs");
8237
- import_node_path32 = require("node:path");
8432
+ import_node_fs26 = require("node:fs");
8433
+ import_node_path34 = require("node:path");
8238
8434
  init_launch_kit_paths();
8239
8435
  EXTENSION_TO_LANGUAGE = {
8240
8436
  // Web / Frontend
@@ -8356,7 +8552,7 @@ __export(watcher_exports, {
8356
8552
  function isIgnoredPath(rel) {
8357
8553
  if (rel.startsWith(GRAPHS_RELATIVE)) return true;
8358
8554
  if (rel.endsWith(".lock") || rel.endsWith(".log")) return true;
8359
- for (const part of rel.split(import_node_path33.sep)) {
8555
+ for (const part of rel.split(import_node_path35.sep)) {
8360
8556
  if (IGNORE_SEGMENTS.has(part)) return true;
8361
8557
  }
8362
8558
  return false;
@@ -8398,7 +8594,7 @@ function startGraphWatcher(rootDir, opts = {}) {
8398
8594
  regenerating = false;
8399
8595
  }
8400
8596
  }
8401
- const watcher = (0, import_node_fs26.watch)(rootDir, { recursive: true }, (event, filename) => {
8597
+ const watcher = (0, import_node_fs27.watch)(rootDir, { recursive: true }, (event, filename) => {
8402
8598
  if (!filename) return;
8403
8599
  const rel = filename.toString();
8404
8600
  if (process.env.LAUNCH_CHART_WATCH_TRACE === "1") {
@@ -8431,12 +8627,12 @@ function startGraphWatcher(rootDir, opts = {}) {
8431
8627
  freshness: () => getFreshnessTracker(rootDir).get()
8432
8628
  };
8433
8629
  }
8434
- var import_node_fs26, import_node_path33, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
8630
+ var import_node_fs27, import_node_path35, IGNORE_SEGMENTS, TRIGGER_EXTENSIONS, GRAPHS_RELATIVE;
8435
8631
  var init_watcher = __esm({
8436
8632
  "src/server/graph/core/watcher.ts"() {
8437
8633
  "use strict";
8438
- import_node_fs26 = require("node:fs");
8439
- import_node_path33 = require("node:path");
8634
+ import_node_fs27 = require("node:fs");
8635
+ import_node_path35 = require("node:path");
8440
8636
  init_launch_kit_paths();
8441
8637
  init_graph();
8442
8638
  init_freshness();
@@ -8465,7 +8661,7 @@ var init_watcher = __esm({
8465
8661
  ".prisma",
8466
8662
  ".sql"
8467
8663
  ]);
8468
- GRAPHS_RELATIVE = (0, import_node_path33.join)(LAUNCHSECURE_DIR, "graphs");
8664
+ GRAPHS_RELATIVE = (0, import_node_path35.join)(LAUNCHSECURE_DIR, "graphs");
8469
8665
  }
8470
8666
  });
8471
8667
 
@@ -9198,12 +9394,12 @@ function handleReadGraph(args) {
9198
9394
  return okJson(result);
9199
9395
  }
9200
9396
  function nodeToFilePath(rootDir, layer, nodeId) {
9201
- if (layer === "ui" || layer === "api") return (0, import_node_path34.join)(rootDir, "src", nodeId);
9202
- if (layer === "db") return (0, import_node_path34.join)(rootDir, "prisma", "schema.prisma");
9203
- const withSrc = (0, import_node_path34.join)(rootDir, "src", nodeId);
9204
- if ((0, import_node_fs27.existsSync)(withSrc)) return withSrc;
9205
- const direct = (0, import_node_path34.join)(rootDir, nodeId);
9206
- if ((0, import_node_fs27.existsSync)(direct)) return direct;
9397
+ if (layer === "ui" || layer === "api") return (0, import_node_path36.join)(rootDir, "src", nodeId);
9398
+ if (layer === "db") return (0, import_node_path36.join)(rootDir, "prisma", "schema.prisma");
9399
+ const withSrc = (0, import_node_path36.join)(rootDir, "src", nodeId);
9400
+ if ((0, import_node_fs28.existsSync)(withSrc)) return withSrc;
9401
+ const direct = (0, import_node_path36.join)(rootDir, nodeId);
9402
+ if ((0, import_node_fs28.existsSync)(direct)) return direct;
9207
9403
  return null;
9208
9404
  }
9209
9405
  function handleInspectNode(args) {
@@ -9471,11 +9667,11 @@ function handleGrepNodes(args) {
9471
9667
  let filesSearched = 0;
9472
9668
  let truncated = false;
9473
9669
  for (const [filePath, nodeId] of filePaths) {
9474
- if (!(0, import_node_fs27.existsSync)(filePath)) continue;
9670
+ if (!(0, import_node_fs28.existsSync)(filePath)) continue;
9475
9671
  filesSearched++;
9476
9672
  let content;
9477
9673
  try {
9478
- content = (0, import_node_fs27.readFileSync)(filePath, "utf-8");
9674
+ content = (0, import_node_fs28.readFileSync)(filePath, "utf-8");
9479
9675
  } catch {
9480
9676
  continue;
9481
9677
  }
@@ -9694,11 +9890,11 @@ function handleStartChartServer(args) {
9694
9890
  });
9695
9891
  }
9696
9892
  const entryPath = process.argv[1];
9697
- const logDir = (0, import_node_path34.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
9698
- (0, import_node_fs27.mkdirSync)(logDir, { recursive: true });
9699
- const logPath = (0, import_node_path34.join)(logDir, "launch-chart.log");
9700
- const out = (0, import_node_fs27.openSync)(logPath, "a");
9701
- const err2 = (0, import_node_fs27.openSync)(logPath, "a");
9893
+ const logDir = (0, import_node_path36.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
9894
+ (0, import_node_fs28.mkdirSync)(logDir, { recursive: true });
9895
+ const logPath = (0, import_node_path36.join)(logDir, "launch-chart.log");
9896
+ const out = (0, import_node_fs28.openSync)(logPath, "a");
9897
+ const err2 = (0, import_node_fs28.openSync)(logPath, "a");
9702
9898
  const portArgs = args.port ? ["--port", String(args.port)] : [];
9703
9899
  const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
9704
9900
  detached: true,
@@ -9876,6 +10072,60 @@ function handleDriftReport(args) {
9876
10072
  items: page
9877
10073
  });
9878
10074
  }
10075
+ function handleGenerateSpec(args) {
10076
+ const __resolved = resolveOrErr(args);
10077
+ if ("content" in __resolved) return __resolved;
10078
+ const { rootDir } = __resolved;
10079
+ const target = typeof args.target === "string" ? args.target.trim() : "";
10080
+ if (!target) return err("target is required (a context action node id).");
10081
+ const snippetsDir = typeof args.snippets_dir === "string" && args.snippets_dir.trim() ? args.snippets_dir.trim() : DEFAULT_SNIPPETS_DIR;
10082
+ const specDir = typeof args.spec_dir === "string" && args.spec_dir.trim() ? args.spec_dir.trim() : "tests/generated";
10083
+ const fixtures = args.fixtures && typeof args.fixtures === "object" ? args.fixtures : {};
10084
+ const title = typeof args.title === "string" ? args.title : void 0;
10085
+ const g = readGraph(rootDir, "context");
10086
+ if (!g) return err("No `context` layer found. Run generate_graph first to build context.json.");
10087
+ let node = g.nodes.find((n) => n.id === target);
10088
+ if (!node && !target.startsWith("action:") && !target.startsWith("state:")) {
10089
+ node = g.nodes.find((n) => n.id === `action:${target}`) ?? g.nodes.find((n) => n.id === `state:${target}`);
10090
+ }
10091
+ if (!node) return err(`No context node matching "${target}". Use read_graph layer:context to list nodes.`);
10092
+ const snippets = loadSnippetIndex(rootDir, snippetsDir);
10093
+ const plan = buildTestPlan(g, node.id, snippets);
10094
+ const spec = generateSpec(plan, { title: title ?? String(node.name), specDir, fixtures });
10095
+ return okJson({
10096
+ target: node.id,
10097
+ spec_dir: specDir,
10098
+ suggested_path: `${specDir}/${String(node.name).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "") || "spec"}.spec.ts`,
10099
+ complete: spec.complete,
10100
+ missing_fixtures: spec.missingFixtures,
10101
+ plan_gaps: plan.gaps,
10102
+ code: spec.code,
10103
+ next: spec.complete ? "Write `code` to suggested_path and run `playwright test` (needs @faker-js/faker installed + the secret env vars set)." : "Supply `fixtures` for missing_fixtures (real seeded values), then regenerate."
10104
+ });
10105
+ }
10106
+ function handleTestPlan(args) {
10107
+ const __resolved = resolveOrErr(args);
10108
+ if ("content" in __resolved) return __resolved;
10109
+ const { rootDir } = __resolved;
10110
+ const target = typeof args.target === "string" ? args.target.trim() : "";
10111
+ if (!target) return err("target is required (a context action node id).");
10112
+ const snippetsDir = typeof args.snippets_dir === "string" && args.snippets_dir.trim() ? args.snippets_dir.trim() : DEFAULT_SNIPPETS_DIR;
10113
+ const g = readGraph(rootDir, "context");
10114
+ if (!g) return err("No `context` layer found. Run generate_graph first to build context.json.");
10115
+ let node = g.nodes.find((n) => n.id === target);
10116
+ if (!node && !target.startsWith("action:") && !target.startsWith("state:")) {
10117
+ node = g.nodes.find((n) => n.id === `action:${target}`) ?? g.nodes.find((n) => n.id === `state:${target}`);
10118
+ }
10119
+ if (!node) return err(`No context node matching "${target}". Use read_graph layer:context to list nodes.`);
10120
+ const snippets = loadSnippetIndex(rootDir, snippetsDir);
10121
+ const plan = buildTestPlan(g, node.id, snippets);
10122
+ return okJson({
10123
+ ...plan,
10124
+ snippets_dir: snippetsDir,
10125
+ snippets_found: snippets.size,
10126
+ next: plan.runnable ? "Runnable: execute the snippets in `steps` order, threading produced state forward." : "Not yet runnable \u2014 resolve `gaps` (scaffold_snippet for missing bindings; add a producer for unmet states)."
10127
+ });
10128
+ }
9879
10129
  function handleScaffoldSnippet(args) {
9880
10130
  const __resolved = resolveOrErr(args);
9881
10131
  if ("content" in __resolved) return __resolved;
@@ -10157,20 +10407,20 @@ function handleDetectProjectStack() {
10157
10407
  if (ref.type === "references_api") stats.references_api++;
10158
10408
  }
10159
10409
  }
10160
- const srcDir = (0, import_node_path34.join)(rootDir, "src");
10161
- if ((0, import_node_fs27.existsSync)(srcDir)) {
10410
+ const srcDir = (0, import_node_path36.join)(rootDir, "src");
10411
+ if ((0, import_node_fs28.existsSync)(srcDir)) {
10162
10412
  const scanDir = (dir) => {
10163
- if (!(0, import_node_fs27.existsSync)(dir)) return;
10164
- for (const entry of (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true })) {
10413
+ if (!(0, import_node_fs28.existsSync)(dir)) return;
10414
+ for (const entry of (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true })) {
10165
10415
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
10166
- const full = (0, import_node_path34.join)(dir, entry.name);
10416
+ const full = (0, import_node_path36.join)(dir, entry.name);
10167
10417
  if (entry.isDirectory()) {
10168
10418
  scanDir(full);
10169
10419
  continue;
10170
10420
  }
10171
- if (![".ts", ".tsx"].includes((0, import_node_path34.extname)(entry.name))) continue;
10421
+ if (![".ts", ".tsx"].includes((0, import_node_path36.extname)(entry.name))) continue;
10172
10422
  try {
10173
- const content = (0, import_node_fs27.readFileSync)(full, "utf-8");
10423
+ const content = (0, import_node_fs28.readFileSync)(full, "utf-8");
10174
10424
  const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
10175
10425
  if (matches) stats.annotations += matches.length;
10176
10426
  } catch {
@@ -10189,7 +10439,7 @@ function handleDetectProjectStack() {
10189
10439
  name: p.name,
10190
10440
  root: p.root,
10191
10441
  absolute_root: p.absoluteRoot,
10192
- has_graph: (0, import_node_fs27.existsSync)((0, import_node_path34.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
10442
+ has_graph: (0, import_node_fs28.existsSync)((0, import_node_path36.join)(p.absoluteRoot, LAUNCHSECURE_DIR, "graphs"))
10193
10443
  }));
10194
10444
  return okJson({
10195
10445
  languages,
@@ -10323,6 +10573,14 @@ async function handleMessage(msg) {
10323
10573
  respond(id ?? null, withFreshnessMeta(handleScaffoldSnippet(args), args));
10324
10574
  return;
10325
10575
  }
10576
+ if (toolName === "test_plan") {
10577
+ respond(id ?? null, withFreshnessMeta(handleTestPlan(args), args));
10578
+ return;
10579
+ }
10580
+ if (toolName === "generate_spec") {
10581
+ respond(id ?? null, withFreshnessMeta(handleGenerateSpec(args), args));
10582
+ return;
10583
+ }
10326
10584
  if (toolName === "trace_path") {
10327
10585
  respond(id ?? null, withFreshnessMeta(handleTracePath(args), args));
10328
10586
  return;
@@ -10391,17 +10649,20 @@ function startGraphMcpServer() {
10391
10649
  process.stderr.write(`[launchsecure-graph] MCP server started (cwd: ${process.cwd()})
10392
10650
  `);
10393
10651
  }
10394
- var import_node_fs27, import_node_path34, import_node_child_process2, import_node_os4, SERVER_INFO, TOOLS, MINIMAL_STRIP_FIELDS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, DEEP_FIELDS, NOTE_KIND_CATEGORY, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, DEFAULT_EST_NODE_FULL, DEFAULT_EST_NODE_MIN, DEFAULT_EST_EDGE, NEIGHBORHOOD_BUDGET_CHARS, MAX_FILTER_EDGES, BATCH_BUDGET_CHARS, watcherHandle;
10652
+ var import_node_fs28, import_node_path36, import_node_child_process2, import_node_os4, SERVER_INFO, TOOLS, MINIMAL_STRIP_FIELDS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, DEEP_FIELDS, NOTE_KIND_CATEGORY, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, DEFAULT_EST_NODE_FULL, DEFAULT_EST_NODE_MIN, DEFAULT_EST_EDGE, NEIGHBORHOOD_BUDGET_CHARS, MAX_FILTER_EDGES, BATCH_BUDGET_CHARS, watcherHandle;
10395
10653
  var init_graph_mcp = __esm({
10396
10654
  "src/server/graph-mcp.ts"() {
10397
10655
  "use strict";
10398
- import_node_fs27 = require("node:fs");
10399
- import_node_path34 = require("node:path");
10656
+ import_node_fs28 = require("node:fs");
10657
+ import_node_path36 = require("node:path");
10400
10658
  import_node_child_process2 = require("node:child_process");
10401
10659
  import_node_os4 = require("node:os");
10402
10660
  init_launch_kit_paths();
10403
10661
  init_graph();
10404
10662
  init_snippet();
10663
+ init_snippet_registry();
10664
+ init_test_plan();
10665
+ init_spec_gen();
10405
10666
  init_lockfile();
10406
10667
  init_config();
10407
10668
  init_parser_registry();
@@ -10800,6 +11061,39 @@ USE THIS FOR: "are there unsafe migrations on this branch", "audit the migration
10800
11061
  required: ["target"]
10801
11062
  }
10802
11063
  },
11064
+ {
11065
+ name: "test_plan",
11066
+ description: 'Assemble the ordered snippet chain needed to exercise a target action. Walks the context graph\'s requires/produces backward from the target (prerequisites first, target last), binds each step to its snippet (from tests/snippets/), and flags gaps \u2014 a required state nothing produces, an action with no snippet, or a cycle.\n\nUSE THIS FOR: "what\'s the test plan for creating a tag", "what setup does action X need". Returns { target, steps[], gaps[], runnable }. Requires the `context` layer (run generate_graph first). When runnable is false, the gaps tell you which snippets to scaffold next.',
11067
+ inputSchema: {
11068
+ type: "object",
11069
+ properties: {
11070
+ target: { type: "string", description: 'Context action node id (e.g. "action:app/api/orgs/[orgSlug]/tags/route.ts") or the bare key.' },
11071
+ snippets_dir: { type: "string", description: `Snippets directory relative to project root. Default "${DEFAULT_SNIPPETS_DIR}".` },
11072
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
11073
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
11074
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
11075
+ },
11076
+ required: ["target"]
11077
+ }
11078
+ },
11079
+ {
11080
+ name: "generate_spec",
11081
+ description: 'Generate a runnable Playwright .spec.ts from a target action: builds the test_plan, then emits imports + ordered `await snippet.run(page, {...})` calls. Params are auto-classified \u2014 secrets \u2192 process.env, free-form (name/color/icon/description) \u2192 faker.*, and state-bound (email/slug/id) \u2192 TODO + returned in missingFixtures. Supply those via `fixtures`.\n\nUSE THIS FOR: "generate the test for creating a tag". Returns { code, missingFixtures, complete }. complete:true means every state-bound param had a fixture (secrets via env + faker auto). Requires the `context` layer + snippets.',
11082
+ inputSchema: {
11083
+ type: "object",
11084
+ properties: {
11085
+ target: { type: "string", description: "Context action node id (or bare key)." },
11086
+ fixtures: { type: "object", description: 'param name -> value for state-bound params (e.g. {"email":"...","orgSlug":"..."}). Overrides classification.', additionalProperties: true },
11087
+ title: { type: "string", description: "Test title. Defaults to the target." },
11088
+ spec_dir: { type: "string", description: 'Where the spec will live (for import paths). Default "tests/generated".' },
11089
+ snippets_dir: { type: "string", description: `Snippets directory. Default "${DEFAULT_SNIPPETS_DIR}".` },
11090
+ project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
11091
+ worktree: { type: "string", description: WORKTREE_PARAM_DESCRIPTION2 },
11092
+ project_root: { type: "string", description: PROJECT_ROOT_PARAM_DESCRIPTION2 }
11093
+ },
11094
+ required: ["target"]
11095
+ }
11096
+ },
10803
11097
  {
10804
11098
  name: "auth_coverage_report",
10805
11099
  description: 'Aggregate every API endpoint by its auth[] wrapper(s) \u2014 surfaces endpoints with empty auth, groups by module, shows which auth strategies dominate. Computed from api.json endpoint auth field (100% populated).\n\nUSE THIS FOR: "are there unauthenticated endpoints", "which auth wrappers are in use", "audit auth strategy consistency across modules". Paginated. Returns { total, by_strategy: {strategy: count}, unauthenticated: [endpoint_ids], by_module: {module: {total, by_strategy}} } plus the paginated endpoint list.',
@@ -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");
@@ -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.40",
611
+ version: "0.0.42",
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.40",
3
+ "version": "0.0.42",
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.