@monoes/cli 1.2.1 → 1.2.3

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.
@@ -44,7 +44,7 @@ const os = require('os');
44
44
 
45
45
  // Configuration
46
46
  const CONFIG = {
47
- maxAgents: ${{ maxAgents }},
47
+ maxAgents: ${maxAgents},
48
48
  };
49
49
 
50
50
  const CWD = process.cwd();
@@ -138,10 +138,16 @@ export const graphifyBuildTool = {
138
138
  return {
139
139
  success: true,
140
140
  graphPath: result.graphPath,
141
+ reportPath: result.reportPath,
141
142
  filesProcessed: result.filesProcessed,
143
+ fromCache: result.fromCache,
142
144
  nodes: result.analysis.stats.nodes,
143
145
  edges: result.analysis.stats.edges,
144
- message: `Knowledge graph built at ${result.graphPath}`,
146
+ communities: result.analysis.stats.communities,
147
+ graphQuality: result.graphQuality,
148
+ experimentStatus: result.experimentStatus,
149
+ corpusWarnings: result.corpusWarnings,
150
+ message: `[${result.experimentStatus}] Knowledge graph built — quality=${result.graphQuality.toFixed(4)} (${result.analysis.stats.nodes}n/${result.analysis.stats.edges}e/${result.analysis.stats.communities}c)`,
145
151
  };
146
152
  }
147
153
  catch (err) {
@@ -824,6 +830,320 @@ export const graphifyVisualizeTool = {
824
830
  }
825
831
  },
826
832
  };
833
+ // ── Watch PID helpers ─────────────────────────────────────────────────────────
834
+ function getPidPath(cwd) {
835
+ return resolve(join(cwd, '.monobrain', 'graph', 'watch.pid'));
836
+ }
837
+ function readWatchPid(cwd) {
838
+ const pidPath = getPidPath(cwd);
839
+ if (!existsSync(pidPath))
840
+ return null;
841
+ try {
842
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
843
+ return isNaN(pid) ? null : pid;
844
+ }
845
+ catch {
846
+ return null;
847
+ }
848
+ }
849
+ function isProcessRunning(pid) {
850
+ try {
851
+ process.kill(pid, 0);
852
+ return true;
853
+ }
854
+ catch {
855
+ return false;
856
+ }
857
+ }
858
+ // ── Watch tools ───────────────────────────────────────────────────────────────
859
+ export const graphifyWatchTool = {
860
+ name: 'graphify_watch',
861
+ description: 'Start a background file watcher that automatically rebuilds the knowledge graph ' +
862
+ '(graph.json + graph.html) whenever source files change. Uses a 2-second debounce ' +
863
+ 'so rapid saves do not trigger repeated rebuilds. The watcher runs as a detached ' +
864
+ 'background process — call graphify_watch_stop to stop it.',
865
+ category: 'graphify',
866
+ tags: ['knowledge-graph', 'watch', 'auto-rebuild'],
867
+ inputSchema: {
868
+ type: 'object',
869
+ properties: {
870
+ path: {
871
+ type: 'string',
872
+ description: 'Project root to watch (defaults to current project root)',
873
+ },
874
+ debounce: {
875
+ type: 'number',
876
+ description: 'Debounce delay in milliseconds (default 2000)',
877
+ default: 2000,
878
+ },
879
+ extensions: {
880
+ type: 'string',
881
+ description: 'Comma-separated list of file extensions to watch (default: ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c)',
882
+ default: 'ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c',
883
+ },
884
+ },
885
+ },
886
+ handler: async (params) => {
887
+ const cwd = getProjectCwd();
888
+ const targetPath = params.path || cwd;
889
+ const debounceMs = params.debounce || 2000;
890
+ const extensions = ((params.extensions) || 'ts,js,tsx,jsx,py,go,rs,java,cs,rb,cpp,c')
891
+ .split(',')
892
+ .map((e) => e.trim())
893
+ .filter(Boolean);
894
+ // Check if already running
895
+ const existingPid = readWatchPid(targetPath);
896
+ if (existingPid !== null && isProcessRunning(existingPid)) {
897
+ return {
898
+ success: false,
899
+ message: `Watcher already running (PID ${existingPid})`,
900
+ hint: 'Call graphify_watch_stop first to restart.',
901
+ };
902
+ }
903
+ const pidPath = getPidPath(targetPath);
904
+ const outputDir = resolve(join(targetPath, '.monobrain', 'graph'));
905
+ const watcherScript = `
906
+ import { watch } from 'chokidar';
907
+ import { writeFileSync, mkdirSync } from 'fs';
908
+
909
+ const TARGET = ${JSON.stringify(targetPath)};
910
+ const OUTPUT_DIR = ${JSON.stringify(outputDir)};
911
+ const PID_PATH = ${JSON.stringify(pidPath)};
912
+ const DEBOUNCE_MS = ${debounceMs};
913
+ const EXTS = new Set(${JSON.stringify(extensions)});
914
+ const IGNORE = [/node_modules/, /\\.git/, /\\.monobrain/, /dist[\\/]/, /\\.next/, /\\.turbo/, /coverage/];
915
+
916
+ mkdirSync(OUTPUT_DIR, { recursive: true });
917
+ writeFileSync(PID_PATH, String(process.pid));
918
+ console.log('[graphify-watch] PID', process.pid, '— watching', TARGET);
919
+
920
+ let timer = null;
921
+
922
+ async function rebuild() {
923
+ const start = Date.now();
924
+ console.log('[graphify-watch] Change detected — rebuilding graph…');
925
+ try {
926
+ const { buildGraph } = await import('@monoes/graph');
927
+ const { exportHTML } = await import('@monoes/graph');
928
+ const { graph: serialized } = await buildGraph(TARGET, { outputDir: OUTPUT_DIR });
929
+ exportHTML(serialized, OUTPUT_DIR);
930
+ console.log('[graphify-watch] Done in', Date.now() - start, 'ms');
931
+ } catch (err) {
932
+ console.error('[graphify-watch] Build error:', err.message ?? err);
933
+ }
934
+ }
935
+
936
+ const watcher = watch(TARGET, {
937
+ ignored: (p) => IGNORE.some((r) => r.test(p)),
938
+ persistent: true,
939
+ ignoreInitial: true,
940
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
941
+ });
942
+
943
+ watcher.on('all', (event, filePath) => {
944
+ const ext = filePath.split('.').pop() ?? '';
945
+ if (!EXTS.has(ext)) return;
946
+ if (timer) clearTimeout(timer);
947
+ timer = setTimeout(rebuild, DEBOUNCE_MS);
948
+ });
949
+
950
+ watcher.on('error', (err) => console.error('[graphify-watch] Watcher error:', err));
951
+ process.on('SIGINT', () => process.exit(0));
952
+ process.on('SIGTERM', () => process.exit(0));
953
+ `;
954
+ try {
955
+ const { spawn } = await import('child_process');
956
+ const { writeFileSync, mkdirSync } = await import('fs');
957
+ mkdirSync(outputDir, { recursive: true });
958
+ const scriptPath = resolve(join(outputDir, '_watcher.mjs'));
959
+ writeFileSync(scriptPath, watcherScript);
960
+ const child = spawn(process.execPath, [scriptPath], {
961
+ detached: true,
962
+ stdio: 'ignore',
963
+ env: { ...process.env },
964
+ });
965
+ child.unref();
966
+ await new Promise((r) => setTimeout(r, 500));
967
+ const pid = readWatchPid(targetPath) ?? child.pid;
968
+ return {
969
+ success: true,
970
+ pid,
971
+ watching: targetPath,
972
+ debounceMs,
973
+ extensions,
974
+ message: `Graph watcher started (PID ${pid}). graph.json + graph.html will rebuild on file changes.`,
975
+ hint: 'Call graphify_watch_stop to stop.',
976
+ };
977
+ }
978
+ catch (err) {
979
+ return { error: true, message: String(err) };
980
+ }
981
+ },
982
+ };
983
+ export const graphifyWatchStopTool = {
984
+ name: 'graphify_watch_stop',
985
+ description: 'Stop the background file watcher started by graphify_watch.',
986
+ category: 'graphify',
987
+ tags: ['knowledge-graph', 'watch'],
988
+ inputSchema: {
989
+ type: 'object',
990
+ properties: {
991
+ path: {
992
+ type: 'string',
993
+ description: 'Project root (defaults to current project root)',
994
+ },
995
+ },
996
+ },
997
+ handler: async (params) => {
998
+ const cwd = getProjectCwd();
999
+ const targetPath = params.path || cwd;
1000
+ const pid = readWatchPid(targetPath);
1001
+ if (pid === null) {
1002
+ return { success: false, message: 'No watcher PID found — watcher may not be running.' };
1003
+ }
1004
+ if (!isProcessRunning(pid)) {
1005
+ try {
1006
+ const { unlinkSync } = await import('fs');
1007
+ unlinkSync(getPidPath(targetPath));
1008
+ }
1009
+ catch { /* ignore */ }
1010
+ return { success: false, message: `Process ${pid} is not running (stale PID cleaned up).` };
1011
+ }
1012
+ try {
1013
+ process.kill(pid, 'SIGTERM');
1014
+ try {
1015
+ const { unlinkSync } = await import('fs');
1016
+ unlinkSync(getPidPath(targetPath));
1017
+ }
1018
+ catch { /* ignore */ }
1019
+ return { success: true, message: `Watcher (PID ${pid}) stopped.` };
1020
+ }
1021
+ catch (err) {
1022
+ return { error: true, message: `Failed to stop PID ${pid}: ${String(err)}` };
1023
+ }
1024
+ },
1025
+ };
1026
+ /**
1027
+ * Read the GRAPH_REPORT.md generated during the last build.
1028
+ */
1029
+ export const graphifyReportTool = {
1030
+ name: 'graphify_report',
1031
+ description: 'Read the GRAPH_REPORT.md generated by the last graphify_build. ' +
1032
+ 'Returns the full markdown audit trail: corpus check, god nodes, surprising connections, ' +
1033
+ 'communities, ambiguous edges, knowledge gaps, and suggested questions.',
1034
+ category: 'graphify',
1035
+ tags: ['knowledge-graph', 'report', 'audit'],
1036
+ inputSchema: {
1037
+ type: 'object',
1038
+ properties: {
1039
+ path: {
1040
+ type: 'string',
1041
+ description: 'Project path (defaults to current project root)',
1042
+ },
1043
+ },
1044
+ },
1045
+ handler: async (params) => {
1046
+ const cwd = getProjectCwd();
1047
+ const targetPath = params.path || cwd;
1048
+ const reportPath = resolve(join(targetPath, '.monobrain', 'graph', 'GRAPH_REPORT.md'));
1049
+ if (!existsSync(reportPath)) {
1050
+ return {
1051
+ error: true,
1052
+ message: 'No report found. Run graphify_build first.',
1053
+ hint: `Expected: ${reportPath}`,
1054
+ };
1055
+ }
1056
+ try {
1057
+ const content = readFileSync(reportPath, 'utf-8');
1058
+ return { success: true, reportPath, content };
1059
+ }
1060
+ catch (err) {
1061
+ return { error: true, message: String(err) };
1062
+ }
1063
+ },
1064
+ };
1065
+ /**
1066
+ * Suggest questions the knowledge graph can answer about the codebase.
1067
+ */
1068
+ export const graphifySuggestTool = {
1069
+ name: 'graphify_suggest',
1070
+ description: 'Return a prioritised list of questions this knowledge graph is uniquely ' +
1071
+ 'positioned to answer — e.g. bridge nodes between subsystems, god nodes worth investigating, ' +
1072
+ 'isolated components with no connections, and low-cohesion communities. ' +
1073
+ 'Use after graphify_build to guide architectural analysis.',
1074
+ category: 'graphify',
1075
+ tags: ['knowledge-graph', 'questions', 'analysis'],
1076
+ inputSchema: {
1077
+ type: 'object',
1078
+ properties: {
1079
+ path: {
1080
+ type: 'string',
1081
+ description: 'Project path (defaults to current project root)',
1082
+ },
1083
+ },
1084
+ },
1085
+ handler: async (params) => {
1086
+ const cwd = getProjectCwd();
1087
+ const targetPath = params.path || cwd;
1088
+ if (!graphExists(targetPath)) {
1089
+ return { error: true, message: 'No graph found. Run graphify_build first.' };
1090
+ }
1091
+ try {
1092
+ const { loadGraph, suggestQuestions, buildAnalysis, buildGraphologyGraph } = await import('@monoes/graph');
1093
+ const graphPath = getGraphPath(targetPath);
1094
+ const outputDir = resolve(join(targetPath, '.monobrain', 'graph'));
1095
+ const raw = loadGraph(graphPath);
1096
+ const graph = buildGraphologyGraph({ nodes: raw.nodes, edges: raw.edges, hyperedges: [], filesProcessed: 0, fromCache: 0, errors: [] });
1097
+ const analysis = buildAnalysis(graph, outputDir);
1098
+ const questions = suggestQuestions(graph, analysis.communities);
1099
+ return { success: true, questions, total: questions.length };
1100
+ }
1101
+ catch (err) {
1102
+ return { error: true, message: String(err) };
1103
+ }
1104
+ },
1105
+ };
1106
+ /**
1107
+ * Run a corpus health check on the project files.
1108
+ */
1109
+ export const graphifyHealthTool = {
1110
+ name: 'graphify_health',
1111
+ description: 'Run a corpus health check on the project directory — warns when the codebase ' +
1112
+ 'is too small for graph analysis, too large to be useful, security-sensitive files were ' +
1113
+ 'found, or when the doc-to-code ratio is skewed. Run before graphify_build to catch issues early.',
1114
+ category: 'graphify',
1115
+ tags: ['knowledge-graph', 'health', 'corpus'],
1116
+ inputSchema: {
1117
+ type: 'object',
1118
+ properties: {
1119
+ path: {
1120
+ type: 'string',
1121
+ description: 'Project path (defaults to current project root)',
1122
+ },
1123
+ },
1124
+ },
1125
+ handler: async (params) => {
1126
+ const cwd = getProjectCwd();
1127
+ const targetPath = params.path || cwd;
1128
+ try {
1129
+ const { collectFiles, corpusHealth } = await import('@monoes/graph');
1130
+ const files = collectFiles(targetPath);
1131
+ const warnings = corpusHealth(files);
1132
+ return {
1133
+ success: true,
1134
+ totalFiles: files.length,
1135
+ warnings,
1136
+ healthy: warnings.length === 0,
1137
+ message: warnings.length === 0
1138
+ ? `Corpus looks healthy (${files.length} files).`
1139
+ : `${warnings.length} warning(s) found.`,
1140
+ };
1141
+ }
1142
+ catch (err) {
1143
+ return { error: true, message: String(err) };
1144
+ }
1145
+ },
1146
+ };
827
1147
  // ── Exports ───────────────────────────────────────────────────────────────────
828
1148
  export const graphifyTools = [
829
1149
  graphifyBuildTool,
@@ -835,6 +1155,11 @@ export const graphifyTools = [
835
1155
  graphifyStatsTool,
836
1156
  graphifySurprisesTool,
837
1157
  graphifyVisualizeTool,
1158
+ graphifyWatchTool,
1159
+ graphifyWatchStopTool,
1160
+ graphifyReportTool,
1161
+ graphifySuggestTool,
1162
+ graphifyHealthTool,
838
1163
  ];
839
1164
  export default graphifyTools;
840
1165
  //# sourceMappingURL=graphify-tools.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "description": "Monobrain CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",