@launchsecure/launch-kit 0.0.40 → 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.
- package/dist/chart-client/assets/index-Dd6IotOZ.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-DE0uje6k.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-CGYusOCK.css +1 -0
- package/dist/council-client/assets/{index-B1v46vTE.js → index-DkTFX53U.js} +1 -1
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/_baseUniq-mvYvzeEJ.js +1 -0
- package/dist/deck-client/assets/arc-CX4ylnp2.js +1 -0
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-BkR-5IRK.js +36 -0
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CUdblaWk.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.js → c4Diagram-AHTNJAMY-Cbq1rlG8.js} +2 -2
- package/dist/deck-client/assets/channel-B9GC-CLn.js +1 -0
- package/dist/deck-client/assets/chunk-4BX2VUAB-D58Co4lU.js +1 -0
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BUJtZ7jO.js → chunk-4TB4RGXK-BYvhTm3d.js} +1 -1
- package/dist/deck-client/assets/chunk-55IACEB6-oWukUhYg.js +1 -0
- package/dist/deck-client/assets/chunk-EDXVE4YY-Cm58kVnZ.js +1 -0
- package/dist/deck-client/assets/{chunk-FMBD7UC4-PnZ9v6ey.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.js → chunk-OYMX7WX6-C72wigPl.js} +1 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-CLgeuAKw.js +1 -0
- package/dist/deck-client/assets/chunk-YZCP3GAM-HDDlJ5oI.js +1 -0
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/clone-n-WQlAGe.js +1 -0
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CUXQKg2M.js +1 -0
- package/dist/deck-client/assets/dagre-KV5264BT-C5M-fVDc.js +4 -0
- package/dist/deck-client/assets/diagram-5BDNPKRD-CcVsQ0S8.js +10 -0
- package/dist/deck-client/assets/diagram-G4DWMVQ6-DJswXyep.js +24 -0
- package/dist/deck-client/assets/diagram-MMDJMWI5-CGT76fm1.js +43 -0
- package/dist/deck-client/assets/diagram-TYMM5635-BBsYUNN6.js +24 -0
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-56pn_93p.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.js → flowDiagram-DWJPFMVM-DLuDYIKT.js} +2 -2
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-B19b6Qtj.js +292 -0
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-BYLAfYVS.js +106 -0
- package/dist/deck-client/assets/graph-CfzQUfPh.js +1 -0
- package/dist/deck-client/assets/index-DlwdTgE_.js +892 -0
- package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-Dp3mUA9c.js +2 -0
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.js → kanban-definition-6JOO6SKY-D9-lmhQf.js} +2 -2
- package/dist/deck-client/assets/layout-CfIe_Su8.js +1 -0
- package/dist/deck-client/assets/linear-09ZFRoh_.js +1 -0
- package/dist/deck-client/assets/mermaid.core-BaQyIOvj.js +309 -0
- package/dist/deck-client/assets/min-CYwCzYaL.js +1 -0
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-oAybLedr.js → mindmap-definition-QFDTVHPH-CouFxf6C.js} +2 -2
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DMB1ufC0.js +30 -0
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-dtluDZXs.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.js → requirementDiagram-MS252O5E-BMc3GJkx.js} +2 -2
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-CxACUncm.js +10 -0
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CYkd7oQK.js → sequenceDiagram-FGHM5R23-Ch-P3Mzc.js} +2 -2
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-Cy8n7Yzk.js +1 -0
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DZIxSyd1.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
- package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-Bxo5x40D.js} +3 -3
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
- package/dist/deck-client/index.html +2 -2
- package/dist/server/cli.js +488 -215
- package/dist/server/council-entry.js +7 -1
- package/dist/server/council-serve.js +7 -1
- package/dist/server/deck-mcp-entry.js +287 -49
- package/dist/server/deck-serve.js +258 -48
- package/dist/server/graph-mcp-entry.js +331 -37
- package/dist/server/init-entry.js +17 -7
- package/dist/server/rover-entry.js +1 -1
- package/package.json +1 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
- package/dist/chart-client/assets/index-CWJFFDPu.css +0 -1
- package/dist/client/assets/index-CTzFcfGV.css +0 -32
- package/dist/council-client/assets/index-ArgRc5mN.css +0 -1
- package/dist/deck-client/assets/_baseUniq-BZP7n41F.js +0 -1
- package/dist/deck-client/assets/arc-31biU3Az.js +0 -1
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DHg6Ss--.js +0 -36
- package/dist/deck-client/assets/channel-BBkRLdnC.js +0 -1
- package/dist/deck-client/assets/chunk-4BX2VUAB-DQ1MrGgN.js +0 -1
- package/dist/deck-client/assets/chunk-55IACEB6-BdSnXB6g.js +0 -1
- package/dist/deck-client/assets/chunk-EDXVE4YY-94yZIUI8.js +0 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-CsIGIDKX.js +0 -1
- package/dist/deck-client/assets/chunk-YZCP3GAM-DVkBO9tn.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-DFCaeF-7.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-DFCaeF-7.js +0 -1
- package/dist/deck-client/assets/clone-GCEVRScB.js +0 -1
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-m126Oh3b.js +0 -1
- package/dist/deck-client/assets/dagre-KV5264BT-C2aig8U5.js +0 -4
- package/dist/deck-client/assets/diagram-5BDNPKRD-CKpoRfGn.js +0 -10
- package/dist/deck-client/assets/diagram-G4DWMVQ6-Cjh115Ep.js +0 -24
- package/dist/deck-client/assets/diagram-MMDJMWI5-DKlBv_2L.js +0 -43
- package/dist/deck-client/assets/diagram-TYMM5635-CdBh4cEn.js +0 -24
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DTIsC6Zg.js +0 -292
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-CJYeyCLe.js +0 -106
- package/dist/deck-client/assets/graph-BDvMu1Ss.js +0 -1
- package/dist/deck-client/assets/index-D4eSxcBn.css +0 -1
- package/dist/deck-client/assets/index-QnGVE9PZ.js +0 -1196
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-BWyKJnpW.js +0 -2
- package/dist/deck-client/assets/layout-vOnxnCQU.js +0 -1
- package/dist/deck-client/assets/linear-B0J0WCGz.js +0 -1
- package/dist/deck-client/assets/min-B0AXlT9L.js +0 -1
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-BjHyHxGk.js +0 -30
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-C1Vih91z.js +0 -10
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-CtyG8wBK.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BLyKWfcN.js +0 -1
- /package/dist/chart-client/assets/{index-Dzlj-oCj.js → index-CrYM1-ac.js} +0 -0
- /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,
|
|
8376
|
+
if (!(0, import_node_fs26.existsSync)(dir)) return;
|
|
8181
8377
|
let entries;
|
|
8182
8378
|
try {
|
|
8183
|
-
entries = (0,
|
|
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,
|
|
8387
|
+
walkForExtensions((0, import_node_path34.join)(dir, entry.name), extCounts, depth + 1);
|
|
8192
8388
|
} else {
|
|
8193
|
-
const ext = (0,
|
|
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
|
|
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
|
-
|
|
8237
|
-
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
8439
|
-
|
|
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,
|
|
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,
|
|
9202
|
-
if (layer === "db") return (0,
|
|
9203
|
-
const withSrc = (0,
|
|
9204
|
-
if ((0,
|
|
9205
|
-
const direct = (0,
|
|
9206
|
-
if ((0,
|
|
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,
|
|
9670
|
+
if (!(0, import_node_fs28.existsSync)(filePath)) continue;
|
|
9475
9671
|
filesSearched++;
|
|
9476
9672
|
let content;
|
|
9477
9673
|
try {
|
|
9478
|
-
content = (0,
|
|
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,
|
|
9698
|
-
(0,
|
|
9699
|
-
const logPath = (0,
|
|
9700
|
-
const out = (0,
|
|
9701
|
-
const err2 = (0,
|
|
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,
|
|
10161
|
-
if ((0,
|
|
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,
|
|
10164
|
-
for (const entry of (0,
|
|
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,
|
|
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,
|
|
10421
|
+
if (![".ts", ".tsx"].includes((0, import_node_path36.extname)(entry.name))) continue;
|
|
10172
10422
|
try {
|
|
10173
|
-
const content = (0,
|
|
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,
|
|
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
|
|
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
|
-
|
|
10399
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|