@launchsecure/launch-kit 0.0.5 → 0.0.6
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-BN_N_I08.js +379 -0
- package/dist/chart-client/assets/index-DJRXEWQm.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-CCpAvTkG.css +32 -0
- package/dist/client/assets/{index-DCC--GO-.js → index-DldfczJ1.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/chart-serve.js +589 -99
- package/dist/server/cli.js +599 -124
- package/dist/server/graph-mcp-entry.js +731 -128
- package/package.json +1 -1
- package/dist/chart-client/assets/index-BUih0oqR.js +0 -358
- package/dist/chart-client/assets/index-DFslt72L.css +0 -1
- package/dist/client/assets/index-BCxRNp8I.css +0 -32
|
@@ -29,17 +29,36 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
33
|
|
|
33
34
|
// src/server/lockfile.ts
|
|
34
|
-
function lockDir() {
|
|
35
|
+
function lockDir(projectRoot) {
|
|
36
|
+
if (projectRoot) {
|
|
37
|
+
return (0, import_node_path.join)(projectRoot, ".launchsecure");
|
|
38
|
+
}
|
|
35
39
|
return (0, import_node_path.join)((0, import_node_os.homedir)(), ".launchsecure");
|
|
36
40
|
}
|
|
37
|
-
function lockPath() {
|
|
38
|
-
return (0, import_node_path.join)(lockDir(), "launch-chart.lock");
|
|
41
|
+
function lockPath(projectRoot) {
|
|
42
|
+
return (0, import_node_path.join)(lockDir(projectRoot), "launch-chart.lock");
|
|
39
43
|
}
|
|
40
|
-
function readLock() {
|
|
41
|
-
const
|
|
42
|
-
|
|
44
|
+
function readLock(projectRoot) {
|
|
45
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
46
|
+
const p = lockPath(root);
|
|
47
|
+
if (!(0, import_node_fs.existsSync)(p)) {
|
|
48
|
+
if (root) {
|
|
49
|
+
const globalP = lockPath();
|
|
50
|
+
if ((0, import_node_fs.existsSync)(globalP)) {
|
|
51
|
+
try {
|
|
52
|
+
const data = JSON.parse((0, import_node_fs.readFileSync)(globalP, "utf-8"));
|
|
53
|
+
if (typeof data.pid === "number" && typeof data.port === "number" && data.cwd === root) {
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
43
62
|
try {
|
|
44
63
|
const data = JSON.parse((0, import_node_fs.readFileSync)(p, "utf-8"));
|
|
45
64
|
if (typeof data.pid !== "number" || typeof data.port !== "number") return null;
|
|
@@ -70,31 +89,35 @@ function getListenerPid(port) {
|
|
|
70
89
|
return null;
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
|
-
function getLiveLock() {
|
|
74
|
-
const
|
|
92
|
+
function getLiveLock(projectRoot) {
|
|
93
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
94
|
+
const lock = readLock(root);
|
|
75
95
|
if (!lock) return null;
|
|
76
96
|
const listenerPid = getListenerPid(lock.port);
|
|
77
97
|
const live = listenerPid !== null ? listenerPid === lock.pid : isPidAlive(lock.pid);
|
|
78
98
|
if (!live) {
|
|
79
99
|
try {
|
|
80
|
-
(0, import_node_fs.unlinkSync)(lockPath());
|
|
100
|
+
(0, import_node_fs.unlinkSync)(lockPath(root));
|
|
81
101
|
} catch {
|
|
82
102
|
}
|
|
83
103
|
return null;
|
|
84
104
|
}
|
|
85
105
|
return lock;
|
|
86
106
|
}
|
|
87
|
-
function writeLock(data) {
|
|
88
|
-
|
|
89
|
-
(0, import_node_fs.
|
|
107
|
+
function writeLock(data, projectRoot) {
|
|
108
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
109
|
+
(0, import_node_fs.mkdirSync)(lockDir(root), { recursive: true });
|
|
110
|
+
(0, import_node_fs.writeFileSync)(lockPath(root), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
111
|
+
if (root) _activeProjectRoot = root;
|
|
90
112
|
}
|
|
91
|
-
function clearLock() {
|
|
113
|
+
function clearLock(projectRoot) {
|
|
114
|
+
const root = projectRoot ?? _activeProjectRoot;
|
|
92
115
|
try {
|
|
93
|
-
(0, import_node_fs.unlinkSync)(lockPath());
|
|
116
|
+
(0, import_node_fs.unlinkSync)(lockPath(root));
|
|
94
117
|
} catch {
|
|
95
118
|
}
|
|
96
119
|
}
|
|
97
|
-
var import_node_child_process, import_node_fs, import_node_os, import_node_path;
|
|
120
|
+
var import_node_child_process, import_node_fs, import_node_os, import_node_path, _activeProjectRoot;
|
|
98
121
|
var init_lockfile = __esm({
|
|
99
122
|
"src/server/lockfile.ts"() {
|
|
100
123
|
"use strict";
|
|
@@ -106,6 +129,10 @@ var init_lockfile = __esm({
|
|
|
106
129
|
});
|
|
107
130
|
|
|
108
131
|
// src/server/graph/core/config.ts
|
|
132
|
+
var config_exports = {};
|
|
133
|
+
__export(config_exports, {
|
|
134
|
+
loadConfig: () => loadConfig
|
|
135
|
+
});
|
|
109
136
|
function loadConfig(rootDir) {
|
|
110
137
|
const configPath = (0, import_node_path2.join)(rootDir, CONFIG_FILENAME);
|
|
111
138
|
if (!(0, import_node_fs2.existsSync)(configPath)) return {};
|
|
@@ -586,34 +613,6 @@ function classifyType(id) {
|
|
|
586
613
|
if (id.startsWith("lib/") || id.startsWith("config/")) return "lib";
|
|
587
614
|
return "component";
|
|
588
615
|
}
|
|
589
|
-
function classifyModule(id) {
|
|
590
|
-
if (/app\/\(auth\)\//.test(id)) return "auth";
|
|
591
|
-
if (/app\/\(admin\)\//.test(id)) return "admin";
|
|
592
|
-
if (/app\/\(settings\)\//.test(id)) return "settings";
|
|
593
|
-
if (/app\/\(app\)\/\[orgSlug\]\/\(project-pages\)\//.test(id)) return "project";
|
|
594
|
-
if (/app\/\(app\)\/\[orgSlug\]\/\(org-pages\)\//.test(id)) return "org";
|
|
595
|
-
if (/app\/\(app\)\/\[orgSlug\]\//.test(id)) return "org";
|
|
596
|
-
if (id.startsWith("app/integrations/")) return "integrations";
|
|
597
|
-
if (id.startsWith("app/docs/")) return "admin";
|
|
598
|
-
if (id.startsWith("client/components/ui/")) return "shared-ui";
|
|
599
|
-
if (id.startsWith("client/components/layout/") || /client\/lib\/navigation/.test(id)) return "layout";
|
|
600
|
-
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";
|
|
601
|
-
if (/client\/components\/prd-/.test(id) || /client\/hooks\/use-admin/.test(id)) return "admin";
|
|
602
|
-
if (/client\/components\/org-/.test(id) || /client\/hooks\/use-org-/.test(id) || /client\/hooks\/use-provider-def/.test(id)) return "org";
|
|
603
|
-
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";
|
|
604
|
-
if (/client\/hooks\/use-(profile|sessions|organizations|notification)/.test(id)) return "settings";
|
|
605
|
-
if (id.startsWith("server/auth/")) return "auth";
|
|
606
|
-
if (id.startsWith("server/mcp/")) return "mcp";
|
|
607
|
-
if (id.startsWith("server/lib/")) return "server-lib";
|
|
608
|
-
if (id.startsWith("server/middleware")) return "middleware";
|
|
609
|
-
if (id.startsWith("server/services/")) return "services";
|
|
610
|
-
if (id.startsWith("server/db")) return "db";
|
|
611
|
-
if (id.startsWith("server/errors")) return "errors";
|
|
612
|
-
if (id.startsWith("server/")) return "server-lib";
|
|
613
|
-
if (id.startsWith("config/")) return "config";
|
|
614
|
-
if (id.startsWith("lib/")) return "lib";
|
|
615
|
-
return "root";
|
|
616
|
-
}
|
|
617
616
|
function extractRoute(id) {
|
|
618
617
|
if (!id.endsWith("/page.tsx")) return null;
|
|
619
618
|
let route = id.replace(/^app\//, "/").replace(/\/page\.tsx$/, "");
|
|
@@ -834,8 +833,7 @@ function generate(rootDir) {
|
|
|
834
833
|
const parsed = parsedByPath.get(absPath);
|
|
835
834
|
const name = parsed.name || nameFromFilename(absPath);
|
|
836
835
|
const route = extractRoute(id);
|
|
837
|
-
|
|
838
|
-
nodes.push({ id, type, name, route, module: module_, exports: parsed.exports });
|
|
836
|
+
nodes.push({ id, type, name, route, exports: parsed.exports });
|
|
839
837
|
nodeIdSet.add(id);
|
|
840
838
|
nodeTypeMap.set(id, type);
|
|
841
839
|
if (route) routeToNodeId.set(route, id);
|
|
@@ -939,7 +937,6 @@ function generate(rootDir) {
|
|
|
939
937
|
type: "external",
|
|
940
938
|
name: parsed.name || nameFromFilename(absPath),
|
|
941
939
|
route: null,
|
|
942
|
-
module: "external",
|
|
943
940
|
exports: parsed.exports
|
|
944
941
|
});
|
|
945
942
|
nodeIdSet.add(externalId);
|
|
@@ -2031,29 +2028,421 @@ var init_graph_builder = __esm({
|
|
|
2031
2028
|
}
|
|
2032
2029
|
});
|
|
2033
2030
|
|
|
2031
|
+
// src/server/graph/taggers/module-tagger.ts
|
|
2032
|
+
function matchGlob(pattern, id) {
|
|
2033
|
+
const patParts = pattern.split("/");
|
|
2034
|
+
const idParts = id.split("/");
|
|
2035
|
+
return matchParts(patParts, 0, idParts, 0);
|
|
2036
|
+
}
|
|
2037
|
+
function matchParts(pat, pi, id, ii) {
|
|
2038
|
+
while (pi < pat.length && ii < id.length) {
|
|
2039
|
+
const p = pat[pi];
|
|
2040
|
+
if (p === "**") {
|
|
2041
|
+
for (let skip = ii; skip <= id.length; skip++) {
|
|
2042
|
+
if (matchParts(pat, pi + 1, id, skip)) return true;
|
|
2043
|
+
}
|
|
2044
|
+
return false;
|
|
2045
|
+
}
|
|
2046
|
+
if (p === "*") {
|
|
2047
|
+
pi++;
|
|
2048
|
+
ii++;
|
|
2049
|
+
continue;
|
|
2050
|
+
}
|
|
2051
|
+
if (p !== id[ii]) return false;
|
|
2052
|
+
pi++;
|
|
2053
|
+
ii++;
|
|
2054
|
+
}
|
|
2055
|
+
while (pi < pat.length && pat[pi] === "**") pi++;
|
|
2056
|
+
return pi === pat.length && ii === id.length;
|
|
2057
|
+
}
|
|
2058
|
+
function detectConventionDirs(rootDir) {
|
|
2059
|
+
const result = /* @__PURE__ */ new Map();
|
|
2060
|
+
const searchDirs = [
|
|
2061
|
+
rootDir,
|
|
2062
|
+
(0, import_node_path11.join)(rootDir, "src"),
|
|
2063
|
+
(0, import_node_path11.join)(rootDir, "app"),
|
|
2064
|
+
(0, import_node_path11.join)(rootDir, "lib")
|
|
2065
|
+
];
|
|
2066
|
+
for (const base of searchDirs) {
|
|
2067
|
+
for (const convention of CONVENTION_DIRS) {
|
|
2068
|
+
const dir = (0, import_node_path11.join)(base, convention);
|
|
2069
|
+
if (!(0, import_node_fs10.existsSync)(dir)) continue;
|
|
2070
|
+
try {
|
|
2071
|
+
const stat = (0, import_node_fs10.statSync)(dir);
|
|
2072
|
+
if (!stat.isDirectory()) continue;
|
|
2073
|
+
const entries = (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
2074
|
+
if (entries.length > 0) {
|
|
2075
|
+
const relPath = dir.replace(rootDir + "/", "").replace(rootDir + "\\", "");
|
|
2076
|
+
result.set(relPath, entries);
|
|
2077
|
+
}
|
|
2078
|
+
} catch {
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return result;
|
|
2083
|
+
}
|
|
2084
|
+
function extractRouteGroups(id) {
|
|
2085
|
+
const groups = [];
|
|
2086
|
+
const re = /\(([^)]+)\)/g;
|
|
2087
|
+
let m;
|
|
2088
|
+
while ((m = re.exec(id)) !== null) {
|
|
2089
|
+
groups.push(m[1]);
|
|
2090
|
+
}
|
|
2091
|
+
return groups;
|
|
2092
|
+
}
|
|
2093
|
+
function isRouteGroup(segment) {
|
|
2094
|
+
return segment.startsWith("(") && segment.endsWith(")");
|
|
2095
|
+
}
|
|
2096
|
+
function isDynamicSegment(segment) {
|
|
2097
|
+
return segment.startsWith("[") || segment.startsWith(":");
|
|
2098
|
+
}
|
|
2099
|
+
function isDomainDir(segment) {
|
|
2100
|
+
return segment.includes(".") && !segment.endsWith(".tsx") && !segment.endsWith(".ts") && !segment.endsWith(".js") && !segment.endsWith(".jsx") && !segment.endsWith(".vue");
|
|
2101
|
+
}
|
|
2102
|
+
function isTrivialGroup(name) {
|
|
2103
|
+
if (TRIVIAL_GROUPS.has(name)) return true;
|
|
2104
|
+
const lower = name.toLowerCase();
|
|
2105
|
+
const wrapperPatterns = [
|
|
2106
|
+
/^.*-?wrapper$/,
|
|
2107
|
+
// "page-wrapper", "use-page-wrapper"
|
|
2108
|
+
/^.*-?layout$/,
|
|
2109
|
+
// "admin-layout", "settings-layout"
|
|
2110
|
+
/^use-/,
|
|
2111
|
+
// "use-page-wrapper"
|
|
2112
|
+
/^default$/
|
|
2113
|
+
];
|
|
2114
|
+
return wrapperPatterns.some((p) => p.test(lower));
|
|
2115
|
+
}
|
|
2116
|
+
function normalizeGroupName(name) {
|
|
2117
|
+
return name.replace(/-pages?$/, "").replace(/-layout$/, "").replace(/-wrapper$/, "");
|
|
2118
|
+
}
|
|
2119
|
+
function extractModuleFromPath(id) {
|
|
2120
|
+
const segments = id.split("/");
|
|
2121
|
+
const routeGroups = extractRouteGroups(id);
|
|
2122
|
+
const moduleGroups = routeGroups.filter((g) => !isTrivialGroup(g)).map(normalizeGroupName);
|
|
2123
|
+
if (moduleGroups.length > 0) {
|
|
2124
|
+
return moduleGroups[moduleGroups.length - 1];
|
|
2125
|
+
}
|
|
2126
|
+
const meaningful = [];
|
|
2127
|
+
for (const seg of segments) {
|
|
2128
|
+
if (seg.includes(".")) continue;
|
|
2129
|
+
if (isRouteGroup(seg)) continue;
|
|
2130
|
+
if (isDynamicSegment(seg)) continue;
|
|
2131
|
+
if (isDomainDir(seg)) continue;
|
|
2132
|
+
if (SKIP_SEGMENTS.has(seg)) continue;
|
|
2133
|
+
meaningful.push(seg);
|
|
2134
|
+
}
|
|
2135
|
+
if (meaningful.length > 0) {
|
|
2136
|
+
return meaningful[0];
|
|
2137
|
+
}
|
|
2138
|
+
return "root";
|
|
2139
|
+
}
|
|
2140
|
+
var import_node_fs10, import_node_path11, CONVENTION_DIRS, SKIP_SEGMENTS, TRIVIAL_GROUPS, cachedRootDir, cachedConventionDirs, moduleTagger;
|
|
2141
|
+
var init_module_tagger = __esm({
|
|
2142
|
+
"src/server/graph/taggers/module-tagger.ts"() {
|
|
2143
|
+
"use strict";
|
|
2144
|
+
import_node_fs10 = require("node:fs");
|
|
2145
|
+
import_node_path11 = require("node:path");
|
|
2146
|
+
CONVENTION_DIRS = ["features", "modules", "domains", "areas"];
|
|
2147
|
+
SKIP_SEGMENTS = /* @__PURE__ */ new Set([
|
|
2148
|
+
"src",
|
|
2149
|
+
"app",
|
|
2150
|
+
"client",
|
|
2151
|
+
"server",
|
|
2152
|
+
"lib",
|
|
2153
|
+
"config"
|
|
2154
|
+
]);
|
|
2155
|
+
TRIVIAL_GROUPS = /* @__PURE__ */ new Set([
|
|
2156
|
+
"app",
|
|
2157
|
+
"all",
|
|
2158
|
+
"ee",
|
|
2159
|
+
"home",
|
|
2160
|
+
"root"
|
|
2161
|
+
]);
|
|
2162
|
+
cachedRootDir = null;
|
|
2163
|
+
cachedConventionDirs = /* @__PURE__ */ new Map();
|
|
2164
|
+
moduleTagger = {
|
|
2165
|
+
id: "module",
|
|
2166
|
+
tagKey: "module",
|
|
2167
|
+
trackUntagged: true,
|
|
2168
|
+
layers: null,
|
|
2169
|
+
// applies to all layers
|
|
2170
|
+
tag(nodes, layer, rootDir) {
|
|
2171
|
+
if (cachedRootDir !== rootDir) {
|
|
2172
|
+
cachedConventionDirs = detectConventionDirs(rootDir);
|
|
2173
|
+
cachedRootDir = rootDir;
|
|
2174
|
+
}
|
|
2175
|
+
let configRules = [];
|
|
2176
|
+
try {
|
|
2177
|
+
const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
|
|
2178
|
+
const config = loadConfig2(rootDir);
|
|
2179
|
+
configRules = config.taggers?.module?.rules ?? [];
|
|
2180
|
+
} catch {
|
|
2181
|
+
}
|
|
2182
|
+
const result = /* @__PURE__ */ new Map();
|
|
2183
|
+
for (const node of nodes) {
|
|
2184
|
+
const id = node.id;
|
|
2185
|
+
let matched = false;
|
|
2186
|
+
for (const rule of configRules) {
|
|
2187
|
+
if (matchGlob(rule.match, id)) {
|
|
2188
|
+
result.set(id, rule.module);
|
|
2189
|
+
matched = true;
|
|
2190
|
+
break;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
if (matched) continue;
|
|
2194
|
+
matched = false;
|
|
2195
|
+
for (const [convDir, moduleNames] of cachedConventionDirs) {
|
|
2196
|
+
if (id.startsWith(convDir + "/")) {
|
|
2197
|
+
const rest = id.slice(convDir.length + 1);
|
|
2198
|
+
const firstSeg = rest.split("/")[0];
|
|
2199
|
+
if (moduleNames.includes(firstSeg)) {
|
|
2200
|
+
result.set(id, firstSeg);
|
|
2201
|
+
matched = true;
|
|
2202
|
+
break;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (matched) continue;
|
|
2207
|
+
const module2 = extractModuleFromPath(id);
|
|
2208
|
+
result.set(id, module2);
|
|
2209
|
+
}
|
|
2210
|
+
return result;
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
|
|
2216
|
+
// src/server/graph/taggers/screen-tagger.ts
|
|
2217
|
+
var SCREEN_TYPES, screenTagger;
|
|
2218
|
+
var init_screen_tagger = __esm({
|
|
2219
|
+
"src/server/graph/taggers/screen-tagger.ts"() {
|
|
2220
|
+
"use strict";
|
|
2221
|
+
SCREEN_TYPES = /* @__PURE__ */ new Set(["page", "layout"]);
|
|
2222
|
+
screenTagger = {
|
|
2223
|
+
id: "screen",
|
|
2224
|
+
tagKey: "screen",
|
|
2225
|
+
trackUntagged: true,
|
|
2226
|
+
layers: ["ui"],
|
|
2227
|
+
tag(nodes, layer) {
|
|
2228
|
+
if (layer !== "ui") return /* @__PURE__ */ new Map();
|
|
2229
|
+
const result = /* @__PURE__ */ new Map();
|
|
2230
|
+
for (const node of nodes) {
|
|
2231
|
+
if (SCREEN_TYPES.has(node.type)) {
|
|
2232
|
+
result.set(node.id, "true");
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return result;
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
// src/server/graph/core/tagger-registry.ts
|
|
2242
|
+
function registerBuiltins2(registry, disabled, config) {
|
|
2243
|
+
for (const tagger of BUILTIN_TAGGERS) {
|
|
2244
|
+
if (disabled.has(tagger.id)) continue;
|
|
2245
|
+
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
2246
|
+
if (override !== void 0) {
|
|
2247
|
+
tagger.trackUntagged = override;
|
|
2248
|
+
}
|
|
2249
|
+
registry.register(tagger);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
function loadCustomTaggers(registry, config, rootDir, disabled) {
|
|
2253
|
+
for (const entry of config.taggers?.custom ?? []) {
|
|
2254
|
+
if (disabled.has(entry.id)) continue;
|
|
2255
|
+
try {
|
|
2256
|
+
const absPath = (0, import_node_path12.resolve)(rootDir, entry.path);
|
|
2257
|
+
const mod = require(absPath);
|
|
2258
|
+
const tagger = "default" in mod ? mod.default : mod;
|
|
2259
|
+
const override = config.taggers?.trackUntagged?.[tagger.id];
|
|
2260
|
+
if (override !== void 0) {
|
|
2261
|
+
tagger.trackUntagged = override;
|
|
2262
|
+
}
|
|
2263
|
+
registry.register(tagger);
|
|
2264
|
+
} catch (err2) {
|
|
2265
|
+
process.stderr.write(`[launch-chart] failed to load custom tagger from ${entry.path}: ${err2}
|
|
2266
|
+
`);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function createTaggerRegistry(config, rootDir) {
|
|
2271
|
+
const registry = new TaggerRegistry();
|
|
2272
|
+
const disabled = new Set(config.taggers?.disabled ?? []);
|
|
2273
|
+
registerBuiltins2(registry, disabled, config);
|
|
2274
|
+
loadCustomTaggers(registry, config, rootDir, disabled);
|
|
2275
|
+
return registry;
|
|
2276
|
+
}
|
|
2277
|
+
var import_node_path12, TaggerRegistry, BUILTIN_TAGGERS;
|
|
2278
|
+
var init_tagger_registry = __esm({
|
|
2279
|
+
"src/server/graph/core/tagger-registry.ts"() {
|
|
2280
|
+
"use strict";
|
|
2281
|
+
import_node_path12 = require("node:path");
|
|
2282
|
+
init_module_tagger();
|
|
2283
|
+
init_screen_tagger();
|
|
2284
|
+
TaggerRegistry = class {
|
|
2285
|
+
constructor() {
|
|
2286
|
+
this.taggers = [];
|
|
2287
|
+
this.ids = /* @__PURE__ */ new Set();
|
|
2288
|
+
}
|
|
2289
|
+
register(tagger) {
|
|
2290
|
+
if (this.ids.has(tagger.id)) {
|
|
2291
|
+
throw new Error(`Duplicate tagger id: ${tagger.id}`);
|
|
2292
|
+
}
|
|
2293
|
+
this.ids.add(tagger.id);
|
|
2294
|
+
this.taggers.push(tagger);
|
|
2295
|
+
}
|
|
2296
|
+
getAll() {
|
|
2297
|
+
return this.taggers;
|
|
2298
|
+
}
|
|
2299
|
+
getForLayer(layer) {
|
|
2300
|
+
return this.taggers.filter((t) => t.layers === null || t.layers.includes(layer));
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
BUILTIN_TAGGERS = [moduleTagger, screenTagger];
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
// src/server/graph/core/tag-store.ts
|
|
2308
|
+
function tagsFilePath(rootDir) {
|
|
2309
|
+
return (0, import_node_path13.join)(rootDir, GRAPHS_DIR, TAGS_FILENAME);
|
|
2310
|
+
}
|
|
2311
|
+
function readTagStore(rootDir) {
|
|
2312
|
+
const filePath = tagsFilePath(rootDir);
|
|
2313
|
+
if (!(0, import_node_fs11.existsSync)(filePath)) return {};
|
|
2314
|
+
const stat = (0, import_node_fs11.statSync)(filePath);
|
|
2315
|
+
const cached = tagCache.get(filePath);
|
|
2316
|
+
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
2317
|
+
return cached.store;
|
|
2318
|
+
}
|
|
2319
|
+
try {
|
|
2320
|
+
const content = (0, import_node_fs11.readFileSync)(filePath, "utf-8");
|
|
2321
|
+
const store = JSON.parse(content);
|
|
2322
|
+
tagCache.set(filePath, { mtimeMs: stat.mtimeMs, store });
|
|
2323
|
+
return store;
|
|
2324
|
+
} catch {
|
|
2325
|
+
return {};
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function writeTagStore(rootDir, store) {
|
|
2329
|
+
const filePath = tagsFilePath(rootDir);
|
|
2330
|
+
const dir = (0, import_node_path13.dirname)(filePath);
|
|
2331
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
2332
|
+
const cleaned = {};
|
|
2333
|
+
for (const [nodeId, tags] of Object.entries(store)) {
|
|
2334
|
+
if (Object.keys(tags).length > 0) {
|
|
2335
|
+
cleaned[nodeId] = tags;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
(0, import_node_fs11.writeFileSync)(filePath, JSON.stringify(cleaned, null, 2) + "\n", "utf-8");
|
|
2339
|
+
tagCache.delete(filePath);
|
|
2340
|
+
}
|
|
2341
|
+
function setTag(rootDir, nodeId, key, value) {
|
|
2342
|
+
const store = readTagStore(rootDir);
|
|
2343
|
+
if (!store[nodeId]) store[nodeId] = {};
|
|
2344
|
+
store[nodeId][key] = value;
|
|
2345
|
+
writeTagStore(rootDir, store);
|
|
2346
|
+
}
|
|
2347
|
+
function removeTag(rootDir, nodeId, key) {
|
|
2348
|
+
const store = readTagStore(rootDir);
|
|
2349
|
+
if (!store[nodeId]) return;
|
|
2350
|
+
delete store[nodeId][key];
|
|
2351
|
+
if (Object.keys(store[nodeId]).length === 0) {
|
|
2352
|
+
delete store[nodeId];
|
|
2353
|
+
}
|
|
2354
|
+
writeTagStore(rootDir, store);
|
|
2355
|
+
}
|
|
2356
|
+
var import_node_fs11, import_node_path13, TAGS_FILENAME, GRAPHS_DIR, tagCache;
|
|
2357
|
+
var init_tag_store = __esm({
|
|
2358
|
+
"src/server/graph/core/tag-store.ts"() {
|
|
2359
|
+
"use strict";
|
|
2360
|
+
import_node_fs11 = require("node:fs");
|
|
2361
|
+
import_node_path13 = require("node:path");
|
|
2362
|
+
TAGS_FILENAME = "tags.json";
|
|
2363
|
+
GRAPHS_DIR = ".launchsecure/graphs";
|
|
2364
|
+
tagCache = /* @__PURE__ */ new Map();
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2034
2368
|
// src/server/graph/index.ts
|
|
2035
2369
|
function graphsDir(rootDir) {
|
|
2036
|
-
return (0,
|
|
2370
|
+
return (0, import_node_path14.join)(rootDir, GRAPHS_DIR2);
|
|
2037
2371
|
}
|
|
2038
2372
|
function graphFilePath(rootDir, layer) {
|
|
2039
|
-
return (0,
|
|
2373
|
+
return (0, import_node_path14.join)(graphsDir(rootDir), `${layer}.json`);
|
|
2374
|
+
}
|
|
2375
|
+
function tagsFilePath2(rootDir) {
|
|
2376
|
+
return (0, import_node_path14.join)(graphsDir(rootDir), "tags.json");
|
|
2377
|
+
}
|
|
2378
|
+
function getMtimeMs(filePath) {
|
|
2379
|
+
if (!(0, import_node_fs12.existsSync)(filePath)) return 0;
|
|
2380
|
+
return (0, import_node_fs12.statSync)(filePath).mtimeMs;
|
|
2040
2381
|
}
|
|
2041
2382
|
function invalidateCache(filePath) {
|
|
2042
2383
|
graphCache.delete(filePath);
|
|
2043
2384
|
}
|
|
2044
|
-
function
|
|
2385
|
+
function invalidateTaggedCache(rootDir, layer) {
|
|
2386
|
+
taggedCache.delete(`${rootDir}:${layer}`);
|
|
2387
|
+
}
|
|
2388
|
+
function applyTags(graph, layer, rootDir) {
|
|
2389
|
+
const config = loadConfig(rootDir);
|
|
2390
|
+
const registry = createTaggerRegistry(config, rootDir);
|
|
2391
|
+
const manualTags = readTagStore(rootDir);
|
|
2392
|
+
const taggedNodes = graph.nodes.map((n) => ({ ...n }));
|
|
2393
|
+
const taggers = registry.getForLayer(layer);
|
|
2394
|
+
for (const tagger of taggers) {
|
|
2395
|
+
const assignments = tagger.tag(taggedNodes, layer, rootDir);
|
|
2396
|
+
for (const node of taggedNodes) {
|
|
2397
|
+
if (!node.tags) node.tags = {};
|
|
2398
|
+
const tags = node.tags;
|
|
2399
|
+
const value = assignments.get(node.id);
|
|
2400
|
+
if (value !== void 0) {
|
|
2401
|
+
tags[tagger.tagKey] = value;
|
|
2402
|
+
} else if (tagger.trackUntagged) {
|
|
2403
|
+
tags[tagger.tagKey] = "untagged";
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
for (const node of taggedNodes) {
|
|
2408
|
+
const manual = manualTags[node.id];
|
|
2409
|
+
if (manual) {
|
|
2410
|
+
if (!node.tags) node.tags = {};
|
|
2411
|
+
const tags = node.tags;
|
|
2412
|
+
Object.assign(tags, manual);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return { ...graph, nodes: taggedNodes };
|
|
2416
|
+
}
|
|
2417
|
+
function readGraphRaw(rootDir, layer) {
|
|
2045
2418
|
const filePath = graphFilePath(rootDir, layer);
|
|
2046
|
-
if (!(0,
|
|
2047
|
-
const stat = (0,
|
|
2419
|
+
if (!(0, import_node_fs12.existsSync)(filePath)) return null;
|
|
2420
|
+
const stat = (0, import_node_fs12.statSync)(filePath);
|
|
2048
2421
|
const cached = graphCache.get(filePath);
|
|
2049
2422
|
if (cached && cached.mtimeMs === stat.mtimeMs) {
|
|
2050
2423
|
return cached.graph;
|
|
2051
2424
|
}
|
|
2052
|
-
const content = (0,
|
|
2425
|
+
const content = (0, import_node_fs12.readFileSync)(filePath, "utf-8");
|
|
2053
2426
|
const graph = JSON.parse(content);
|
|
2054
2427
|
graphCache.set(filePath, { mtimeMs: stat.mtimeMs, graph });
|
|
2055
2428
|
return graph;
|
|
2056
2429
|
}
|
|
2430
|
+
function readGraph(rootDir, layer) {
|
|
2431
|
+
const rawFilePath = graphFilePath(rootDir, layer);
|
|
2432
|
+
if (!(0, import_node_fs12.existsSync)(rawFilePath)) return null;
|
|
2433
|
+
const rawMtime = getMtimeMs(rawFilePath);
|
|
2434
|
+
const tagsMtime = getMtimeMs(tagsFilePath2(rootDir));
|
|
2435
|
+
const cacheKey = `${rootDir}:${layer}`;
|
|
2436
|
+
const cached = taggedCache.get(cacheKey);
|
|
2437
|
+
if (cached && cached.rawMtimeMs === rawMtime && cached.tagsMtimeMs === tagsMtime) {
|
|
2438
|
+
return cached.graph;
|
|
2439
|
+
}
|
|
2440
|
+
const raw = readGraphRaw(rootDir, layer);
|
|
2441
|
+
if (!raw) return null;
|
|
2442
|
+
const tagged = applyTags(raw, layer, rootDir);
|
|
2443
|
+
taggedCache.set(cacheKey, { rawMtimeMs: rawMtime, tagsMtimeMs: tagsMtime, graph: tagged });
|
|
2444
|
+
return tagged;
|
|
2445
|
+
}
|
|
2057
2446
|
function readAllGraphs(rootDir) {
|
|
2058
2447
|
const result = {};
|
|
2059
2448
|
for (const layer of LAYERS) {
|
|
@@ -2064,25 +2453,31 @@ function readAllGraphs(rootDir) {
|
|
|
2064
2453
|
}
|
|
2065
2454
|
function generateGraph(rootDir, layer) {
|
|
2066
2455
|
const dir = graphsDir(rootDir);
|
|
2067
|
-
(0,
|
|
2456
|
+
(0, import_node_fs12.mkdirSync)(dir, { recursive: true });
|
|
2068
2457
|
const results = layer ? [generateLayer(rootDir, layer)].filter((r) => r !== null) : generateAll(rootDir);
|
|
2069
2458
|
for (const result of results) {
|
|
2070
2459
|
const filePath = graphFilePath(rootDir, result.layer);
|
|
2071
|
-
(0,
|
|
2460
|
+
(0, import_node_fs12.writeFileSync)(filePath, JSON.stringify(result.output, null, 2) + "\n", "utf-8");
|
|
2072
2461
|
invalidateCache(filePath);
|
|
2462
|
+
invalidateTaggedCache(rootDir, result.layer);
|
|
2073
2463
|
}
|
|
2074
2464
|
return results;
|
|
2075
2465
|
}
|
|
2076
|
-
var
|
|
2466
|
+
var import_node_fs12, import_node_path14, GRAPHS_DIR2, LAYERS, graphCache, taggedCache;
|
|
2077
2467
|
var init_graph = __esm({
|
|
2078
2468
|
"src/server/graph/index.ts"() {
|
|
2079
2469
|
"use strict";
|
|
2080
|
-
|
|
2081
|
-
|
|
2470
|
+
import_node_fs12 = require("node:fs");
|
|
2471
|
+
import_node_path14 = require("node:path");
|
|
2082
2472
|
init_graph_builder();
|
|
2083
|
-
|
|
2473
|
+
init_config();
|
|
2474
|
+
init_tagger_registry();
|
|
2475
|
+
init_tag_store();
|
|
2476
|
+
init_tag_store();
|
|
2477
|
+
GRAPHS_DIR2 = ".launchsecure/graphs";
|
|
2084
2478
|
LAYERS = ["ui", "api", "db"];
|
|
2085
2479
|
graphCache = /* @__PURE__ */ new Map();
|
|
2480
|
+
taggedCache = /* @__PURE__ */ new Map();
|
|
2086
2481
|
}
|
|
2087
2482
|
});
|
|
2088
2483
|
|
|
@@ -2092,19 +2487,22 @@ __export(chart_serve_exports, {
|
|
|
2092
2487
|
runServeCli: () => runServeCli,
|
|
2093
2488
|
startChartServer: () => startChartServer
|
|
2094
2489
|
});
|
|
2095
|
-
function
|
|
2490
|
+
function randomPort() {
|
|
2491
|
+
return 49152 + Math.floor(Math.random() * (65535 - 49152));
|
|
2492
|
+
}
|
|
2493
|
+
function findProjectRoot(startDir) {
|
|
2096
2494
|
let dir = startDir;
|
|
2097
2495
|
for (let i = 0; i < 8; i++) {
|
|
2098
|
-
const graphsDir2 =
|
|
2099
|
-
if (
|
|
2100
|
-
const parent =
|
|
2496
|
+
const graphsDir2 = import_node_path15.default.join(dir, ".launchsecure", "graphs");
|
|
2497
|
+
if (import_node_fs13.default.existsSync(import_node_path15.default.join(graphsDir2, "ui.json")) || import_node_fs13.default.existsSync(import_node_path15.default.join(graphsDir2, "api.json")) || import_node_fs13.default.existsSync(import_node_path15.default.join(graphsDir2, "db.json"))) return dir;
|
|
2498
|
+
const parent = import_node_path15.default.dirname(dir);
|
|
2101
2499
|
if (parent === dir) break;
|
|
2102
2500
|
dir = parent;
|
|
2103
2501
|
}
|
|
2104
2502
|
dir = startDir;
|
|
2105
2503
|
for (let i = 0; i < 8; i++) {
|
|
2106
|
-
if (
|
|
2107
|
-
const parent =
|
|
2504
|
+
if (import_node_fs13.default.existsSync(import_node_path15.default.join(dir, ".git"))) return dir;
|
|
2505
|
+
const parent = import_node_path15.default.dirname(dir);
|
|
2108
2506
|
if (parent === dir) break;
|
|
2109
2507
|
dir = parent;
|
|
2110
2508
|
}
|
|
@@ -2156,16 +2554,16 @@ function buildMergedGraph(projectRoot) {
|
|
|
2156
2554
|
};
|
|
2157
2555
|
}
|
|
2158
2556
|
function serveStatic(res, filePath) {
|
|
2159
|
-
if (!
|
|
2160
|
-
const ext =
|
|
2557
|
+
if (!import_node_fs13.default.existsSync(filePath) || !import_node_fs13.default.statSync(filePath).isFile()) return false;
|
|
2558
|
+
const ext = import_node_path15.default.extname(filePath).toLowerCase();
|
|
2161
2559
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2162
2560
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
|
|
2163
|
-
|
|
2561
|
+
import_node_fs13.default.createReadStream(filePath).pipe(res);
|
|
2164
2562
|
return true;
|
|
2165
2563
|
}
|
|
2166
2564
|
function serveIndex(res, clientDir) {
|
|
2167
|
-
const indexPath =
|
|
2168
|
-
if (!
|
|
2565
|
+
const indexPath = import_node_path15.default.join(clientDir, "index.html");
|
|
2566
|
+
if (!import_node_fs13.default.existsSync(indexPath)) {
|
|
2169
2567
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
2170
2568
|
res.end(`LaunchChart client bundle not found at ${clientDir}. Run 'npm run build:chart-client'.`);
|
|
2171
2569
|
return;
|
|
@@ -2173,14 +2571,14 @@ function serveIndex(res, clientDir) {
|
|
|
2173
2571
|
serveStatic(res, indexPath);
|
|
2174
2572
|
}
|
|
2175
2573
|
function tryListen(server, port) {
|
|
2176
|
-
return new Promise((
|
|
2574
|
+
return new Promise((resolve3, reject) => {
|
|
2177
2575
|
const onError = (err2) => {
|
|
2178
2576
|
server.off("listening", onListening);
|
|
2179
2577
|
reject(err2);
|
|
2180
2578
|
};
|
|
2181
2579
|
const onListening = () => {
|
|
2182
2580
|
server.off("error", onError);
|
|
2183
|
-
|
|
2581
|
+
resolve3(port);
|
|
2184
2582
|
};
|
|
2185
2583
|
server.once("error", onError);
|
|
2186
2584
|
server.once("listening", onListening);
|
|
@@ -2206,8 +2604,8 @@ async function bindWithFallback(server, startPort) {
|
|
|
2206
2604
|
}
|
|
2207
2605
|
async function startChartServer(opts = {}) {
|
|
2208
2606
|
const cwd = opts.cwd ?? process.cwd();
|
|
2209
|
-
const projectRoot =
|
|
2210
|
-
const existing = getLiveLock();
|
|
2607
|
+
const projectRoot = findProjectRoot(cwd);
|
|
2608
|
+
const existing = getLiveLock(projectRoot);
|
|
2211
2609
|
if (existing) {
|
|
2212
2610
|
if (!opts.quiet) {
|
|
2213
2611
|
process.stderr.write(
|
|
@@ -2217,7 +2615,7 @@ async function startChartServer(opts = {}) {
|
|
|
2217
2615
|
}
|
|
2218
2616
|
return { port: existing.port, url: existing.url };
|
|
2219
2617
|
}
|
|
2220
|
-
const clientDir = opts.clientDir ??
|
|
2618
|
+
const clientDir = opts.clientDir ?? import_node_path15.default.join(__dirname, "..", "chart-client");
|
|
2221
2619
|
const server = import_node_http.default.createServer((req, res) => {
|
|
2222
2620
|
try {
|
|
2223
2621
|
const url2 = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
@@ -2255,6 +2653,26 @@ async function startChartServer(opts = {}) {
|
|
|
2255
2653
|
}
|
|
2256
2654
|
return;
|
|
2257
2655
|
}
|
|
2656
|
+
if (req.method === "GET" && url2.pathname === "/api/file-content") {
|
|
2657
|
+
const relPath = url2.searchParams.get("path");
|
|
2658
|
+
if (!relPath || relPath.includes("..") || import_node_path15.default.isAbsolute(relPath)) {
|
|
2659
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2660
|
+
res.end(JSON.stringify({ error: "Invalid path" }));
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
const filePath = import_node_path15.default.join(projectRoot, relPath);
|
|
2664
|
+
if (!filePath.startsWith(projectRoot) || !import_node_fs13.default.existsSync(filePath) || !import_node_fs13.default.statSync(filePath).isFile()) {
|
|
2665
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2666
|
+
res.end(JSON.stringify({ error: "File not found" }));
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
const ext = import_node_path15.default.extname(filePath).toLowerCase();
|
|
2670
|
+
const langMap = { ".ts": "typescript", ".tsx": "tsx", ".js": "javascript", ".jsx": "jsx", ".prisma": "prisma", ".json": "json", ".css": "css" };
|
|
2671
|
+
const content = import_node_fs13.default.readFileSync(filePath, "utf-8");
|
|
2672
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2673
|
+
res.end(JSON.stringify({ content, language: langMap[ext] ?? "text", path: relPath }));
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2258
2676
|
if (req.method === "GET" && url2.pathname === "/api/health") {
|
|
2259
2677
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2260
2678
|
res.end(JSON.stringify({ ok: true, projectRoot }));
|
|
@@ -2284,8 +2702,94 @@ async function startChartServer(opts = {}) {
|
|
|
2284
2702
|
req.on("end", () => {
|
|
2285
2703
|
try {
|
|
2286
2704
|
const newConfig = JSON.parse(body);
|
|
2287
|
-
const configPath =
|
|
2288
|
-
|
|
2705
|
+
const configPath = import_node_path15.default.join(projectRoot, ".launchchart.json");
|
|
2706
|
+
import_node_fs13.default.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
|
|
2707
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2708
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2709
|
+
} catch (err2) {
|
|
2710
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2711
|
+
res.end(JSON.stringify({ ok: false, error: String(err2) }));
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
if (req.method === "GET" && url2.pathname === "/api/tagger-config") {
|
|
2717
|
+
const config = loadConfig(projectRoot);
|
|
2718
|
+
const builtinTaggers = [
|
|
2719
|
+
{ id: "module", tagKey: "module", trackUntagged: config.taggers?.trackUntagged?.module ?? true },
|
|
2720
|
+
{ id: "screen", tagKey: "screen", trackUntagged: config.taggers?.trackUntagged?.screen ?? true }
|
|
2721
|
+
];
|
|
2722
|
+
const disabled = config.taggers?.disabled ?? [];
|
|
2723
|
+
const customTaggers = config.taggers?.custom ?? [];
|
|
2724
|
+
const moduleRules = config.taggers?.module?.rules ?? [];
|
|
2725
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2726
|
+
res.end(JSON.stringify({ builtinTaggers, disabled, customTaggers, moduleRules }));
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
if (req.method === "POST" && url2.pathname === "/api/tagger-config") {
|
|
2730
|
+
let body = "";
|
|
2731
|
+
req.on("data", (chunk) => {
|
|
2732
|
+
body += chunk.toString();
|
|
2733
|
+
});
|
|
2734
|
+
req.on("end", () => {
|
|
2735
|
+
try {
|
|
2736
|
+
const taggerConfig = JSON.parse(body);
|
|
2737
|
+
const config = loadConfig(projectRoot);
|
|
2738
|
+
config.taggers = taggerConfig;
|
|
2739
|
+
const configPath = import_node_path15.default.join(projectRoot, ".launchchart.json");
|
|
2740
|
+
import_node_fs13.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2741
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2742
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2743
|
+
} catch (err2) {
|
|
2744
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2745
|
+
res.end(JSON.stringify({ ok: false, error: String(err2) }));
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
if (req.method === "GET" && url2.pathname === "/api/tags") {
|
|
2751
|
+
const store = readTagStore(projectRoot);
|
|
2752
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2753
|
+
res.end(JSON.stringify(store));
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
if (req.method === "POST" && url2.pathname === "/api/tags") {
|
|
2757
|
+
let body = "";
|
|
2758
|
+
req.on("data", (chunk) => {
|
|
2759
|
+
body += chunk.toString();
|
|
2760
|
+
});
|
|
2761
|
+
req.on("end", () => {
|
|
2762
|
+
try {
|
|
2763
|
+
const { nodeId, key, value } = JSON.parse(body);
|
|
2764
|
+
if (!nodeId || !key || !value) {
|
|
2765
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2766
|
+
res.end(JSON.stringify({ ok: false, error: "nodeId, key, and value are required" }));
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
setTag(projectRoot, nodeId, key, value);
|
|
2770
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2771
|
+
res.end(JSON.stringify({ ok: true }));
|
|
2772
|
+
} catch (err2) {
|
|
2773
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2774
|
+
res.end(JSON.stringify({ ok: false, error: String(err2) }));
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
if (req.method === "DELETE" && url2.pathname === "/api/tags") {
|
|
2780
|
+
let body = "";
|
|
2781
|
+
req.on("data", (chunk) => {
|
|
2782
|
+
body += chunk.toString();
|
|
2783
|
+
});
|
|
2784
|
+
req.on("end", () => {
|
|
2785
|
+
try {
|
|
2786
|
+
const { nodeId, key } = JSON.parse(body);
|
|
2787
|
+
if (!nodeId || !key) {
|
|
2788
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2789
|
+
res.end(JSON.stringify({ ok: false, error: "nodeId and key are required" }));
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
removeTag(projectRoot, nodeId, key);
|
|
2289
2793
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2290
2794
|
res.end(JSON.stringify({ ok: true }));
|
|
2291
2795
|
} catch (err2) {
|
|
@@ -2296,7 +2800,7 @@ async function startChartServer(opts = {}) {
|
|
|
2296
2800
|
return;
|
|
2297
2801
|
}
|
|
2298
2802
|
if (url2.pathname !== "/") {
|
|
2299
|
-
const staticPath =
|
|
2803
|
+
const staticPath = import_node_path15.default.join(clientDir, url2.pathname);
|
|
2300
2804
|
if (serveStatic(res, staticPath)) return;
|
|
2301
2805
|
}
|
|
2302
2806
|
serveIndex(res, clientDir);
|
|
@@ -2305,7 +2809,8 @@ async function startChartServer(opts = {}) {
|
|
|
2305
2809
|
res.end(JSON.stringify({ error: String(err2) }));
|
|
2306
2810
|
}
|
|
2307
2811
|
});
|
|
2308
|
-
const
|
|
2812
|
+
const startPort = opts.port ?? randomPort();
|
|
2813
|
+
const port = await bindWithFallback(server, startPort);
|
|
2309
2814
|
const url = `http://localhost:${port}`;
|
|
2310
2815
|
writeLock({
|
|
2311
2816
|
pid: process.pid,
|
|
@@ -2313,9 +2818,9 @@ async function startChartServer(opts = {}) {
|
|
|
2313
2818
|
cwd,
|
|
2314
2819
|
url,
|
|
2315
2820
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2316
|
-
});
|
|
2821
|
+
}, projectRoot);
|
|
2317
2822
|
const cleanup = () => {
|
|
2318
|
-
clearLock();
|
|
2823
|
+
clearLock(projectRoot);
|
|
2319
2824
|
server.close();
|
|
2320
2825
|
};
|
|
2321
2826
|
process.once("SIGINT", () => {
|
|
@@ -2350,21 +2855,20 @@ function runServeCli(argv) {
|
|
|
2350
2855
|
process.exit(1);
|
|
2351
2856
|
});
|
|
2352
2857
|
}
|
|
2353
|
-
var import_node_http,
|
|
2858
|
+
var import_node_http, import_node_fs13, import_node_path15, MAX_PORT_SCAN, MIME_TYPES;
|
|
2354
2859
|
var init_chart_serve = __esm({
|
|
2355
2860
|
"src/server/chart-serve.ts"() {
|
|
2356
2861
|
"use strict";
|
|
2357
2862
|
import_node_http = __toESM(require("node:http"));
|
|
2358
|
-
|
|
2359
|
-
|
|
2863
|
+
import_node_fs13 = __toESM(require("node:fs"));
|
|
2864
|
+
import_node_path15 = __toESM(require("node:path"));
|
|
2360
2865
|
init_graph();
|
|
2361
2866
|
init_lockfile();
|
|
2362
2867
|
init_config();
|
|
2363
2868
|
init_react_nextjs();
|
|
2364
2869
|
init_nextjs_routes();
|
|
2365
2870
|
init_prisma_schema();
|
|
2366
|
-
|
|
2367
|
-
MAX_PORT_SCAN = 20;
|
|
2871
|
+
MAX_PORT_SCAN = 3;
|
|
2368
2872
|
MIME_TYPES = {
|
|
2369
2873
|
".html": "text/html; charset=utf-8",
|
|
2370
2874
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -2395,7 +2899,7 @@ function matchesSearch(node, query) {
|
|
|
2395
2899
|
function toMinimal(nodes) {
|
|
2396
2900
|
return nodes.map((n) => {
|
|
2397
2901
|
const out = { id: n.id, type: n.type, name: n.name };
|
|
2398
|
-
if (n.
|
|
2902
|
+
if (n.tags != null) out.tags = n.tags;
|
|
2399
2903
|
if (n.route != null) out.route = n.route;
|
|
2400
2904
|
if (n.methods != null) out.methods = n.methods;
|
|
2401
2905
|
return out;
|
|
@@ -2403,11 +2907,13 @@ function toMinimal(nodes) {
|
|
|
2403
2907
|
}
|
|
2404
2908
|
function toCompactNode(n) {
|
|
2405
2909
|
const out = { i: n.id, t: n.type, n: n.name };
|
|
2406
|
-
|
|
2910
|
+
const tags = n.tags;
|
|
2911
|
+
if (tags?.module) out.m = tags.module;
|
|
2407
2912
|
if (n.route != null) out.r = n.route;
|
|
2408
2913
|
if (n.methods != null) out.mt = n.methods;
|
|
2409
2914
|
if (n.exports != null) out.x = n.exports;
|
|
2410
2915
|
if (n.columns != null) out.c = n.columns;
|
|
2916
|
+
if (tags != null) out.tg = tags;
|
|
2411
2917
|
for (const k of Object.keys(n)) {
|
|
2412
2918
|
if (!COMPACT_NODE_KNOWN_KEYS.has(k) && n[k] != null) out[k] = n[k];
|
|
2413
2919
|
}
|
|
@@ -2483,7 +2989,8 @@ function layerSummary(graph) {
|
|
|
2483
2989
|
const moduleCounts = {};
|
|
2484
2990
|
for (const n of graph.nodes) {
|
|
2485
2991
|
typeCounts[n.type] = (typeCounts[n.type] ?? 0) + 1;
|
|
2486
|
-
const
|
|
2992
|
+
const tags = n.tags;
|
|
2993
|
+
const mod = tags?.module;
|
|
2487
2994
|
if (mod) moduleCounts[mod] = (moduleCounts[mod] ?? 0) + 1;
|
|
2488
2995
|
}
|
|
2489
2996
|
const edgeTypeCounts = {};
|
|
@@ -2540,12 +3047,14 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
2540
3047
|
const search = args.search;
|
|
2541
3048
|
const type = args.type;
|
|
2542
3049
|
const module_ = args.module;
|
|
3050
|
+
const tagKey = args.tag_key;
|
|
3051
|
+
const tagValue = args.tag_value;
|
|
2543
3052
|
const nodeId = args.node_id;
|
|
2544
3053
|
const hops = args.hops ?? 1;
|
|
2545
3054
|
const layerIsDb = args.layer === "db";
|
|
2546
3055
|
const minimal = args.minimal ?? layerIsDb;
|
|
2547
3056
|
const includeEdges = args.include_edges;
|
|
2548
|
-
const hasFilter = !!(search || type || module_ || nodeId);
|
|
3057
|
+
const hasFilter = !!(search || type || module_ || nodeId || tagKey && tagValue);
|
|
2549
3058
|
if (layer && !["ui", "api", "db"].includes(layer)) {
|
|
2550
3059
|
return { error: `Invalid layer "${layer}". Must be one of: ui, api, db` };
|
|
2551
3060
|
}
|
|
@@ -2601,7 +3110,9 @@ function runReadGraphQueryRaw(rootDir, args) {
|
|
|
2601
3110
|
const matched = graph.nodes.filter((n) => {
|
|
2602
3111
|
if (search && !matchesSearch(n, search)) return false;
|
|
2603
3112
|
if (type && n.type !== type) return false;
|
|
2604
|
-
|
|
3113
|
+
const nodeTags = n.tags;
|
|
3114
|
+
if (module_ && nodeTags?.module !== module_) return false;
|
|
3115
|
+
if (tagKey && tagValue && nodeTags?.[tagKey] !== tagValue) return false;
|
|
2605
3116
|
return true;
|
|
2606
3117
|
});
|
|
2607
3118
|
const matchedIds = new Set(matched.map((n) => n.id));
|
|
@@ -2688,9 +3199,9 @@ function handleReadGraph(args) {
|
|
|
2688
3199
|
return okJson(result);
|
|
2689
3200
|
}
|
|
2690
3201
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
2691
|
-
if (layer === "ui") return (0,
|
|
2692
|
-
if (layer === "api") return (0,
|
|
2693
|
-
if (layer === "db") return (0,
|
|
3202
|
+
if (layer === "ui") return (0, import_node_path16.join)(rootDir, "src", nodeId);
|
|
3203
|
+
if (layer === "api") return (0, import_node_path16.join)(rootDir, nodeId);
|
|
3204
|
+
if (layer === "db") return (0, import_node_path16.join)(rootDir, "prisma", "schema.prisma");
|
|
2694
3205
|
return null;
|
|
2695
3206
|
}
|
|
2696
3207
|
function handleGrepNodes(args) {
|
|
@@ -2750,11 +3261,11 @@ function handleGrepNodes(args) {
|
|
|
2750
3261
|
let filesSearched = 0;
|
|
2751
3262
|
let truncated = false;
|
|
2752
3263
|
for (const [filePath, nodeId] of filePaths) {
|
|
2753
|
-
if (!(0,
|
|
3264
|
+
if (!(0, import_node_fs14.existsSync)(filePath)) continue;
|
|
2754
3265
|
filesSearched++;
|
|
2755
3266
|
let content;
|
|
2756
3267
|
try {
|
|
2757
|
-
content = (0,
|
|
3268
|
+
content = (0, import_node_fs14.readFileSync)(filePath, "utf-8");
|
|
2758
3269
|
} catch {
|
|
2759
3270
|
continue;
|
|
2760
3271
|
}
|
|
@@ -2792,7 +3303,8 @@ function handleGrepNodes(args) {
|
|
|
2792
3303
|
});
|
|
2793
3304
|
}
|
|
2794
3305
|
function handleChartServerStatus() {
|
|
2795
|
-
const
|
|
3306
|
+
const rootDir = process.cwd();
|
|
3307
|
+
const lock = getLiveLock(rootDir);
|
|
2796
3308
|
if (!lock) {
|
|
2797
3309
|
return okJson({ running: false });
|
|
2798
3310
|
}
|
|
@@ -2806,7 +3318,8 @@ function handleChartServerStatus() {
|
|
|
2806
3318
|
});
|
|
2807
3319
|
}
|
|
2808
3320
|
function handleStartChartServer(args) {
|
|
2809
|
-
const
|
|
3321
|
+
const rootDir = process.cwd();
|
|
3322
|
+
const lock = getLiveLock(rootDir);
|
|
2810
3323
|
if (lock) {
|
|
2811
3324
|
return okJson({
|
|
2812
3325
|
started: false,
|
|
@@ -2817,11 +3330,11 @@ function handleStartChartServer(args) {
|
|
|
2817
3330
|
});
|
|
2818
3331
|
}
|
|
2819
3332
|
const entryPath = process.argv[1];
|
|
2820
|
-
const logDir = (0,
|
|
2821
|
-
(0,
|
|
2822
|
-
const logPath = (0,
|
|
2823
|
-
const out = (0,
|
|
2824
|
-
const err2 = (0,
|
|
3333
|
+
const logDir = (0, import_node_path16.join)((0, import_node_os2.homedir)(), ".launchsecure");
|
|
3334
|
+
(0, import_node_fs14.mkdirSync)(logDir, { recursive: true });
|
|
3335
|
+
const logPath = (0, import_node_path16.join)(logDir, "launch-chart.log");
|
|
3336
|
+
const out = (0, import_node_fs14.openSync)(logPath, "a");
|
|
3337
|
+
const err2 = (0, import_node_fs14.openSync)(logPath, "a");
|
|
2825
3338
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
2826
3339
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
2827
3340
|
detached: true,
|
|
@@ -2836,7 +3349,8 @@ function handleStartChartServer(args) {
|
|
|
2836
3349
|
});
|
|
2837
3350
|
}
|
|
2838
3351
|
function handleStopChartServer() {
|
|
2839
|
-
const
|
|
3352
|
+
const rootDir = process.cwd();
|
|
3353
|
+
const lock = getLiveLock(rootDir);
|
|
2840
3354
|
if (!lock) {
|
|
2841
3355
|
return okJson({ stopped: false, reason: "not_running" });
|
|
2842
3356
|
}
|
|
@@ -2846,14 +3360,45 @@ function handleStopChartServer() {
|
|
|
2846
3360
|
} catch (e) {
|
|
2847
3361
|
const code = e.code;
|
|
2848
3362
|
if (code === "ESRCH") {
|
|
2849
|
-
clearLock();
|
|
3363
|
+
clearLock(rootDir);
|
|
2850
3364
|
return okJson({ stopped: true, pid: lock.pid, note: "process was already gone, lock cleaned up" });
|
|
2851
3365
|
}
|
|
2852
3366
|
return okJson({ stopped: false, reason: `kill failed: ${code ?? e}` });
|
|
2853
3367
|
}
|
|
2854
3368
|
}
|
|
3369
|
+
function handleAddTag(args) {
|
|
3370
|
+
const rootDir = process.cwd();
|
|
3371
|
+
const nodeId = args.node_id;
|
|
3372
|
+
const key = args.key;
|
|
3373
|
+
const value = args.value;
|
|
3374
|
+
if (!nodeId) return err("node_id is required");
|
|
3375
|
+
if (!key) return err("key is required");
|
|
3376
|
+
if (!value) return err("value is required");
|
|
3377
|
+
const graphs = readAllGraphs(rootDir);
|
|
3378
|
+
let found = false;
|
|
3379
|
+
for (const graph of Object.values(graphs)) {
|
|
3380
|
+
if (graph && graph.nodes.some((n) => n.id === nodeId)) {
|
|
3381
|
+
found = true;
|
|
3382
|
+
break;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
if (!found) {
|
|
3386
|
+
return err(`Node "${nodeId}" not found in any graph layer. Check the node_id.`);
|
|
3387
|
+
}
|
|
3388
|
+
setTag(rootDir, nodeId, key, value);
|
|
3389
|
+
return okJson({ ok: true, node_id: nodeId, tag: { [key]: value } });
|
|
3390
|
+
}
|
|
3391
|
+
function handleRemoveTag(args) {
|
|
3392
|
+
const rootDir = process.cwd();
|
|
3393
|
+
const nodeId = args.node_id;
|
|
3394
|
+
const key = args.key;
|
|
3395
|
+
if (!nodeId) return err("node_id is required");
|
|
3396
|
+
if (!key) return err("key is required");
|
|
3397
|
+
removeTag(rootDir, nodeId, key);
|
|
3398
|
+
return okJson({ ok: true, node_id: nodeId, removed_key: key });
|
|
3399
|
+
}
|
|
2855
3400
|
function handleDetectProjectStack() {
|
|
2856
|
-
const rootDir =
|
|
3401
|
+
const rootDir = process.cwd();
|
|
2857
3402
|
const parsers = [
|
|
2858
3403
|
{ id: "react-nextjs", layer: "ui", detected: reactNextjsParser.detect(rootDir) },
|
|
2859
3404
|
{ id: "nextjs-routes", layer: "api", detected: nextjsRoutesParser.detect(rootDir) },
|
|
@@ -2871,20 +3416,20 @@ function handleDetectProjectStack() {
|
|
|
2871
3416
|
if (f.type === "out_of_pattern") stats.out_of_pattern++;
|
|
2872
3417
|
}
|
|
2873
3418
|
}
|
|
2874
|
-
const srcDir = (0,
|
|
2875
|
-
if ((0,
|
|
3419
|
+
const srcDir = (0, import_node_path16.join)(rootDir, "src");
|
|
3420
|
+
if ((0, import_node_fs14.existsSync)(srcDir)) {
|
|
2876
3421
|
const scanDir = (dir) => {
|
|
2877
|
-
if (!(0,
|
|
2878
|
-
for (const entry of (0,
|
|
3422
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return;
|
|
3423
|
+
for (const entry of (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true })) {
|
|
2879
3424
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2880
|
-
const full = (0,
|
|
3425
|
+
const full = (0, import_node_path16.join)(dir, entry.name);
|
|
2881
3426
|
if (entry.isDirectory()) {
|
|
2882
3427
|
scanDir(full);
|
|
2883
3428
|
continue;
|
|
2884
3429
|
}
|
|
2885
|
-
if (![".ts", ".tsx"].includes((0,
|
|
3430
|
+
if (![".ts", ".tsx"].includes((0, import_node_path16.extname)(entry.name))) continue;
|
|
2886
3431
|
try {
|
|
2887
|
-
const content = (0,
|
|
3432
|
+
const content = (0, import_node_fs14.readFileSync)(full, "utf-8");
|
|
2888
3433
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
2889
3434
|
if (matches) stats.annotations += matches.length;
|
|
2890
3435
|
} catch {
|
|
@@ -2971,6 +3516,14 @@ function handleMessage(msg) {
|
|
|
2971
3516
|
respond(id ?? null, handleDetectProjectStack());
|
|
2972
3517
|
return;
|
|
2973
3518
|
}
|
|
3519
|
+
if (toolName === "add_tag") {
|
|
3520
|
+
respond(id ?? null, handleAddTag(args));
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
if (toolName === "remove_tag") {
|
|
3524
|
+
respond(id ?? null, handleRemoveTag(args));
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
2974
3527
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
2975
3528
|
return;
|
|
2976
3529
|
}
|
|
@@ -3006,12 +3559,12 @@ function startGraphMcpServer() {
|
|
|
3006
3559
|
process.stderr.write(`[launchsecure-graph] MCP server started (cwd: ${process.cwd()})
|
|
3007
3560
|
`);
|
|
3008
3561
|
}
|
|
3009
|
-
var
|
|
3562
|
+
var import_node_fs14, import_node_path16, import_node_child_process2, import_node_os2, SERVER_INFO, TOOLS, COMPACT_SCHEMA, COMPACT_NODE_KNOWN_KEYS, EST_CHARS_PER_NODE_FULL, EST_CHARS_PER_NODE_MIN, EST_CHARS_PER_EDGE, NEIGHBORHOOD_BUDGET_CHARS, BATCH_BUDGET_CHARS;
|
|
3010
3563
|
var init_graph_mcp = __esm({
|
|
3011
3564
|
"src/server/graph-mcp.ts"() {
|
|
3012
3565
|
"use strict";
|
|
3013
|
-
|
|
3014
|
-
|
|
3566
|
+
import_node_fs14 = require("node:fs");
|
|
3567
|
+
import_node_path16 = require("node:path");
|
|
3015
3568
|
import_node_child_process2 = require("node:child_process");
|
|
3016
3569
|
import_node_os2 = require("node:os");
|
|
3017
3570
|
init_graph();
|
|
@@ -3041,7 +3594,7 @@ var init_graph_mcp = __esm({
|
|
|
3041
3594
|
},
|
|
3042
3595
|
{
|
|
3043
3596
|
name: "read_graph",
|
|
3044
|
-
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 (
|
|
3597
|
+
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.',
|
|
3045
3598
|
inputSchema: {
|
|
3046
3599
|
type: "object",
|
|
3047
3600
|
properties: {
|
|
@@ -3060,7 +3613,15 @@ var init_graph_mcp = __esm({
|
|
|
3060
3613
|
},
|
|
3061
3614
|
module: {
|
|
3062
3615
|
type: "string",
|
|
3063
|
-
description: '
|
|
3616
|
+
description: 'Filter by module tag (e.g. "auth", "admin", "settings"). Works across all layers.'
|
|
3617
|
+
},
|
|
3618
|
+
tag_key: {
|
|
3619
|
+
type: "string",
|
|
3620
|
+
description: "Filter by arbitrary tag key. Must be used with tag_value."
|
|
3621
|
+
},
|
|
3622
|
+
tag_value: {
|
|
3623
|
+
type: "string",
|
|
3624
|
+
description: "Filter by tag value for the given tag_key."
|
|
3064
3625
|
},
|
|
3065
3626
|
node_id: {
|
|
3066
3627
|
type: "string",
|
|
@@ -3184,6 +3745,46 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
3184
3745
|
type: "object",
|
|
3185
3746
|
properties: {}
|
|
3186
3747
|
}
|
|
3748
|
+
},
|
|
3749
|
+
{
|
|
3750
|
+
name: "add_tag",
|
|
3751
|
+
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.',
|
|
3752
|
+
inputSchema: {
|
|
3753
|
+
type: "object",
|
|
3754
|
+
properties: {
|
|
3755
|
+
node_id: {
|
|
3756
|
+
type: "string",
|
|
3757
|
+
description: 'The node id to tag (e.g. "app/(auth)/login/page.tsx").'
|
|
3758
|
+
},
|
|
3759
|
+
key: {
|
|
3760
|
+
type: "string",
|
|
3761
|
+
description: 'Tag key (e.g. "module", "owner", "refactor_later").'
|
|
3762
|
+
},
|
|
3763
|
+
value: {
|
|
3764
|
+
type: "string",
|
|
3765
|
+
description: 'Tag value (e.g. "auth", "alice", "true").'
|
|
3766
|
+
}
|
|
3767
|
+
},
|
|
3768
|
+
required: ["node_id", "key", "value"]
|
|
3769
|
+
}
|
|
3770
|
+
},
|
|
3771
|
+
{
|
|
3772
|
+
name: "remove_tag",
|
|
3773
|
+
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).",
|
|
3774
|
+
inputSchema: {
|
|
3775
|
+
type: "object",
|
|
3776
|
+
properties: {
|
|
3777
|
+
node_id: {
|
|
3778
|
+
type: "string",
|
|
3779
|
+
description: "The node id to remove the tag from."
|
|
3780
|
+
},
|
|
3781
|
+
key: {
|
|
3782
|
+
type: "string",
|
|
3783
|
+
description: "Tag key to remove."
|
|
3784
|
+
}
|
|
3785
|
+
},
|
|
3786
|
+
required: ["node_id", "key"]
|
|
3787
|
+
}
|
|
3187
3788
|
}
|
|
3188
3789
|
];
|
|
3189
3790
|
COMPACT_SCHEMA = {
|
|
@@ -3191,11 +3792,12 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
3191
3792
|
i: "id",
|
|
3192
3793
|
t: "type",
|
|
3193
3794
|
n: "name",
|
|
3194
|
-
m: "module",
|
|
3795
|
+
m: "module (from tags)",
|
|
3195
3796
|
r: "route",
|
|
3196
3797
|
mt: "methods",
|
|
3197
3798
|
x: "exports",
|
|
3198
|
-
c: "columns"
|
|
3799
|
+
c: "columns",
|
|
3800
|
+
tg: "tags"
|
|
3199
3801
|
},
|
|
3200
3802
|
edges: {
|
|
3201
3803
|
s: "source_node_index",
|
|
@@ -3213,7 +3815,8 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
3213
3815
|
"route",
|
|
3214
3816
|
"methods",
|
|
3215
3817
|
"exports",
|
|
3216
|
-
"columns"
|
|
3818
|
+
"columns",
|
|
3819
|
+
"tags"
|
|
3217
3820
|
]);
|
|
3218
3821
|
EST_CHARS_PER_NODE_FULL = {
|
|
3219
3822
|
ui: 300,
|
|
@@ -3237,10 +3840,10 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
3237
3840
|
|
|
3238
3841
|
// src/server/graph-mcp-entry.ts
|
|
3239
3842
|
var import_node_child_process3 = require("node:child_process");
|
|
3240
|
-
var
|
|
3241
|
-
var
|
|
3843
|
+
var import_node_fs15 = require("node:fs");
|
|
3844
|
+
var import_node_path17 = __toESM(require("node:path"));
|
|
3242
3845
|
var import_node_os3 = require("node:os");
|
|
3243
|
-
var
|
|
3846
|
+
var import_node_fs16 = require("node:fs");
|
|
3244
3847
|
init_lockfile();
|
|
3245
3848
|
function logStderr(msg) {
|
|
3246
3849
|
process.stderr.write(`[launch-chart] ${msg}
|
|
@@ -3254,11 +3857,11 @@ function maybeAutoServe() {
|
|
|
3254
3857
|
return;
|
|
3255
3858
|
}
|
|
3256
3859
|
try {
|
|
3257
|
-
const logDir =
|
|
3258
|
-
(0,
|
|
3259
|
-
const logPath =
|
|
3260
|
-
const out = (0,
|
|
3261
|
-
const err2 = (0,
|
|
3860
|
+
const logDir = import_node_path17.default.join((0, import_node_os3.homedir)(), ".launchsecure");
|
|
3861
|
+
(0, import_node_fs16.mkdirSync)(logDir, { recursive: true });
|
|
3862
|
+
const logPath = import_node_path17.default.join(logDir, "launch-chart.log");
|
|
3863
|
+
const out = (0, import_node_fs15.openSync)(logPath, "a");
|
|
3864
|
+
const err2 = (0, import_node_fs15.openSync)(logPath, "a");
|
|
3262
3865
|
const entryPath = process.argv[1];
|
|
3263
3866
|
const child = (0, import_node_child_process3.spawn)(process.execPath, [entryPath, "serve"], {
|
|
3264
3867
|
detached: true,
|