@launchsecure/launch-kit 0.0.5 → 0.0.7
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-0Xm1mXjM.js +379 -0
- package/dist/chart-client/assets/{index-DFslt72L.css → index-C-OUsIfD.css} +1 -1
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/{index-DCC--GO-.js → index-Ci95xk2_.js} +1 -1
- package/dist/client/assets/index-DbqEe7we.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chart-serve.js +611 -99
- package/dist/server/cli.js +621 -124
- package/dist/server/graph-mcp-entry.js +760 -129
- package/package.json +1 -1
- package/dist/chart-client/assets/index-BUih0oqR.js +0 -358
- package/dist/client/assets/index-BCxRNp8I.css +0 -32
package/dist/server/cli.js
CHANGED
|
@@ -1727,7 +1727,7 @@ var require_usage_reader = __commonJS({
|
|
|
1727
1727
|
async readJsonlFile(filePath, cutoffTime) {
|
|
1728
1728
|
const entries = [];
|
|
1729
1729
|
const fileProcessedEntries = /* @__PURE__ */ new Set();
|
|
1730
|
-
return new Promise((
|
|
1730
|
+
return new Promise((resolve3) => {
|
|
1731
1731
|
const rl = readline.createInterface({
|
|
1732
1732
|
input: createReadStream(filePath),
|
|
1733
1733
|
crlfDelay: Infinity
|
|
@@ -1785,11 +1785,11 @@ var require_usage_reader = __commonJS({
|
|
|
1785
1785
|
}
|
|
1786
1786
|
});
|
|
1787
1787
|
rl.on("close", () => {
|
|
1788
|
-
|
|
1788
|
+
resolve3(entries);
|
|
1789
1789
|
});
|
|
1790
1790
|
rl.on("error", (error) => {
|
|
1791
1791
|
console.error("Error reading file:", filePath, error);
|
|
1792
|
-
|
|
1792
|
+
resolve3(entries);
|
|
1793
1793
|
});
|
|
1794
1794
|
});
|
|
1795
1795
|
}
|
|
@@ -3587,7 +3587,7 @@ var require_src = __commonJS({
|
|
|
3587
3587
|
if (session.active) throw new Error(`Agent already running in session ${sessionId}`);
|
|
3588
3588
|
const { command, args = [], env = {} } = options;
|
|
3589
3589
|
if (!command) throw new Error("startScriptInSession requires a command");
|
|
3590
|
-
return new Promise((
|
|
3590
|
+
return new Promise((resolve3, reject) => {
|
|
3591
3591
|
this.scriptBridge.startSession(sessionId, {
|
|
3592
3592
|
command,
|
|
3593
3593
|
args,
|
|
@@ -3609,7 +3609,7 @@ var require_src = __commonJS({
|
|
|
3609
3609
|
session.lastActivity = /* @__PURE__ */ new Date();
|
|
3610
3610
|
this.broadcastToSession(sessionId, { type: "script_stopped", sessionId });
|
|
3611
3611
|
if (exitCode === 0) {
|
|
3612
|
-
|
|
3612
|
+
resolve3({ code: exitCode, signal });
|
|
3613
3613
|
} else {
|
|
3614
3614
|
reject(new Error(`Script exited with code ${exitCode}`));
|
|
3615
3615
|
}
|
|
@@ -3701,6 +3701,30 @@ var require_src = __commonJS({
|
|
|
3701
3701
|
}
|
|
3702
3702
|
});
|
|
3703
3703
|
|
|
3704
|
+
// src/server/graph/core/config.ts
|
|
3705
|
+
var config_exports = {};
|
|
3706
|
+
__export(config_exports, {
|
|
3707
|
+
loadConfig: () => loadConfig
|
|
3708
|
+
});
|
|
3709
|
+
function loadConfig(rootDir) {
|
|
3710
|
+
const configPath = (0, import_node_path.join)(rootDir, CONFIG_FILENAME);
|
|
3711
|
+
if (!(0, import_node_fs.existsSync)(configPath)) return {};
|
|
3712
|
+
try {
|
|
3713
|
+
return JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf-8"));
|
|
3714
|
+
} catch {
|
|
3715
|
+
return {};
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
var import_node_fs, import_node_path, CONFIG_FILENAME;
|
|
3719
|
+
var init_config = __esm({
|
|
3720
|
+
"src/server/graph/core/config.ts"() {
|
|
3721
|
+
"use strict";
|
|
3722
|
+
import_node_fs = require("node:fs");
|
|
3723
|
+
import_node_path = require("node:path");
|
|
3724
|
+
CONFIG_FILENAME = ".launchchart.json";
|
|
3725
|
+
}
|
|
3726
|
+
});
|
|
3727
|
+
|
|
3704
3728
|
// src/server/cli.ts
|
|
3705
3729
|
var import_http = __toESM(require("http"));
|
|
3706
3730
|
var import_https = __toESM(require("https"));
|
|
@@ -5270,7 +5294,7 @@ var PostImplLaunchExecutor = class {
|
|
|
5270
5294
|
return 3001;
|
|
5271
5295
|
}
|
|
5272
5296
|
startDevServer(port, databaseUrl) {
|
|
5273
|
-
return new Promise((
|
|
5297
|
+
return new Promise((resolve3) => {
|
|
5274
5298
|
const env = { ...process.env, PORT: String(port), ...databaseUrl ? { DATABASE_URL: databaseUrl } : {} };
|
|
5275
5299
|
this.devProcess = (0, import_child_process3.spawn)("npm", ["run", "dev"], {
|
|
5276
5300
|
cwd: this.workingDir,
|
|
@@ -5282,7 +5306,7 @@ var PostImplLaunchExecutor = class {
|
|
|
5282
5306
|
const timeout = setTimeout(() => {
|
|
5283
5307
|
if (!resolved) {
|
|
5284
5308
|
resolved = true;
|
|
5285
|
-
this.healthCheck(port).then(
|
|
5309
|
+
this.healthCheck(port).then(resolve3);
|
|
5286
5310
|
}
|
|
5287
5311
|
}, 15e3);
|
|
5288
5312
|
const onData = (data) => {
|
|
@@ -5291,7 +5315,7 @@ var PostImplLaunchExecutor = class {
|
|
|
5291
5315
|
if (!resolved) {
|
|
5292
5316
|
resolved = true;
|
|
5293
5317
|
clearTimeout(timeout);
|
|
5294
|
-
|
|
5318
|
+
resolve3(true);
|
|
5295
5319
|
}
|
|
5296
5320
|
}
|
|
5297
5321
|
};
|
|
@@ -5302,7 +5326,7 @@ var PostImplLaunchExecutor = class {
|
|
|
5302
5326
|
if (!resolved) {
|
|
5303
5327
|
resolved = true;
|
|
5304
5328
|
clearTimeout(timeout);
|
|
5305
|
-
|
|
5329
|
+
resolve3(false);
|
|
5306
5330
|
}
|
|
5307
5331
|
});
|
|
5308
5332
|
this.devProcess.unref();
|
|
@@ -6324,26 +6348,13 @@ ${links}
|
|
|
6324
6348
|
}
|
|
6325
6349
|
|
|
6326
6350
|
// src/server/graph/index.ts
|
|
6327
|
-
var
|
|
6328
|
-
var
|
|
6351
|
+
var import_node_fs11 = require("node:fs");
|
|
6352
|
+
var import_node_path13 = require("node:path");
|
|
6329
6353
|
|
|
6330
6354
|
// src/server/graph/core/graph-builder.ts
|
|
6331
6355
|
var import_node_fs8 = require("node:fs");
|
|
6332
6356
|
var import_node_path9 = require("node:path");
|
|
6333
|
-
|
|
6334
|
-
// src/server/graph/core/config.ts
|
|
6335
|
-
var import_node_fs = require("node:fs");
|
|
6336
|
-
var import_node_path = require("node:path");
|
|
6337
|
-
var CONFIG_FILENAME = ".launchchart.json";
|
|
6338
|
-
function loadConfig(rootDir) {
|
|
6339
|
-
const configPath = (0, import_node_path.join)(rootDir, CONFIG_FILENAME);
|
|
6340
|
-
if (!(0, import_node_fs.existsSync)(configPath)) return {};
|
|
6341
|
-
try {
|
|
6342
|
-
return JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf-8"));
|
|
6343
|
-
} catch {
|
|
6344
|
-
return {};
|
|
6345
|
-
}
|
|
6346
|
-
}
|
|
6357
|
+
init_config();
|
|
6347
6358
|
|
|
6348
6359
|
// src/server/graph/core/parser-registry.ts
|
|
6349
6360
|
var import_node_path8 = require("node:path");
|
|
@@ -6809,34 +6820,6 @@ function classifyType(id) {
|
|
|
6809
6820
|
if (id.startsWith("lib/") || id.startsWith("config/")) return "lib";
|
|
6810
6821
|
return "component";
|
|
6811
6822
|
}
|
|
6812
|
-
function classifyModule(id) {
|
|
6813
|
-
if (/app\/\(auth\)\//.test(id)) return "auth";
|
|
6814
|
-
if (/app\/\(admin\)\//.test(id)) return "admin";
|
|
6815
|
-
if (/app\/\(settings\)\//.test(id)) return "settings";
|
|
6816
|
-
if (/app\/\(app\)\/\[orgSlug\]\/\(project-pages\)\//.test(id)) return "project";
|
|
6817
|
-
if (/app\/\(app\)\/\[orgSlug\]\/\(org-pages\)\//.test(id)) return "org";
|
|
6818
|
-
if (/app\/\(app\)\/\[orgSlug\]\//.test(id)) return "org";
|
|
6819
|
-
if (id.startsWith("app/integrations/")) return "integrations";
|
|
6820
|
-
if (id.startsWith("app/docs/")) return "admin";
|
|
6821
|
-
if (id.startsWith("client/components/ui/")) return "shared-ui";
|
|
6822
|
-
if (id.startsWith("client/components/layout/") || /client\/lib\/navigation/.test(id)) return "layout";
|
|
6823
|
-
if (/client\/components\/auth\//.test(id) || /client\/lib\/auth-/.test(id) || /client\/lib\/github-oauth/.test(id) || /client\/lib\/permission-service/.test(id) || /client\/hooks\/use-permissions/.test(id)) return "auth";
|
|
6824
|
-
if (/client\/components\/prd-/.test(id) || /client\/hooks\/use-admin/.test(id)) return "admin";
|
|
6825
|
-
if (/client\/components\/org-/.test(id) || /client\/hooks\/use-org-/.test(id) || /client\/hooks\/use-provider-def/.test(id)) return "org";
|
|
6826
|
-
if (/client\/components\/project/.test(id) || /client\/hooks\/use-project-/.test(id) || /client\/hooks\/use-pipeline/.test(id) || /client\/hooks\/use-databases/.test(id) || /client\/hooks\/use-provider-env/.test(id) || /client\/hooks\/use-role-assign/.test(id) || /client\/components\/pipeline/.test(id) || /client\/components\/deployments/.test(id)) return "project";
|
|
6827
|
-
if (/client\/hooks\/use-(profile|sessions|organizations|notification)/.test(id)) return "settings";
|
|
6828
|
-
if (id.startsWith("server/auth/")) return "auth";
|
|
6829
|
-
if (id.startsWith("server/mcp/")) return "mcp";
|
|
6830
|
-
if (id.startsWith("server/lib/")) return "server-lib";
|
|
6831
|
-
if (id.startsWith("server/middleware")) return "middleware";
|
|
6832
|
-
if (id.startsWith("server/services/")) return "services";
|
|
6833
|
-
if (id.startsWith("server/db")) return "db";
|
|
6834
|
-
if (id.startsWith("server/errors")) return "errors";
|
|
6835
|
-
if (id.startsWith("server/")) return "server-lib";
|
|
6836
|
-
if (id.startsWith("config/")) return "config";
|
|
6837
|
-
if (id.startsWith("lib/")) return "lib";
|
|
6838
|
-
return "root";
|
|
6839
|
-
}
|
|
6840
6823
|
function extractRoute(id) {
|
|
6841
6824
|
if (!id.endsWith("/page.tsx")) return null;
|
|
6842
6825
|
let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
|
|
@@ -7057,8 +7040,7 @@ function generate(rootDir) {
|
|
|
7057
7040
|
const parsed = parsedByPath.get(absPath);
|
|
7058
7041
|
const name = parsed.name || nameFromFilename(absPath);
|
|
7059
7042
|
const route = extractRoute(id);
|
|
7060
|
-
|
|
7061
|
-
nodes.push({ id, type, name, route, module: module_, exports: parsed.exports });
|
|
7043
|
+
nodes.push({ id, type, name, route, exports: parsed.exports });
|
|
7062
7044
|
nodeIdSet.add(id);
|
|
7063
7045
|
nodeTypeMap.set(id, type);
|
|
7064
7046
|
if (route) routeToNodeId.set(route, id);
|
|
@@ -7162,7 +7144,6 @@ function generate(rootDir) {
|
|
|
7162
7144
|
type: "external",
|
|
7163
7145
|
name: parsed.name || nameFromFilename(absPath),
|
|
7164
7146
|
route: null,
|
|
7165
|
-
module: "external",
|
|
7166
7147
|
exports: parsed.exports
|
|
7167
7148
|
});
|
|
7168
7149
|
nodeIdSet.add(externalId);
|
|
@@ -8177,31 +8158,425 @@ function generateAll(rootDir) {
|
|
|
8177
8158
|
}
|
|
8178
8159
|
|
|
8179
8160
|
// src/server/graph/index.ts
|
|
8161
|
+
init_config();
|
|
8162
|
+
|
|
8163
|
+
// src/server/graph/core/tagger-registry.ts
|
|
8164
|
+
var import_node_path11 = require("node:path");
|
|
8165
|
+
|
|
8166
|
+
// src/server/graph/taggers/module-tagger.ts
|
|
8167
|
+
var import_node_fs9 = require("node:fs");
|
|
8168
|
+
var import_node_path10 = require("node:path");
|
|
8169
|
+
function matchGlob(pattern, id) {
|
|
8170
|
+
const patParts = pattern.split("/");
|
|
8171
|
+
const idParts = id.split("/");
|
|
8172
|
+
return matchParts(patParts, 0, idParts, 0);
|
|
8173
|
+
}
|
|
8174
|
+
function matchParts(pat, pi, id, ii) {
|
|
8175
|
+
while (pi < pat.length && ii < id.length) {
|
|
8176
|
+
const p = pat[pi];
|
|
8177
|
+
if (p === "**") {
|
|
8178
|
+
for (let skip = ii; skip <= id.length; skip++) {
|
|
8179
|
+
if (matchParts(pat, pi + 1, id, skip)) return true;
|
|
8180
|
+
}
|
|
8181
|
+
return false;
|
|
8182
|
+
}
|
|
8183
|
+
if (p === "*") {
|
|
8184
|
+
pi++;
|
|
8185
|
+
ii++;
|
|
8186
|
+
continue;
|
|
8187
|
+
}
|
|
8188
|
+
if (p !== id[ii]) return false;
|
|
8189
|
+
pi++;
|
|
8190
|
+
ii++;
|
|
8191
|
+
}
|
|
8192
|
+
while (pi < pat.length && pat[pi] === "**") pi++;
|
|
8193
|
+
return pi === pat.length && ii === id.length;
|
|
8194
|
+
}
|
|
8195
|
+
var CONVENTION_DIRS = ["features", "modules", "domains", "areas"];
|
|
8196
|
+
function detectConventionDirs(rootDir) {
|
|
8197
|
+
const result = /* @__PURE__ */ new Map();
|
|
8198
|
+
const searchDirs = [
|
|
8199
|
+
rootDir,
|
|
8200
|
+
(0, import_node_path10.join)(rootDir, "src"),
|
|
8201
|
+
(0, import_node_path10.join)(rootDir, "app"),
|
|
8202
|
+
(0, import_node_path10.join)(rootDir, "lib")
|
|
8203
|
+
];
|
|
8204
|
+
for (const base of searchDirs) {
|
|
8205
|
+
for (const convention of CONVENTION_DIRS) {
|
|
8206
|
+
const dir = (0, import_node_path10.join)(base, convention);
|
|
8207
|
+
if (!(0, import_node_fs9.existsSync)(dir)) continue;
|
|
8208
|
+
try {
|
|
8209
|
+
const stat = (0, import_node_fs9.statSync)(dir);
|
|
8210
|
+
if (!stat.isDirectory()) continue;
|
|
8211
|
+
const entries = (0, import_node_fs9.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
8212
|
+
if (entries.length > 0) {
|
|
8213
|
+
const relPath = dir.replace(rootDir + "/", "").replace(rootDir + "\\", "");
|
|
8214
|
+
result.set(relPath, entries);
|
|
8215
|
+
}
|
|
8216
|
+
} catch {
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
return result;
|
|
8221
|
+
}
|
|
8222
|
+
function extractRouteGroups(id) {
|
|
8223
|
+
const groups = [];
|
|
8224
|
+
const re = /\(([^)]+)\)/g;
|
|
8225
|
+
let m;
|
|
8226
|
+
while ((m = re.exec(id)) !== null) {
|
|
8227
|
+
groups.push(m[1]);
|
|
8228
|
+
}
|
|
8229
|
+
return groups;
|
|
8230
|
+
}
|
|
8231
|
+
var SKIP_SEGMENTS = /* @__PURE__ */ new Set([
|
|
8232
|
+
"src",
|
|
8233
|
+
"app",
|
|
8234
|
+
"client",
|
|
8235
|
+
"server",
|
|
8236
|
+
"lib",
|
|
8237
|
+
"config"
|
|
8238
|
+
]);
|
|
8239
|
+
function isRouteGroup(segment) {
|
|
8240
|
+
return segment.startsWith("(") && segment.endsWith(")");
|
|
8241
|
+
}
|
|
8242
|
+
function isDynamicSegment(segment) {
|
|
8243
|
+
return segment.startsWith("[") || segment.startsWith(":");
|
|
8244
|
+
}
|
|
8245
|
+
function isDomainDir(segment) {
|
|
8246
|
+
return segment.includes(".") && !segment.endsWith(".tsx") && !segment.endsWith(".ts") && !segment.endsWith(".js") && !segment.endsWith(".jsx") && !segment.endsWith(".vue");
|
|
8247
|
+
}
|
|
8248
|
+
var TRIVIAL_GROUPS = /* @__PURE__ */ new Set([
|
|
8249
|
+
// Generic app wrappers
|
|
8250
|
+
"app",
|
|
8251
|
+
"all",
|
|
8252
|
+
"ee",
|
|
8253
|
+
"home",
|
|
8254
|
+
"root",
|
|
8255
|
+
"main",
|
|
8256
|
+
"site",
|
|
8257
|
+
// Auth/access boundary wrappers — protect routes, not feature modules
|
|
8258
|
+
"protected",
|
|
8259
|
+
"authenticated",
|
|
8260
|
+
"authed",
|
|
8261
|
+
"private",
|
|
8262
|
+
"public",
|
|
8263
|
+
"logged-in",
|
|
8264
|
+
"logged-out",
|
|
8265
|
+
"unprotected",
|
|
8266
|
+
"unauthenticated",
|
|
8267
|
+
"auth-required",
|
|
8268
|
+
"no-auth",
|
|
8269
|
+
"guest-only"
|
|
8270
|
+
]);
|
|
8271
|
+
function isTrivialGroup(name, extraTrivial) {
|
|
8272
|
+
if (TRIVIAL_GROUPS.has(name)) return true;
|
|
8273
|
+
if (extraTrivial?.has(name)) return true;
|
|
8274
|
+
const lower = name.toLowerCase();
|
|
8275
|
+
const wrapperPatterns = [
|
|
8276
|
+
/^.*-?wrapper$/,
|
|
8277
|
+
// "page-wrapper", "use-page-wrapper"
|
|
8278
|
+
/^.*-?layout$/,
|
|
8279
|
+
// "admin-layout", "settings-layout"
|
|
8280
|
+
/^use-/,
|
|
8281
|
+
// "use-page-wrapper"
|
|
8282
|
+
/^default$/
|
|
8283
|
+
];
|
|
8284
|
+
return wrapperPatterns.some((p) => p.test(lower));
|
|
8285
|
+
}
|
|
8286
|
+
function normalizeGroupName(name) {
|
|
8287
|
+
return name.replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
8288
|
+
}
|
|
8289
|
+
function extractModuleFromPath(id, extraTrivial) {
|
|
8290
|
+
const segments = id.split("/");
|
|
8291
|
+
const routeGroups = extractRouteGroups(id);
|
|
8292
|
+
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g, extraTrivial)).map(normalizeGroupName);
|
|
8293
|
+
if (moduleGroups.length > 0) {
|
|
8294
|
+
return moduleGroups[moduleGroups.length - 1];
|
|
8295
|
+
}
|
|
8296
|
+
const meaningful = [];
|
|
8297
|
+
for (const seg of segments) {
|
|
8298
|
+
if (seg.includes(".")) continue;
|
|
8299
|
+
if (isRouteGroup(seg)) continue;
|
|
8300
|
+
if (isDynamicSegment(seg)) continue;
|
|
8301
|
+
if (isDomainDir(seg)) continue;
|
|
8302
|
+
if (SKIP_SEGMENTS.has(seg)) continue;
|
|
8303
|
+
meaningful.push(seg);
|
|
8304
|
+
}
|
|
8305
|
+
if (meaningful.length > 0) {
|
|
8306
|
+
return meaningful[0];
|
|
8307
|
+
}
|
|
8308
|
+
return "root";
|
|
8309
|
+
}
|
|
8310
|
+
var cachedRootDir = null;
|
|
8311
|
+
var cachedConventionDirs = /* @__PURE__ */ new Map();
|
|
8312
|
+
var moduleTagger = {
|
|
8313
|
+
id: "module",
|
|
8314
|
+
tagKey: "module",
|
|
8315
|
+
trackUntagged: true,
|
|
8316
|
+
layers: null,
|
|
8317
|
+
// applies to all layers
|
|
8318
|
+
tag(nodes, layer, rootDir) {
|
|
8319
|
+
if (cachedRootDir !== rootDir) {
|
|
8320
|
+
cachedConventionDirs = detectConventionDirs(rootDir);
|
|
8321
|
+
cachedRootDir = rootDir;
|
|
8322
|
+
}
|
|
8323
|
+
let configRules = [];
|
|
8324
|
+
let extraTrivial;
|
|
8325
|
+
try {
|
|
8326
|
+
const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
|
|
8327
|
+
const config = loadConfig2(rootDir);
|
|
8328
|
+
configRules = config.taggers?.module?.rules ?? [];
|
|
8329
|
+
const trivialFromConfig = config.taggers?.module?.trivialGroups;
|
|
8330
|
+
if (trivialFromConfig?.length) {
|
|
8331
|
+
extraTrivial = new Set(trivialFromConfig);
|
|
8332
|
+
}
|
|
8333
|
+
} catch {
|
|
8334
|
+
}
|
|
8335
|
+
const result = /* @__PURE__ */ new Map();
|
|
8336
|
+
for (const node of nodes) {
|
|
8337
|
+
const id = node.id;
|
|
8338
|
+
let matched = false;
|
|
8339
|
+
for (const rule of configRules) {
|
|
8340
|
+
if (matchGlob(rule.match, id)) {
|
|
8341
|
+
result.set(id, rule.module);
|
|
8342
|
+
matched = true;
|
|
8343
|
+
break;
|
|
8344
|
+
}
|
|
8345
|
+
}
|
|
8346
|
+
if (matched) continue;
|
|
8347
|
+
matched = false;
|
|
8348
|
+
for (const [convDir, moduleNames] of cachedConventionDirs) {
|
|
8349
|
+
if (id.startsWith(convDir + "/")) {
|
|
8350
|
+
const rest = id.slice(convDir.length + 1);
|
|
8351
|
+
const firstSeg = rest.split("/")[0];
|
|
8352
|
+
if (moduleNames.includes(firstSeg)) {
|
|
8353
|
+
result.set(id, firstSeg);
|
|
8354
|
+
matched = true;
|
|
8355
|
+
break;
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8358
|
+
}
|
|
8359
|
+
if (matched) continue;
|
|
8360
|
+
const module2 = extractModuleFromPath(id, extraTrivial);
|
|
8361
|
+
result.set(id, module2);
|
|
8362
|
+
}
|
|
8363
|
+
return result;
|
|
8364
|
+
}
|
|
8365
|
+
};
|
|
8366
|
+
|
|
8367
|
+
// src/server/graph/taggers/screen-tagger.ts
|
|
8368
|
+
var SCREEN_TYPES = /* @__PURE__ */ new Set(["page", "layout"]);
|
|
8369
|
+
var screenTagger = {
|
|
8370
|
+
id: "screen",
|
|
8371
|
+
tagKey: "screen",
|
|
8372
|
+
trackUntagged: true,
|
|
8373
|
+
layers: ["ui"],
|
|
8374
|
+
tag(nodes, layer) {
|
|
8375
|
+
if (layer !== "ui") return /* @__PURE__ */ new Map();
|
|
8376
|
+
const result = /* @__PURE__ */ new Map();
|
|
8377
|
+
for (const node of nodes) {
|
|
8378
|
+
if (SCREEN_TYPES.has(node.type)) {
|
|
8379
|
+
result.set(node.id, "true");
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
8382
|
+
return result;
|
|
8383
|
+
}
|
|
8384
|
+
};
|
|
8385
|
+
|
|
8386
|
+
// src/server/graph/core/tagger-registry.ts
|
|
8387
|
+
var TaggerRegistry = class {
|
|
8388
|
+
constructor() {
|
|
8389
|
+
this.taggers = [];
|
|
8390
|
+
this.ids = /* @__PURE__ */ new Set();
|
|
8391
|
+
}
|
|
8392
|
+
register(tagger) {
|
|
8393
|
+
if (this.ids.has(tagger.id)) {
|
|
8394
|
+
throw new Error(`Duplicate tagger id: ${tagger.id}`);
|
|
8395
|
+
}
|
|
8396
|
+
this.ids.add(tagger.id);
|
|
8397
|
+
this.taggers.push(tagger);
|
|
8398
|
+
}
|
|
8399
|
+
getAll() {
|
|
8400
|
+
return this.taggers;
|
|
8401
|
+
}
|
|
8402
|
+
getForLayer(layer) {
|
|
8403
|
+
return this.taggers.filter((t) => t.layers === null || t.layers.includes(layer));
|
|
8404
|
+
}
|
|
8405
|
+
};
|
|
8406
|
+
var BUILTIN_TAGGERS = [moduleTagger, screenTagger];
|
|
8407
|
+
function registerBuiltins2(registry, disabled, config) {
|
|
8408
|
+
for (const tagger of BUILTIN_TAGGERS) {
|
|
8409
|
+
if (disabled.has(tagger.id)) continue;
|
|
8410
|
+
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
8411
|
+
if (override !== void 0) {
|
|
8412
|
+
tagger.trackUntagged = override;
|
|
8413
|
+
}
|
|
8414
|
+
registry.register(tagger);
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
function loadCustomTaggers(registry, config, rootDir, disabled) {
|
|
8418
|
+
for (const entry of config.taggers?.custom ?? []) {
|
|
8419
|
+
if (disabled.has(entry.id)) continue;
|
|
8420
|
+
try {
|
|
8421
|
+
const absPath = (0, import_node_path11.resolve)(rootDir, entry.path);
|
|
8422
|
+
const mod = require(absPath);
|
|
8423
|
+
const tagger = "default" in mod ? mod.default : mod;
|
|
8424
|
+
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
8425
|
+
if (override !== void 0) {
|
|
8426
|
+
tagger.trackUntagged = override;
|
|
8427
|
+
}
|
|
8428
|
+
registry.register(tagger);
|
|
8429
|
+
} catch (err2) {
|
|
8430
|
+
process.stderr.write(`[launch-chart] failed to load custom tagger from ${entry.path}: ${err2}
|
|
8431
|
+
`);
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
8434
|
+
}
|
|
8435
|
+
function createTaggerRegistry(config, rootDir) {
|
|
8436
|
+
const registry = new TaggerRegistry();
|
|
8437
|
+
const disabled = new Set(config.taggers?.disabled ?? []);
|
|
8438
|
+
registerBuiltins2(registry, disabled, config);
|
|
8439
|
+
loadCustomTaggers(registry, config, rootDir, disabled);
|
|
8440
|
+
return registry;
|
|
8441
|
+
}
|
|
8442
|
+
|
|
8443
|
+
// src/server/graph/core/tag-store.ts
|
|
8444
|
+
var import_node_fs10 = require("node:fs");
|
|
8445
|
+
var import_node_path12 = require("node:path");
|
|
8446
|
+
var TAGS_FILENAME = "tags.json";
|
|
8180
8447
|
var GRAPHS_DIR = ".launchsecure/graphs";
|
|
8448
|
+
var tagCache = /* @__PURE__ */ new Map();
|
|
8449
|
+
function tagsFilePath(rootDir) {
|
|
8450
|
+
return (0, import_node_path12.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
|
|
8451
|
+
}
|
|
8452
|
+
function readTagStore(rootDir) {
|
|
8453
|
+
const filePath = tagsFilePath(rootDir);
|
|
8454
|
+
if (!(0, import_node_fs10.existsSync)(filePath)) return {};
|
|
8455
|
+
const stat = (0, import_node_fs10.statSync)(filePath);
|
|
8456
|
+
const cached = tagCache.get(filePath);
|
|
8457
|
+
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
8458
|
+
return cached.store;
|
|
8459
|
+
}
|
|
8460
|
+
try {
|
|
8461
|
+
const content = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
|
|
8462
|
+
const store = JSON.parse(content);
|
|
8463
|
+
tagCache.set(filePath, { mtimeMs: stat.mtimeMs, store });
|
|
8464
|
+
return store;
|
|
8465
|
+
} catch {
|
|
8466
|
+
return {};
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
function writeTagStore(rootDir, store) {
|
|
8470
|
+
const filePath = tagsFilePath(rootDir);
|
|
8471
|
+
const dir = (0, import_node_path12.dirname)(filePath);
|
|
8472
|
+
(0, import_node_fs10.mkdirSync)(dir, { recursive: true });
|
|
8473
|
+
const cleaned = {};
|
|
8474
|
+
for (const [nodeId, tags] of Object.entries(store)) {
|
|
8475
|
+
if (Object.keys(tags).length > 0) {
|
|
8476
|
+
cleaned[nodeId] = tags;
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
(0, import_node_fs10.writeFileSync)(filePath, JSON.stringify(cleaned, null, 2) + "\n", "utf-8");
|
|
8480
|
+
tagCache.delete(filePath);
|
|
8481
|
+
}
|
|
8482
|
+
function setTag(rootDir, nodeId, key, value) {
|
|
8483
|
+
const store = readTagStore(rootDir);
|
|
8484
|
+
if (!store[nodeId]) store[nodeId] = {};
|
|
8485
|
+
store[nodeId][key] = value;
|
|
8486
|
+
writeTagStore(rootDir, store);
|
|
8487
|
+
}
|
|
8488
|
+
function removeTag(rootDir, nodeId, key) {
|
|
8489
|
+
const store = readTagStore(rootDir);
|
|
8490
|
+
if (!store[nodeId]) return;
|
|
8491
|
+
delete store[nodeId][key];
|
|
8492
|
+
if (Object.keys(store[nodeId]).length === 0) {
|
|
8493
|
+
delete store[nodeId];
|
|
8494
|
+
}
|
|
8495
|
+
writeTagStore(rootDir, store);
|
|
8496
|
+
}
|
|
8497
|
+
|
|
8498
|
+
// src/server/graph/index.ts
|
|
8499
|
+
var GRAPHS_DIR2 = ".launchsecure/graphs";
|
|
8181
8500
|
var LAYERS = ["ui", "api", "db"];
|
|
8182
8501
|
var graphCache = /* @__PURE__ */ new Map();
|
|
8502
|
+
var taggedCache = /* @__PURE__ */ new Map();
|
|
8183
8503
|
function graphsDir(rootDir) {
|
|
8184
|
-
return (0,
|
|
8504
|
+
return (0, import_node_path13.join)(rootDir, GRAPHS_DIR2);
|
|
8185
8505
|
}
|
|
8186
8506
|
function graphFilePath(rootDir, layer) {
|
|
8187
|
-
return (0,
|
|
8507
|
+
return (0, import_node_path13.join)(graphsDir(rootDir), `${layer}.json`);
|
|
8508
|
+
}
|
|
8509
|
+
function tagsFilePath2(rootDir) {
|
|
8510
|
+
return (0, import_node_path13.join)(graphsDir(rootDir), "tags.json");
|
|
8511
|
+
}
|
|
8512
|
+
function getMtimeMs(filePath) {
|
|
8513
|
+
if (!(0, import_node_fs11.existsSync)(filePath)) return 0;
|
|
8514
|
+
return (0, import_node_fs11.statSync)(filePath).mtimeMs;
|
|
8188
8515
|
}
|
|
8189
8516
|
function invalidateCache(filePath) {
|
|
8190
8517
|
graphCache.delete(filePath);
|
|
8191
8518
|
}
|
|
8192
|
-
function
|
|
8519
|
+
function invalidateTaggedCache(rootDir, layer) {
|
|
8520
|
+
taggedCache.delete(`${rootDir}:${layer}`);
|
|
8521
|
+
}
|
|
8522
|
+
function applyTags(graph, layer, rootDir) {
|
|
8523
|
+
const config = loadConfig(rootDir);
|
|
8524
|
+
const registry = createTaggerRegistry(config, rootDir);
|
|
8525
|
+
const manualTags = readTagStore(rootDir);
|
|
8526
|
+
const taggedNodes = graph.nodes.map((n) => ({ ...n }));
|
|
8527
|
+
const taggers = registry.getForLayer(layer);
|
|
8528
|
+
for (const tagger of taggers) {
|
|
8529
|
+
const assignments = tagger.tag(taggedNodes, layer, rootDir);
|
|
8530
|
+
for (const node of taggedNodes) {
|
|
8531
|
+
if (!node.tags) node.tags = {};
|
|
8532
|
+
const tags = node.tags;
|
|
8533
|
+
const value = assignments.get(node.id);
|
|
8534
|
+
if (value !== void 0) {
|
|
8535
|
+
tags[tagger.tagKey] = value;
|
|
8536
|
+
} else if (tagger.trackUntagged) {
|
|
8537
|
+
tags[tagger.tagKey] = "untagged";
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
for (const node of taggedNodes) {
|
|
8542
|
+
const manual = manualTags[node.id];
|
|
8543
|
+
if (manual) {
|
|
8544
|
+
if (!node.tags) node.tags = {};
|
|
8545
|
+
const tags = node.tags;
|
|
8546
|
+
Object.assign(tags, manual);
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
return { ...graph, nodes: taggedNodes };
|
|
8550
|
+
}
|
|
8551
|
+
function readGraphRaw(rootDir, layer) {
|
|
8193
8552
|
const filePath = graphFilePath(rootDir, layer);
|
|
8194
|
-
if (!(0,
|
|
8195
|
-
const stat = (0,
|
|
8553
|
+
if (!(0, import_node_fs11.existsSync)(filePath)) return null;
|
|
8554
|
+
const stat = (0, import_node_fs11.statSync)(filePath);
|
|
8196
8555
|
const cached = graphCache.get(filePath);
|
|
8197
8556
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
8198
8557
|
return cached.graph;
|
|
8199
8558
|
}
|
|
8200
|
-
const content = (0,
|
|
8559
|
+
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
8201
8560
|
const graph = JSON.parse(content);
|
|
8202
8561
|
graphCache.set(filePath, { mtimeMs: stat.mtimeMs, graph });
|
|
8203
8562
|
return graph;
|
|
8204
8563
|
}
|
|
8564
|
+
function readGraph(rootDir, layer) {
|
|
8565
|
+
const rawFilePath = graphFilePath(rootDir, layer);
|
|
8566
|
+
if (!(0, import_node_fs11.existsSync)(rawFilePath)) return null;
|
|
8567
|
+
const rawMtime = getMtimeMs(rawFilePath);
|
|
8568
|
+
const tagsMtime = getMtimeMs(tagsFilePath2(rootDir));
|
|
8569
|
+
const cacheKey = `${rootDir}:${layer}`;
|
|
8570
|
+
const cached = taggedCache.get(cacheKey);
|
|
8571
|
+
if (cached && cached.rawMtimeMs === rawMtime && cached.tagsMtimeMs === tagsMtime) {
|
|
8572
|
+
return cached.graph;
|
|
8573
|
+
}
|
|
8574
|
+
const raw = readGraphRaw(rootDir, layer);
|
|
8575
|
+
if (!raw) return null;
|
|
8576
|
+
const tagged = applyTags(raw, layer, rootDir);
|
|
8577
|
+
taggedCache.set(cacheKey, { rawMtimeMs: rawMtime, tagsMtimeMs: tagsMtime, graph: tagged });
|
|
8578
|
+
return tagged;
|
|
8579
|
+
}
|
|
8205
8580
|
function readAllGraphs(rootDir) {
|
|
8206
8581
|
const result = {};
|
|
8207
8582
|
for (const layer of LAYERS) {
|
|
@@ -8212,12 +8587,13 @@ function readAllGraphs(rootDir) {
|
|
|
8212
8587
|
}
|
|
8213
8588
|
function generateGraph(rootDir, layer) {
|
|
8214
8589
|
const dir = graphsDir(rootDir);
|
|
8215
|
-
(0,
|
|
8590
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
8216
8591
|
const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
|
|
8217
8592
|
for (const result of results) {
|
|
8218
8593
|
const filePath = graphFilePath(rootDir, result.layer);
|
|
8219
|
-
(0,
|
|
8594
|
+
(0, import_node_fs11.writeFileSync)(filePath, JSON.stringify(result.output, null, 2) + "\n", "utf-8");
|
|
8220
8595
|
invalidateCache(filePath);
|
|
8596
|
+
invalidateTaggedCache(rootDir, result.layer);
|
|
8221
8597
|
}
|
|
8222
8598
|
return results;
|
|
8223
8599
|
}
|
|
@@ -8278,27 +8654,46 @@ function handleGraphCommand(subcommand, args) {
|
|
|
8278
8654
|
}
|
|
8279
8655
|
|
|
8280
8656
|
// src/server/graph-mcp.ts
|
|
8281
|
-
var
|
|
8282
|
-
var
|
|
8657
|
+
var import_node_fs13 = require("node:fs");
|
|
8658
|
+
var import_node_path15 = require("node:path");
|
|
8283
8659
|
var import_node_child_process2 = require("node:child_process");
|
|
8284
8660
|
var import_node_os2 = require("node:os");
|
|
8285
8661
|
|
|
8286
8662
|
// src/server/lockfile.ts
|
|
8287
8663
|
var import_node_child_process = require("node:child_process");
|
|
8288
|
-
var
|
|
8664
|
+
var import_node_fs12 = require("node:fs");
|
|
8289
8665
|
var import_node_os = require("node:os");
|
|
8290
|
-
var
|
|
8291
|
-
function lockDir() {
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
return (0,
|
|
8296
|
-
}
|
|
8297
|
-
function
|
|
8298
|
-
|
|
8299
|
-
|
|
8666
|
+
var import_node_path14 = require("node:path");
|
|
8667
|
+
function lockDir(projectRoot) {
|
|
8668
|
+
if (projectRoot) {
|
|
8669
|
+
return (0, import_node_path14.join)(projectRoot, ".launchsecure");
|
|
8670
|
+
}
|
|
8671
|
+
return (0, import_node_path14.join)((0, import_node_os.homedir)(), ".launchsecure");
|
|
8672
|
+
}
|
|
8673
|
+
function lockPath(projectRoot) {
|
|
8674
|
+
return (0, import_node_path14.join)(lockDir(projectRoot), "launch-chart.lock");
|
|
8675
|
+
}
|
|
8676
|
+
var _activeProjectRoot;
|
|
8677
|
+
function readLock(projectRoot) {
|
|
8678
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
8679
|
+
const p = lockPath(root);
|
|
8680
|
+
if (!(0, import_node_fs12.existsSync)(p)) {
|
|
8681
|
+
if (root) {
|
|
8682
|
+
const globalP = lockPath();
|
|
8683
|
+
if ((0, import_node_fs12.existsSync)(globalP)) {
|
|
8684
|
+
try {
|
|
8685
|
+
const data = JSON.parse((0, import_node_fs12.readFileSync)(globalP, "utf-8"));
|
|
8686
|
+
if (typeof data.pid === "number" && typeof data.port === "number" && data.cwd === root) {
|
|
8687
|
+
return data;
|
|
8688
|
+
}
|
|
8689
|
+
} catch {
|
|
8690
|
+
}
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
return null;
|
|
8694
|
+
}
|
|
8300
8695
|
try {
|
|
8301
|
-
const data = JSON.parse((0,
|
|
8696
|
+
const data = JSON.parse((0, import_node_fs12.readFileSync)(p, "utf-8"));
|
|
8302
8697
|
if (typeof data.pid !== "number" || typeof data.port !== "number") return null;
|
|
8303
8698
|
return data;
|
|
8304
8699
|
} catch {
|
|
@@ -8327,28 +8722,31 @@ function getListenerPid(port) {
|
|
|
8327
8722
|
return null;
|
|
8328
8723
|
}
|
|
8329
8724
|
}
|
|
8330
|
-
function getLiveLock() {
|
|
8331
|
-
const
|
|
8725
|
+
function getLiveLock(projectRoot) {
|
|
8726
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
8727
|
+
const lock = readLock(root);
|
|
8332
8728
|
if (!lock) return null;
|
|
8333
8729
|
const listenerPid = getListenerPid(lock.port);
|
|
8334
8730
|
const live = listenerPid !== null ? listenerPid === lock.pid : isPidAlive(lock.pid);
|
|
8335
8731
|
if (!live) {
|
|
8336
8732
|
try {
|
|
8337
|
-
(0,
|
|
8733
|
+
(0, import_node_fs12.unlinkSync)(lockPath(root));
|
|
8338
8734
|
} catch {
|
|
8339
8735
|
}
|
|
8340
8736
|
return null;
|
|
8341
8737
|
}
|
|
8342
8738
|
return lock;
|
|
8343
8739
|
}
|
|
8344
|
-
function clearLock() {
|
|
8740
|
+
function clearLock(projectRoot) {
|
|
8741
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
8345
8742
|
try {
|
|
8346
|
-
(0,
|
|
8743
|
+
(0, import_node_fs12.unlinkSync)(lockPath(root));
|
|
8347
8744
|
} catch {
|
|
8348
8745
|
}
|
|
8349
8746
|
}
|
|
8350
8747
|
|
|
8351
8748
|
// src/server/graph-mcp.ts
|
|
8749
|
+
init_config();
|
|
8352
8750
|
var SERVER_INFO = {
|
|
8353
8751
|
name: "launchsecure-graph",
|
|
8354
8752
|
version: "0.0.1"
|
|
@@ -8370,7 +8768,7 @@ var TOOLS = [
|
|
|
8370
8768
|
},
|
|
8371
8769
|
{
|
|
8372
8770
|
name: "read_graph",
|
|
8373
|
-
description: 'Query the structural project graph \u2014 a smart Glob replacement that locates files by type/module/name and returns structural metadata (imports, renders, routes, relations). \n\nUSE THIS FOR: "where is X", "what files are in module Y", "what pages exist under /admin", "what components does Z render", "what tables relate to User", "list all hooks in auth module". \n\nDO NOT USE FOR: finding text/code content (use Grep), reading actual source code (use Read), understanding behavior/logic/patterns (graph has no code semantics \u2014 only names and edges). \n\nQUERY PARAMS (at least one required for node data \u2014 unfiltered calls return summary only to stay in context):\n- search: substring match on node id, name, or route\n- type: filter by node type (ui layer: page, layout, component, ui, hook, context, config, util; api layer: endpoint; db layer: table, enum)\n- module: filter by module (
|
|
8771
|
+
description: 'Query the structural project graph \u2014 a smart Glob replacement that locates files by type/module/name and returns structural metadata (imports, renders, routes, relations). \n\nUSE THIS FOR: "where is X", "what files are in module Y", "what pages exist under /admin", "what components does Z render", "what tables relate to User", "list all hooks in auth module". \n\nDO NOT USE FOR: finding text/code content (use Grep), reading actual source code (use Read), understanding behavior/logic/patterns (graph has no code semantics \u2014 only names and edges). \n\nQUERY PARAMS (at least one required for node data \u2014 unfiltered calls return summary only to stay in context):\n- search: substring match on node id, name, or route\n- type: filter by node type (ui layer: page, layout, component, ui, hook, context, config, util; api layer: endpoint; db layer: table, enum)\n- module: filter by module tag (computed from directory structure, e.g. "auth", "admin", "settings")\n- node_id: return this node + its neighborhood (incoming+outgoing edges within `hops`)\n- hops: neighborhood radius when node_id is set (default 1)\n- minimal: return only id/type/name/module/route per node (skip heavy fields like columns, exports)\n- include_edges: return the actual edge list. Default: TRUE for neighborhood queries (node_id), FALSE for filter queries (search/type/module). Filter responses always include `edge_count`; only pass include_edges:true when you actually need to inspect individual edges (e.g. "which components render X"). This default cuts typical filter responses in half.\n\nBATCH MODE: pass `queries` (array of query objects) to run multiple independent queries in a single call. Each query object uses the same params (layer/search/type/module/node_id/hops/minimal). Returns { batch: true, count, results: [{index, query, result}, ...] }. Use this when you need multiple graph views up-front (e.g. scoping a feature across ui+api+db layers) to save round-trips. When batch mode is used, top-level params are ignored.\n\nReturns: filtered nodes + edges between them. If no filter given, returns per-layer counts and type breakdown only.\n\nWIRE FORMAT (compact): responses that include nodes/edges use short keys and edge-by-index refs to cut payload ~40-60%. Every such response carries a `_schema` legend. Quick reference:\n nodes[]: { i: id, t: type, n: name, m: module, r: route, mt: methods, x: exports, c: columns }\n edges[]: { s: source_node_index, d: target_node_index, t: type, l: label }\nedges.s / edges.d are 0-based indices into THIS response\'s nodes array. If a referenced node is not in the response (boundary case), s/d may instead contain the full node id string \u2014 always check the type.\n\nBUDGET GUARDS:\n- Neighborhood queries stop expanding when the projected response exceeds budget. The response then contains `budget_exceeded: true` plus `hops_traversed < hops_requested`. When this happens, drill into a specific neighbor with another node_id call rather than retrying with larger hops \u2014 it will just truncate again.\n- Batch mode caps total response size. Once the budget is hit, later queries return `{skipped: true, reason: "batch_budget_exhausted"}` and you must re-run them individually.',
|
|
8374
8772
|
inputSchema: {
|
|
8375
8773
|
type: "object",
|
|
8376
8774
|
properties: {
|
|
@@ -8389,7 +8787,15 @@ var TOOLS = [
|
|
|
8389
8787
|
},
|
|
8390
8788
|
module: {
|
|
8391
8789
|
type: "string",
|
|
8392
|
-
description: '
|
|
8790
|
+
description: 'Filter by module tag (e.g. "auth", "admin", "settings"). Works across all layers.'
|
|
8791
|
+
},
|
|
8792
|
+
tag_key: {
|
|
8793
|
+
type: "string",
|
|
8794
|
+
description: "Filter by arbitrary tag key. Must be used with tag_value."
|
|
8795
|
+
},
|
|
8796
|
+
tag_value: {
|
|
8797
|
+
type: "string",
|
|
8798
|
+
description: "Filter by tag value for the given tag_key."
|
|
8393
8799
|
},
|
|
8394
8800
|
node_id: {
|
|
8395
8801
|
type: "string",
|
|
@@ -8513,6 +8919,46 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
8513
8919
|
type: "object",
|
|
8514
8920
|
properties: {}
|
|
8515
8921
|
}
|
|
8922
|
+
},
|
|
8923
|
+
{
|
|
8924
|
+
name: "add_tag",
|
|
8925
|
+
description: 'Tag a graph node with a key-value pair. Tags persist in .launchsecure/graphs/tags.json and survive graph regeneration. Use for annotating nodes with arbitrary metadata (e.g. "refactor_later", "owner", "priority"). Manual tags override computed tags (like module and screen) for the same key.',
|
|
8926
|
+
inputSchema: {
|
|
8927
|
+
type: "object",
|
|
8928
|
+
properties: {
|
|
8929
|
+
node_id: {
|
|
8930
|
+
type: "string",
|
|
8931
|
+
description: 'The node id to tag (e.g. "app/(auth)/login/page.tsx").'
|
|
8932
|
+
},
|
|
8933
|
+
key: {
|
|
8934
|
+
type: "string",
|
|
8935
|
+
description: 'Tag key (e.g. "module", "owner", "refactor_later").'
|
|
8936
|
+
},
|
|
8937
|
+
value: {
|
|
8938
|
+
type: "string",
|
|
8939
|
+
description: 'Tag value (e.g. "auth", "alice", "true").'
|
|
8940
|
+
}
|
|
8941
|
+
},
|
|
8942
|
+
required: ["node_id", "key", "value"]
|
|
8943
|
+
}
|
|
8944
|
+
},
|
|
8945
|
+
{
|
|
8946
|
+
name: "remove_tag",
|
|
8947
|
+
description: "Remove a manual tag from a graph node. Only removes tags from tags.json \u2014 computed tags (module, screen) cannot be removed (they are re-derived at read time).",
|
|
8948
|
+
inputSchema: {
|
|
8949
|
+
type: "object",
|
|
8950
|
+
properties: {
|
|
8951
|
+
node_id: {
|
|
8952
|
+
type: "string",
|
|
8953
|
+
description: "The node id to remove the tag from."
|
|
8954
|
+
},
|
|
8955
|
+
key: {
|
|
8956
|
+
type: "string",
|
|
8957
|
+
description: "Tag key to remove."
|
|
8958
|
+
}
|
|
8959
|
+
},
|
|
8960
|
+
required: ["node_id", "key"]
|
|
8961
|
+
}
|
|
8516
8962
|
}
|
|
8517
8963
|
];
|
|
8518
8964
|
function matchesSearch(node, query) {
|
|
@@ -8526,7 +8972,7 @@ function matchesSearch(node, query) {
|
|
|
8526
8972
|
function toMinimal(nodes) {
|
|
8527
8973
|
return nodes.map((n) => {
|
|
8528
8974
|
const out = { id: n.id, type: n.type, name: n.name };
|
|
8529
|
-
if (n.
|
|
8975
|
+
if (n.tags != null) out.tags = n.tags;
|
|
8530
8976
|
if (n.route != null) out.route = n.route;
|
|
8531
8977
|
if (n.methods != null) out.methods = n.methods;
|
|
8532
8978
|
return out;
|
|
@@ -8537,11 +8983,12 @@ var COMPACT_SCHEMA = {
|
|
|
8537
8983
|
i: "id",
|
|
8538
8984
|
t: "type",
|
|
8539
8985
|
n: "name",
|
|
8540
|
-
m: "module",
|
|
8986
|
+
m: "module (from tags)",
|
|
8541
8987
|
r: "route",
|
|
8542
8988
|
mt: "methods",
|
|
8543
8989
|
x: "exports",
|
|
8544
|
-
c: "columns"
|
|
8990
|
+
c: "columns",
|
|
8991
|
+
tg: "tags"
|
|
8545
8992
|
},
|
|
8546
8993
|
edges: {
|
|
8547
8994
|
s: "source_node_index",
|
|
@@ -8559,7 +9006,8 @@ var COMPACT_NODE_KNOWN_KEYS = /* @__PURE__ */ new Set([
|
|
|
8559
9006
|
"route",
|
|
8560
9007
|
"methods",
|
|
8561
9008
|
"exports",
|
|
8562
|
-
"columns"
|
|
9009
|
+
"columns",
|
|
9010
|
+
"tags"
|
|
8563
9011
|
]);
|
|
8564
9012
|
var EST_CHARS_PER_NODE_FULL = {
|
|
8565
9013
|
ui: 300,
|
|
@@ -8580,11 +9028,13 @@ var NEIGHBORHOOD_BUDGET_CHARS = 55e3;
|
|
|
8580
9028
|
var BATCH_BUDGET_CHARS = 6e4;
|
|
8581
9029
|
function toCompactNode(n) {
|
|
8582
9030
|
const out = { i: n.id, t: n.type, n: n.name };
|
|
8583
|
-
|
|
9031
|
+
const tags = n.tags;
|
|
9032
|
+
if (tags?.module) out.m = tags.module;
|
|
8584
9033
|
if (n.route != null) out.r = n.route;
|
|
8585
9034
|
if (n.methods != null) out.mt = n.methods;
|
|
8586
9035
|
if (n.exports != null) out.x = n.exports;
|
|
8587
9036
|
if (n.columns != null) out.c = n.columns;
|
|
9037
|
+
if (tags != null) out.tg = tags;
|
|
8588
9038
|
for (const k of Object.keys(n)) {
|
|
8589
9039
|
if (!COMPACT_NODE_KNOWN_KEYS.has(k) && n[k] != null) out[k] = n[k];
|
|
8590
9040
|
}
|
|
@@ -8660,7 +9110,8 @@ function layerSummary(graph) {
|
|
|
8660
9110
|
const moduleCounts = {};
|
|
8661
9111
|
for (const n of graph.nodes) {
|
|
8662
9112
|
typeCounts[n.type] = (typeCounts[n.type] ?? 0) + 1;
|
|
8663
|
-
const
|
|
9113
|
+
const tags = n.tags;
|
|
9114
|
+
const mod = tags?.module;
|
|
8664
9115
|
if (mod) moduleCounts[mod] = (moduleCounts[mod] ?? 0) + 1;
|
|
8665
9116
|
}
|
|
8666
9117
|
const edgeTypeCounts = {};
|
|
@@ -8717,12 +9168,14 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8717
9168
|
const search = args.search;
|
|
8718
9169
|
const type = args.type;
|
|
8719
9170
|
const module_ = args.module;
|
|
9171
|
+
const tagKey = args.tag_key;
|
|
9172
|
+
const tagValue = args.tag_value;
|
|
8720
9173
|
const nodeId = args.node_id;
|
|
8721
9174
|
const hops = args.hops ?? 1;
|
|
8722
9175
|
const layerIsDb = args.layer === "db";
|
|
8723
9176
|
const minimal = args.minimal ?? layerIsDb;
|
|
8724
9177
|
const includeEdges = args.include_edges;
|
|
8725
|
-
const hasFilter = !!(search || type || module_ || nodeId);
|
|
9178
|
+
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
8726
9179
|
if (layer && !["ui", "api", "db"].includes(layer)) {
|
|
8727
9180
|
return { error: `Invalid layer "${layer}". Must be one of: ui, api, db` };
|
|
8728
9181
|
}
|
|
@@ -8778,7 +9231,9 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
8778
9231
|
const matched = graph.nodes.filter((n) => {
|
|
8779
9232
|
if (search && !matchesSearch(n, search)) return false;
|
|
8780
9233
|
if (type && n.type !== type) return false;
|
|
8781
|
-
|
|
9234
|
+
const nodeTags = n.tags;
|
|
9235
|
+
if (module_ && nodeTags?.module !== module_) return false;
|
|
9236
|
+
if (tagKey && tagValue && nodeTags?.[tagKey] !== tagValue) return false;
|
|
8782
9237
|
return true;
|
|
8783
9238
|
});
|
|
8784
9239
|
const matchedIds = new Set(matched.map((n) => n.id));
|
|
@@ -8865,9 +9320,9 @@ function handleReadGraph(args) {
|
|
|
8865
9320
|
return okJson(result);
|
|
8866
9321
|
}
|
|
8867
9322
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
8868
|
-
if (layer === "ui") return (0,
|
|
8869
|
-
if (layer === "api") return (0,
|
|
8870
|
-
if (layer === "db") return (0,
|
|
9323
|
+
if (layer === "ui") return (0, import_node_path15.join)(rootDir, "src", nodeId);
|
|
9324
|
+
if (layer === "api") return (0, import_node_path15.join)(rootDir, nodeId);
|
|
9325
|
+
if (layer === "db") return (0, import_node_path15.join)(rootDir, "prisma", "schema.prisma");
|
|
8871
9326
|
return null;
|
|
8872
9327
|
}
|
|
8873
9328
|
function handleGrepNodes(args) {
|
|
@@ -8927,11 +9382,11 @@ function handleGrepNodes(args) {
|
|
|
8927
9382
|
let filesSearched = 0;
|
|
8928
9383
|
let truncated = false;
|
|
8929
9384
|
for (const [filePath, nodeId] of filePaths) {
|
|
8930
|
-
if (!(0,
|
|
9385
|
+
if (!(0, import_node_fs13.existsSync)(filePath)) continue;
|
|
8931
9386
|
filesSearched++;
|
|
8932
9387
|
let content;
|
|
8933
9388
|
try {
|
|
8934
|
-
content = (0,
|
|
9389
|
+
content = (0, import_node_fs13.readFileSync)(filePath, "utf-8");
|
|
8935
9390
|
} catch {
|
|
8936
9391
|
continue;
|
|
8937
9392
|
}
|
|
@@ -8969,7 +9424,8 @@ function handleGrepNodes(args) {
|
|
|
8969
9424
|
});
|
|
8970
9425
|
}
|
|
8971
9426
|
function handleChartServerStatus() {
|
|
8972
|
-
const
|
|
9427
|
+
const rootDir = process.cwd();
|
|
9428
|
+
const lock = getLiveLock(rootDir);
|
|
8973
9429
|
if (!lock) {
|
|
8974
9430
|
return okJson({ running: false });
|
|
8975
9431
|
}
|
|
@@ -8983,7 +9439,8 @@ function handleChartServerStatus() {
|
|
|
8983
9439
|
});
|
|
8984
9440
|
}
|
|
8985
9441
|
function handleStartChartServer(args) {
|
|
8986
|
-
const
|
|
9442
|
+
const rootDir = process.cwd();
|
|
9443
|
+
const lock = getLiveLock(rootDir);
|
|
8987
9444
|
if (lock) {
|
|
8988
9445
|
return okJson({
|
|
8989
9446
|
started: false,
|
|
@@ -8994,11 +9451,11 @@ function handleStartChartServer(args) {
|
|
|
8994
9451
|
});
|
|
8995
9452
|
}
|
|
8996
9453
|
const entryPath = process.argv[1];
|
|
8997
|
-
const logDir = (0,
|
|
8998
|
-
(0,
|
|
8999
|
-
const logPath = (0,
|
|
9000
|
-
const out = (0,
|
|
9001
|
-
const err2 = (0,
|
|
9454
|
+
const logDir = (0, import_node_path15.join)((0, import_node_os2.homedir)(), ".launchsecure");
|
|
9455
|
+
(0, import_node_fs13.mkdirSync)(logDir, { recursive: true });
|
|
9456
|
+
const logPath = (0, import_node_path15.join)(logDir, "launch-chart.log");
|
|
9457
|
+
const out = (0, import_node_fs13.openSync)(logPath, "a");
|
|
9458
|
+
const err2 = (0, import_node_fs13.openSync)(logPath, "a");
|
|
9002
9459
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
9003
9460
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
9004
9461
|
detached: true,
|
|
@@ -9013,7 +9470,8 @@ function handleStartChartServer(args) {
|
|
|
9013
9470
|
});
|
|
9014
9471
|
}
|
|
9015
9472
|
function handleStopChartServer() {
|
|
9016
|
-
const
|
|
9473
|
+
const rootDir = process.cwd();
|
|
9474
|
+
const lock = getLiveLock(rootDir);
|
|
9017
9475
|
if (!lock) {
|
|
9018
9476
|
return okJson({ stopped: false, reason: "not_running" });
|
|
9019
9477
|
}
|
|
@@ -9023,14 +9481,45 @@ function handleStopChartServer() {
|
|
|
9023
9481
|
} catch (e) {
|
|
9024
9482
|
const code = e.code;
|
|
9025
9483
|
if (code === "ESRCH") {
|
|
9026
|
-
clearLock();
|
|
9484
|
+
clearLock(rootDir);
|
|
9027
9485
|
return okJson({ stopped: true, pid: lock.pid, note: "process was already gone, lock cleaned up" });
|
|
9028
9486
|
}
|
|
9029
9487
|
return okJson({ stopped: false, reason: `kill failed: ${code ?? e}` });
|
|
9030
9488
|
}
|
|
9031
9489
|
}
|
|
9490
|
+
function handleAddTag(args) {
|
|
9491
|
+
const rootDir = process.cwd();
|
|
9492
|
+
const nodeId = args.node_id;
|
|
9493
|
+
const key = args.key;
|
|
9494
|
+
const value = args.value;
|
|
9495
|
+
if (!nodeId) return err("node_id is required");
|
|
9496
|
+
if (!key) return err("key is required");
|
|
9497
|
+
if (!value) return err("value is required");
|
|
9498
|
+
const graphs = readAllGraphs(rootDir);
|
|
9499
|
+
let found = false;
|
|
9500
|
+
for (const graph of Object.values(graphs)) {
|
|
9501
|
+
if (graph && graph.nodes.some((n) => n.id === nodeId)) {
|
|
9502
|
+
found = true;
|
|
9503
|
+
break;
|
|
9504
|
+
}
|
|
9505
|
+
}
|
|
9506
|
+
if (!found) {
|
|
9507
|
+
return err(`Node "${nodeId}" not found in any graph layer. Check the node_id.`);
|
|
9508
|
+
}
|
|
9509
|
+
setTag(rootDir, nodeId, key, value);
|
|
9510
|
+
return okJson({ ok: true, node_id: nodeId, tag: { [key]: value } });
|
|
9511
|
+
}
|
|
9512
|
+
function handleRemoveTag(args) {
|
|
9513
|
+
const rootDir = process.cwd();
|
|
9514
|
+
const nodeId = args.node_id;
|
|
9515
|
+
const key = args.key;
|
|
9516
|
+
if (!nodeId) return err("node_id is required");
|
|
9517
|
+
if (!key) return err("key is required");
|
|
9518
|
+
removeTag(rootDir, nodeId, key);
|
|
9519
|
+
return okJson({ ok: true, node_id: nodeId, removed_key: key });
|
|
9520
|
+
}
|
|
9032
9521
|
function handleDetectProjectStack() {
|
|
9033
|
-
const rootDir =
|
|
9522
|
+
const rootDir = process.cwd();
|
|
9034
9523
|
const parsers = [
|
|
9035
9524
|
{ id: "react-nextjs", layer: "ui", detected: reactNextjsParser.detect(rootDir) },
|
|
9036
9525
|
{ id: "nextjs-routes", layer: "api", detected: nextjsRoutesParser.detect(rootDir) },
|
|
@@ -9048,20 +9537,20 @@ function handleDetectProjectStack() {
|
|
|
9048
9537
|
if (f.type === "out_of_pattern") stats.out_of_pattern++;
|
|
9049
9538
|
}
|
|
9050
9539
|
}
|
|
9051
|
-
const srcDir = (0,
|
|
9052
|
-
if ((0,
|
|
9540
|
+
const srcDir = (0, import_node_path15.join)(rootDir, "src");
|
|
9541
|
+
if ((0, import_node_fs13.existsSync)(srcDir)) {
|
|
9053
9542
|
const scanDir = (dir) => {
|
|
9054
|
-
if (!(0,
|
|
9055
|
-
for (const entry of (0,
|
|
9543
|
+
if (!(0, import_node_fs13.existsSync)(dir)) return;
|
|
9544
|
+
for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true })) {
|
|
9056
9545
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
9057
|
-
const full = (0,
|
|
9546
|
+
const full = (0, import_node_path15.join)(dir, entry.name);
|
|
9058
9547
|
if (entry.isDirectory()) {
|
|
9059
9548
|
scanDir(full);
|
|
9060
9549
|
continue;
|
|
9061
9550
|
}
|
|
9062
|
-
if (![".ts", ".tsx"].includes((0,
|
|
9551
|
+
if (![".ts", ".tsx"].includes((0, import_node_path15.extname)(entry.name))) continue;
|
|
9063
9552
|
try {
|
|
9064
|
-
const content = (0,
|
|
9553
|
+
const content = (0, import_node_fs13.readFileSync)(full, "utf-8");
|
|
9065
9554
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
9066
9555
|
if (matches) stats.annotations += matches.length;
|
|
9067
9556
|
} catch {
|
|
@@ -9148,6 +9637,14 @@ function handleMessage(msg) {
|
|
|
9148
9637
|
respond(id ?? null, handleDetectProjectStack());
|
|
9149
9638
|
return;
|
|
9150
9639
|
}
|
|
9640
|
+
if (toolName === "add_tag") {
|
|
9641
|
+
respond(id ?? null, handleAddTag(args));
|
|
9642
|
+
return;
|
|
9643
|
+
}
|
|
9644
|
+
if (toolName === "remove_tag") {
|
|
9645
|
+
respond(id ?? null, handleRemoveTag(args));
|
|
9646
|
+
return;
|
|
9647
|
+
}
|
|
9151
9648
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
9152
9649
|
return;
|
|
9153
9650
|
}
|
|
@@ -9245,7 +9742,7 @@ function parseArgs() {
|
|
|
9245
9742
|
return { port, token, serverUrl: LAUNCHSECURE_URL, subcommand };
|
|
9246
9743
|
}
|
|
9247
9744
|
function tryListen(server, port, maxRetries = 10) {
|
|
9248
|
-
return new Promise((
|
|
9745
|
+
return new Promise((resolve3, reject) => {
|
|
9249
9746
|
let attempts = 0;
|
|
9250
9747
|
function attempt(p) {
|
|
9251
9748
|
server.once("error", (err2) => {
|
|
@@ -9256,7 +9753,7 @@ function tryListen(server, port, maxRetries = 10) {
|
|
|
9256
9753
|
reject(err2);
|
|
9257
9754
|
}
|
|
9258
9755
|
});
|
|
9259
|
-
server.listen(p, () =>
|
|
9756
|
+
server.listen(p, () => resolve3(p));
|
|
9260
9757
|
}
|
|
9261
9758
|
attempt(port);
|
|
9262
9759
|
});
|
|
@@ -9277,7 +9774,7 @@ function saveCredentials(creds) {
|
|
|
9277
9774
|
});
|
|
9278
9775
|
}
|
|
9279
9776
|
function verifyToken(serverUrl, token) {
|
|
9280
|
-
return new Promise((
|
|
9777
|
+
return new Promise((resolve3) => {
|
|
9281
9778
|
const url = new URL("/api/mcp/verify", serverUrl);
|
|
9282
9779
|
const body = JSON.stringify({ token });
|
|
9283
9780
|
const mod = url.protocol === "https:" ? import_https.default : import_http.default;
|
|
@@ -9292,30 +9789,30 @@ function verifyToken(serverUrl, token) {
|
|
|
9292
9789
|
res.on("data", (chunk) => data += chunk);
|
|
9293
9790
|
res.on("end", () => {
|
|
9294
9791
|
try {
|
|
9295
|
-
|
|
9792
|
+
resolve3(JSON.parse(data));
|
|
9296
9793
|
} catch {
|
|
9297
|
-
|
|
9794
|
+
resolve3({ valid: false, error: "Invalid response from server" });
|
|
9298
9795
|
}
|
|
9299
9796
|
});
|
|
9300
9797
|
});
|
|
9301
9798
|
req.on("error", (err2) => {
|
|
9302
|
-
|
|
9799
|
+
resolve3({ valid: false, error: `Cannot reach server: ${err2.message}` });
|
|
9303
9800
|
});
|
|
9304
9801
|
req.setTimeout(1e4, () => {
|
|
9305
9802
|
req.destroy();
|
|
9306
|
-
|
|
9803
|
+
resolve3({ valid: false, error: "Connection timed out" });
|
|
9307
9804
|
});
|
|
9308
9805
|
req.write(body);
|
|
9309
9806
|
req.end();
|
|
9310
9807
|
});
|
|
9311
9808
|
}
|
|
9312
9809
|
function httpRequest(reqUrl, options, body, timeout = 3e4) {
|
|
9313
|
-
return new Promise((
|
|
9810
|
+
return new Promise((resolve3, reject) => {
|
|
9314
9811
|
const mod = reqUrl.protocol === "https:" ? import_https.default : import_http.default;
|
|
9315
9812
|
const r = mod.request(reqUrl, options, (resp) => {
|
|
9316
9813
|
let data = "";
|
|
9317
9814
|
resp.on("data", (chunk) => data += chunk);
|
|
9318
|
-
resp.on("end", () =>
|
|
9815
|
+
resp.on("end", () => resolve3({ status: resp.statusCode || 0, headers: resp.headers, body: data }));
|
|
9319
9816
|
});
|
|
9320
9817
|
r.on("error", reject);
|
|
9321
9818
|
r.setTimeout(timeout, () => {
|