@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.
- 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-jjBWyhry.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-CwAGy9lU.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.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-Bk0FUbxU.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-DqOvWr1k.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.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-aiv9GZnL.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.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-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.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-CfXcK1qH.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-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.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-BC1MYBn6.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-DKnITsD4.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
- package/dist/deck-client/assets/wardley-RL74JXVD-Bxo5x40D.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
- package/dist/deck-client/index.html +2 -2
- package/dist/server/beacon-monitor-entry.js +106 -24
- package/dist/server/chart-serve.js +544 -247
- package/dist/server/cli.js +1016 -324
- package/dist/server/council-entry.js +23 -4
- package/dist/server/council-serve.js +22 -3
- package/dist/server/deck-mcp-entry.js +523 -125
- package/dist/server/deck-serve.js +326 -40
- package/dist/server/graph-mcp-entry.js +1160 -357
- package/dist/server/init-entry.js +17 -7
- package/dist/server/orbit-entry.js +91 -7
- package/dist/server/recall-entry.js +94 -12
- 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/scaffolds/statusline/statusline-mcp.sh +68 -19
- package/scaffolds/statusline/statusline-wrapper.sh +12 -9
- package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
- package/dist/client/assets/index-CMN3tlGP.css +0 -32
- package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
- package/dist/deck-client/assets/_baseUniq-DOrnEQMI.js +0 -1
- package/dist/deck-client/assets/arc-DOWK7V3m.js +0 -1
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DPhzvk7q.js +0 -36
- package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
- package/dist/deck-client/assets/chunk-4BX2VUAB-RKm0LXpu.js +0 -1
- package/dist/deck-client/assets/chunk-55IACEB6-Cl3hja-M.js +0 -1
- package/dist/deck-client/assets/chunk-EDXVE4YY-CNIMQCV2.js +0 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-6_kraYpP.js +0 -1
- package/dist/deck-client/assets/chunk-YZCP3GAM-FgAwIWlo.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CigVnnPr.js +0 -1
- package/dist/deck-client/assets/dagre-KV5264BT-DHZXTktX.js +0 -4
- package/dist/deck-client/assets/diagram-5BDNPKRD-H5k0eauU.js +0 -10
- package/dist/deck-client/assets/diagram-G4DWMVQ6-Bg3dFhSY.js +0 -24
- package/dist/deck-client/assets/diagram-MMDJMWI5-CQLC410N.js +0 -43
- package/dist/deck-client/assets/diagram-TYMM5635-DFTCHVkP.js +0 -24
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DSaGMPM4.js +0 -292
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-DMjL2Vix.js +0 -106
- package/dist/deck-client/assets/graph-B7Vn5lkK.js +0 -1
- package/dist/deck-client/assets/index-BD36e-tD.js +0 -1196
- package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-mNi4iygG.js +0 -2
- package/dist/deck-client/assets/layout-CZTyRhOG.js +0 -1
- package/dist/deck-client/assets/linear--7n7iEvd.js +0 -1
- package/dist/deck-client/assets/min-Bh130DN8.js +0 -1
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DjVHLAVw.js +0 -30
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BOH9sLyh.js +0 -10
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-kNp9bv8K.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
- /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-CrYM1-ac.js} +0 -0
- /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.
|
|
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,
|
|
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}${
|
|
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
|
|
3064
|
-
var
|
|
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,
|
|
3332
|
-
if ((0,
|
|
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,
|
|
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,
|
|
288
|
-
const pid = Number((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
428
|
+
if ((0, import_node_fs2.existsSync)(cfgPath)) {
|
|
429
429
|
try {
|
|
430
|
-
config = JSON.parse((0,
|
|
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,
|
|
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,
|
|
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
|
|
554
|
+
void import_node_fs2.statSync;
|
|
555
555
|
}
|
|
556
|
-
var import_node_child_process2,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
@@ -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.
|