noteconnection 1.6.3 → 1.6.5

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/README.md CHANGED
@@ -319,6 +319,13 @@ For developers building from source, NoteConnection offers two build modes:
319
319
  - Run mapping validation: `npm run docs:diataxis:check`.
320
320
  - Run local docs site preview: `npm run docs:site:serve`.
321
321
  - Build static docs site: `npm run docs:site:build`.
322
+ - EdgeOne Docs Portal (MkDocs deploy): `https://noteconnection-docs.edgeone.run`
323
+ - If you see `401 Authorization Required`, open the latest signed URL printed by deployment as `EDGEONE_DEPLOY_URL`, or disable preset-domain protection in EdgeOne Pages settings.
324
+ - Public mirror (fallback): `https://jacobinwwey.github.io/NoteConnection/`.
325
+ - Recommended lookup entry points:
326
+ - Users: `/diataxis/zh/tutorials/first-run/` or `/diataxis/en/tutorials/first-run/`
327
+ - Developers: `/diataxis/en/reference/interfaces-and-runtime/` and `/diataxis/en/reference/release-and-governance/`
328
+ - EdgeOne publish command (MkDocs site): `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`.
322
329
  - CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
323
330
 
324
331
  ## 🛠️ Hardware & Driver Requirements (AMDGPU)
@@ -334,6 +341,13 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
334
341
 
335
342
  ## 📅 Changelog
336
343
 
344
+ ### v1.6.5 - Documentation Portal Update (2026-03-26)
345
+
346
+ - Published MkDocs documentation to EdgeOne Pages project `noteconnection-docs`.
347
+ - Added bilingual README guidance for docs lookup paths (user/tutorial and developer/reference entry points).
348
+ - Standardized docs publish command for maintainers:
349
+ - `npm run docs:site:build`
350
+ - `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
337
351
  ### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
338
352
 
339
353
  - **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
@@ -1196,6 +1210,13 @@ sync_language = true
1196
1210
  - 映射一致性校验:`npm run docs:diataxis:check`。
1197
1211
  - 本地预览文档站点:`npm run docs:site:serve`。
1198
1212
  - 构建静态文档站点:`npm run docs:site:build`。
1213
+ - EdgeOne 文档入口(MkDocs 发布):`https://noteconnection-docs.edgeone.run`
1214
+ - 若出现 `401 Authorization Required`,请使用最近一次部署输出中的 `EDGEONE_DEPLOY_URL` 签名链接,或在 EdgeOne Pages 项目中关闭预设域名访问保护。
1215
+ - 公共镜像(兜底):`https://jacobinwwey.github.io/NoteConnection/`。
1216
+ - 推荐查询入口:
1217
+ - 用户文档:`/diataxis/zh/tutorials/first-run/` 或 `/diataxis/en/tutorials/first-run/`
1218
+ - 开发文档:`/diataxis/en/reference/interfaces-and-runtime/` 与 `/diataxis/en/reference/release-and-governance/`
1219
+ - EdgeOne 发布命令(MkDocs 产物):`edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`。
1199
1220
  - CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
1200
1221
 
1201
1222
  ---
@@ -1204,6 +1225,13 @@ sync_language = true
1204
1225
 
1205
1226
  ## 更新日志 (Changelog)
1206
1227
 
1228
+ ### v1.6.5 - �ĵ��Ż����£�2026-03-26��
1229
+
1230
+ - �ѽ� MkDocs �ĵ������� EdgeOne Pages ��Ŀ `noteconnection-docs`��
1231
+ - ���� README ������Ӣ�IJ�ѯָ���������û��̳�����뿪���ο���ڣ���
1232
+ - ά���߷�������ͳһΪ��
1233
+ - `npm run docs:site:build`
1234
+ - `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
1207
1235
  ### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
1208
1236
 
1209
1237
  - **Tag 对比快照(`v1.3.0..v1.6.0`)**:
@@ -38,6 +38,11 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const os = __importStar(require("os"));
40
40
  class CrashLogger {
41
+ static isIgnorableProcessWriteError(error) {
42
+ const code = typeof error === 'object' && error ? String(error.code || '') : '';
43
+ const syscall = typeof error === 'object' && error ? String(error.syscall || '') : '';
44
+ return code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED' || (code === 'EOF' && syscall === 'write');
45
+ }
41
46
  static log(error, context = 'General') {
42
47
  const timestamp = new Date().toISOString();
43
48
  const errorMessage = error instanceof Error ? error.stack || error.message : String(error);
@@ -55,12 +60,22 @@ class CrashLogger {
55
60
  }
56
61
  }
57
62
  static initGlobalHandlers() {
63
+ if (this.globalHandlersInstalled) {
64
+ return;
65
+ }
66
+ this.globalHandlersInstalled = true;
58
67
  process.on('uncaughtException', (error) => {
68
+ if (CrashLogger.isIgnorableProcessWriteError(error)) {
69
+ return;
70
+ }
59
71
  console.error('Uncaught Exception:', error);
60
72
  CrashLogger.log(error, 'UncaughtException');
61
73
  process.exit(1); // Exit is mandatory for uncaught exceptions to avoid undefined state
62
74
  });
63
75
  process.on('unhandledRejection', (reason, promise) => {
76
+ if (CrashLogger.isIgnorableProcessWriteError(reason)) {
77
+ return;
78
+ }
64
79
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
65
80
  CrashLogger.log(reason, 'UnhandledRejection');
66
81
  });
@@ -68,3 +83,4 @@ class CrashLogger {
68
83
  }
69
84
  exports.CrashLogger = CrashLogger;
70
85
  CrashLogger.logFilePath = path.join(process.cwd(), 'crash.log');
86
+ CrashLogger.globalHandlersInstalled = false;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CrashLogger_1 = require("./backend/utils/CrashLogger");
4
+ describe('CrashLogger broken pipe policy', () => {
5
+ test('treats write-side EPIPE and destroyed stream errors as ignorable process shutdown noise', () => {
6
+ expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('write EPIPE'), { code: 'EPIPE', syscall: 'write' }))).toBe(true);
7
+ expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('stream destroyed'), { code: 'ERR_STREAM_DESTROYED' }))).toBe(true);
8
+ });
9
+ test('does not suppress unrelated runtime exceptions', () => {
10
+ expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('permission denied'), { code: 'EACCES', syscall: 'open' }))).toBe(false);
11
+ expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(new Error('boom'))).toBe(false);
12
+ });
13
+ });
@@ -319,6 +319,13 @@ For developers building from source, NoteConnection offers two build modes:
319
319
  - Run mapping validation: `npm run docs:diataxis:check`.
320
320
  - Run local docs site preview: `npm run docs:site:serve`.
321
321
  - Build static docs site: `npm run docs:site:build`.
322
+ - EdgeOne Docs Portal (MkDocs deploy): `https://noteconnection-docs.edgeone.run`
323
+ - If you see `401 Authorization Required`, open the latest signed URL printed by deployment as `EDGEONE_DEPLOY_URL`, or disable preset-domain protection in EdgeOne Pages settings.
324
+ - Public mirror (fallback): `https://jacobinwwey.github.io/NoteConnection/`.
325
+ - Recommended lookup entry points:
326
+ - Users: `/diataxis/zh/tutorials/first-run/` or `/diataxis/en/tutorials/first-run/`
327
+ - Developers: `/diataxis/en/reference/interfaces-and-runtime/` and `/diataxis/en/reference/release-and-governance/`
328
+ - EdgeOne publish command (MkDocs site): `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`.
322
329
  - CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
323
330
 
324
331
  ## 🛠️ Hardware & Driver Requirements (AMDGPU)
@@ -334,6 +341,13 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
334
341
 
335
342
  ## 📅 Changelog
336
343
 
344
+ ### v1.6.5 - Documentation Portal Update (2026-03-26)
345
+
346
+ - Published MkDocs documentation to EdgeOne Pages project `noteconnection-docs`.
347
+ - Added bilingual README guidance for docs lookup paths (user/tutorial and developer/reference entry points).
348
+ - Standardized docs publish command for maintainers:
349
+ - `npm run docs:site:build`
350
+ - `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
337
351
  ### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
338
352
 
339
353
  - **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
@@ -1196,6 +1210,13 @@ sync_language = true
1196
1210
  - 映射一致性校验:`npm run docs:diataxis:check`。
1197
1211
  - 本地预览文档站点:`npm run docs:site:serve`。
1198
1212
  - 构建静态文档站点:`npm run docs:site:build`。
1213
+ - EdgeOne 文档入口(MkDocs 发布):`https://noteconnection-docs.edgeone.run`
1214
+ - 若出现 `401 Authorization Required`,请使用最近一次部署输出中的 `EDGEONE_DEPLOY_URL` 签名链接,或在 EdgeOne Pages 项目中关闭预设域名访问保护。
1215
+ - 公共镜像(兜底):`https://jacobinwwey.github.io/NoteConnection/`。
1216
+ - 推荐查询入口:
1217
+ - 用户文档:`/diataxis/zh/tutorials/first-run/` 或 `/diataxis/en/tutorials/first-run/`
1218
+ - 开发文档:`/diataxis/en/reference/interfaces-and-runtime/` 与 `/diataxis/en/reference/release-and-governance/`
1219
+ - EdgeOne 发布命令(MkDocs 产物):`edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`。
1199
1220
  - CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
1200
1221
 
1201
1222
  ---
@@ -1204,6 +1225,13 @@ sync_language = true
1204
1225
 
1205
1226
  ## 更新日志 (Changelog)
1206
1227
 
1228
+ ### v1.6.5 - �ĵ��Ż����£�2026-03-26��
1229
+
1230
+ - �ѽ� MkDocs �ĵ������� EdgeOne Pages ��Ŀ `noteconnection-docs`��
1231
+ - ���� README ������Ӣ�IJ�ѯָ���������û��̳�����뿪���ο���ڣ���
1232
+ - ά���߷�������ͳһΪ��
1233
+ - `npm run docs:site:build`
1234
+ - `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
1207
1235
  ### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
1208
1236
 
1209
1237
  - **Tag 对比快照(`v1.3.0..v1.6.0`)**:
@@ -57,8 +57,10 @@ describe('NoteMD API contract wiring', () => {
57
57
  expect(serverSource).toContain(endpoint);
58
58
  });
59
59
  });
60
- test('server persists NoteMD settings in runtime data directory', () => {
61
- expect(serverSource).toContain('NOTEMD_CONFIG_FILE_NAME');
60
+ test('server persists NoteMD settings through app_config.toml helpers', () => {
61
+ expect(serverSource).toContain("from './notemd/AppConfigToml'");
62
+ expect(serverSource).toContain('loadAppConfigToml');
63
+ expect(serverSource).toContain('saveAppConfigToml');
62
64
  expect(serverSource).toContain('persistNotemdSettings');
63
65
  expect(serverSource).toContain('loadNotemdSettings');
64
66
  });
@@ -50,11 +50,13 @@ describe('pkg snapshot safety contract', () => {
50
50
  expect(source).not.toContain('eval(');
51
51
  });
52
52
  });
53
- test('reader and source manager keep static import fallback paths for packaging', () => {
53
+ test('reader and source manager keep packaging-safe Mermaid/runtime fallback paths', () => {
54
54
  const readerRenderer = fs.readFileSync(runtimeEntryFiles[0], 'utf8');
55
55
  const sourceManager = fs.readFileSync(runtimeEntryFiles[2], 'utf8');
56
56
  expect(readerRenderer).toContain('loadMermaidModule');
57
- expect(readerRenderer).toContain("import('mermaid')");
57
+ expect(readerRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
58
+ expect(readerRenderer).toContain("createElement('script')");
59
+ expect(readerRenderer).not.toContain("require('mermaid')");
58
60
  expect(sourceManager).toContain('parseGraphDataPayload');
59
61
  expect(sourceManager).toContain('Fallback for assignment-based payloads without using runtime eval.');
60
62
  });
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var _a;
36
3
  Object.defineProperty(exports, "__esModule", { value: true });
37
4
  exports.renderMathSvg = renderMathSvg;
@@ -51,7 +18,6 @@ const { liteAdaptor } = require('mathjax-full/js/adaptors/liteAdaptor.js');
51
18
  const { HandlerList } = require('mathjax-full/js/core/HandlerList.js');
52
19
  const { HTMLHandler } = require('mathjax-full/js/handlers/html/HTMLHandler.js');
53
20
  const { initWasm, Resvg } = require('@resvg/resvg-wasm');
54
- const importMermaidModule = () => Promise.resolve().then(() => __importStar(require('mermaid')));
55
21
  const MATH_TEXT_COLOR = '#eef4ff';
56
22
  const MERMAID_BACKGROUND = 'transparent';
57
23
  const MERMAID_PADDING = 28;
@@ -111,7 +77,6 @@ const mathDocument = mathHandlers.document('', {
111
77
  OutputJax: svgOutput,
112
78
  });
113
79
  let mermaidEnvironmentPromise = null;
114
- let mermaidModulePromise = null;
115
80
  let mermaidRenderQueue = Promise.resolve();
116
81
  let mermaidRenderCounter = 0;
117
82
  let resvgInitPromise = null;
@@ -390,14 +355,9 @@ async function createMermaidEnvironment(theme) {
390
355
  };
391
356
  }
392
357
  async function loadMermaidModule(window) {
393
- if (!process.pkg) {
394
- if (!mermaidModulePromise) {
395
- mermaidModulePromise = (async () => {
396
- const mermaidModule = await importMermaidModule();
397
- return mermaidModule.default ?? mermaidModule;
398
- })();
399
- }
400
- return mermaidModulePromise;
358
+ const existingMermaid = window.mermaid;
359
+ if (existingMermaid) {
360
+ return existingMermaid;
401
361
  }
402
362
  const script = window.document.createElement('script');
403
363
  script.textContent = Buffer.from(mermaid_runtime_1.MERMAID_BROWSER_BUNDLE_BASE64, 'base64').toString('utf8');
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = __importDefault(require("node:fs"));
6
7
  const node_child_process_1 = require("node:child_process");
7
8
  const node_path_1 = __importDefault(require("node:path"));
8
9
  const jsdom_1 = require("jsdom");
@@ -82,6 +83,12 @@ describe('reader_renderer', () => {
82
83
  stdio: 'inherit',
83
84
  });
84
85
  }, 120000);
86
+ it('keeps the compiled Mermaid loader on the embedded browser runtime path instead of requiring the ESM package directly', () => {
87
+ const compiledRenderer = node_fs_1.default.readFileSync(distRendererPath, 'utf8');
88
+ expect(compiledRenderer).not.toContain("require('mermaid')");
89
+ expect(compiledRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
90
+ expect(compiledRenderer).toContain("createElement('script')");
91
+ });
85
92
  it('renders display math to svg', () => {
86
93
  const svg = runRenderer('renderMathSvg', 'E = mc^2', { displayMode: true });
87
94
  expect(svg.startsWith('<svg')).toBe(true);
@@ -39,6 +39,7 @@ exports.executeNotemdCliCommand = executeNotemdCliCommand;
39
39
  const http = __importStar(require("http"));
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
+ const net = __importStar(require("net"));
42
43
  const readline = __importStar(require("readline"));
43
44
  const events_1 = require("events");
44
45
  const index_1 = require("./index");
@@ -52,6 +53,53 @@ const reader_renderer_1 = require("./reader_renderer");
52
53
  const native_clipboard_1 = require("./native_clipboard");
53
54
  const notemd_1 = require("./notemd");
54
55
  const AppConfigToml_1 = require("./notemd/AppConfigToml");
56
+ function installBrokenPipeGuard(stream) {
57
+ if (!stream || stream.__noteConnectionBrokenPipeGuardInstalled) {
58
+ return;
59
+ }
60
+ stream.__noteConnectionBrokenPipeGuardInstalled = true;
61
+ stream.on('error', (error) => {
62
+ if (error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') {
63
+ return;
64
+ }
65
+ throw error;
66
+ });
67
+ }
68
+ installBrokenPipeGuard(process.stdout);
69
+ installBrokenPipeGuard(process.stderr);
70
+ const IS_JEST_RUNTIME = String(process.env.JEST_WORKER_ID || '').trim().length > 0;
71
+ function logDiagnostic(...args) {
72
+ if (IS_JEST_RUNTIME) {
73
+ return;
74
+ }
75
+ console.log(...args);
76
+ }
77
+ function warnDiagnostic(...args) {
78
+ if (IS_JEST_RUNTIME) {
79
+ return;
80
+ }
81
+ console.warn(...args);
82
+ }
83
+ function installJestSocketBrokenPipeGuard() {
84
+ if (!IS_JEST_RUNTIME) {
85
+ return;
86
+ }
87
+ const socketPrototype = net.Socket.prototype;
88
+ if (socketPrototype.__noteConnectionBrokenPipeGuardInstalled) {
89
+ return;
90
+ }
91
+ const originalEmit = socketPrototype.emit;
92
+ socketPrototype.__noteConnectionBrokenPipeGuardInstalled = true;
93
+ socketPrototype.emit = function (event, ...args) {
94
+ if (event === 'error'
95
+ && CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(args[0])
96
+ && (this.destroyed || this.writable === false)) {
97
+ return true;
98
+ }
99
+ return originalEmit.call(this, event, ...args);
100
+ };
101
+ }
102
+ installJestSocketBrokenPipeGuard();
55
103
  // Initialize Global Crash Handlers
56
104
  CrashLogger_1.CrashLogger.initGlobalHandlers();
57
105
  const LOOPBACK_HOST = '127.0.0.1';
@@ -124,16 +172,16 @@ function resolveBoundedMegabytesFromEnv(options) {
124
172
  }
125
173
  const parsedValue = Number(rawValue);
126
174
  if (!Number.isFinite(parsedValue)) {
127
- console.warn(`[Config] ${envKey} is not a number ("${rawValue}"). Using default ${defaultMb} MiB.`);
175
+ warnDiagnostic(`[Config] ${envKey} is not a number ("${rawValue}"). Using default ${defaultMb} MiB.`);
128
176
  return defaultMb;
129
177
  }
130
178
  const normalizedMb = Math.floor(parsedValue);
131
179
  if (normalizedMb < minMb) {
132
- console.warn(`[Config] ${envKey}=${rawValue} is below minimum ${minMb} MiB. Clamping to ${minMb} MiB.`);
180
+ warnDiagnostic(`[Config] ${envKey}=${rawValue} is below minimum ${minMb} MiB. Clamping to ${minMb} MiB.`);
133
181
  return minMb;
134
182
  }
135
183
  if (normalizedMb > maxMb) {
136
- console.warn(`[Config] ${envKey}=${rawValue} exceeds maximum ${maxMb} MiB. Clamping to ${maxMb} MiB.`);
184
+ warnDiagnostic(`[Config] ${envKey}=${rawValue} exceeds maximum ${maxMb} MiB. Clamping to ${maxMb} MiB.`);
137
185
  return maxMb;
138
186
  }
139
187
  return normalizedMb;
@@ -385,7 +433,7 @@ async function safeUnlink(filePath) {
385
433
  }
386
434
  catch (error) {
387
435
  if (!isFsNotFoundError(error)) {
388
- console.warn('[Sidecar] Failed to clean up temporary request body file:', error);
436
+ warnDiagnostic('[Sidecar] Failed to clean up temporary request body file:', error);
389
437
  }
390
438
  }
391
439
  }
@@ -710,7 +758,7 @@ async function loadNotemdSettings() {
710
758
  cachedNotemdSettings = normalizeNotemdSettings(parsedSettings);
711
759
  }
712
760
  catch (error) {
713
- console.warn('[NoteMD] Failed to read settings from TOML, using defaults:', error);
761
+ warnDiagnostic('[NoteMD] Failed to read settings from TOML, using defaults:', error);
714
762
  cachedNotemdSettings = normalizeNotemdSettings(notemd_1.DEFAULT_SETTINGS);
715
763
  }
716
764
  return cloneNotemdSettings(cachedNotemdSettings);
@@ -894,7 +942,7 @@ async function writeSidecarRuntimeManifest(finalPort) {
894
942
  }, null, 2), 'utf8');
895
943
  }
896
944
  catch (error) {
897
- console.warn('[Sidecar] Failed to write runtime manifest:', error);
945
+ warnDiagnostic('[Sidecar] Failed to write runtime manifest:', error);
898
946
  }
899
947
  }
900
948
  async function renderMermaidWithPreference(source, options) {
@@ -927,7 +975,7 @@ async function renderMermaidWithPreference(source, options) {
927
975
  if (options.rendererPreference === 'frontend') {
928
976
  throw error;
929
977
  }
930
- console.warn('[Reader] Frontend Mermaid render unavailable, falling back to local resvg:', error);
978
+ warnDiagnostic('[Reader] Frontend Mermaid render unavailable, falling back to local resvg:', error);
931
979
  }
932
980
  }
933
981
  const localRendered = await (0, reader_renderer_1.renderMermaidPng)(source, {
@@ -1162,7 +1210,7 @@ if (process.env.NOTE_CONNECTION_GPU === 'true' || process.env.NOTE_CONNECTION_GP
1162
1210
  cliOptions.enableGPULayout = true;
1163
1211
  }
1164
1212
  if (process.env.npm_config_static === 'true' || process.env.npm_config_static === '') {
1165
- console.log('[CLI] Static mode requested (via env).');
1213
+ logDiagnostic('[CLI] Static mode requested (via env).');
1166
1214
  }
1167
1215
  if (process.env.npm_config_workers) {
1168
1216
  cliOptions.maxWorkers = parseInt(process.env.npm_config_workers);
@@ -1183,7 +1231,7 @@ for (let i = 0; i < args.length; i++) {
1183
1231
  cliOptions.enableGPULayout = false;
1184
1232
  }
1185
1233
  else if (arg === '--static') {
1186
- console.log('[CLI] Static mode requested (Frontend auto-detects large graphs).');
1234
+ logDiagnostic('[CLI] Static mode requested (Frontend auto-detects large graphs).');
1187
1235
  }
1188
1236
  else if (arg === '--workers' && args[i + 1]) {
1189
1237
  cliOptions.maxWorkers = parseInt(args[++i]);
@@ -1204,7 +1252,7 @@ for (let i = 0; i < args.length; i++) {
1204
1252
  }
1205
1253
  }
1206
1254
  }
1207
- console.log('[CLI] Parsed Options:', cliOptions);
1255
+ logDiagnostic('[CLI] Parsed Options:', cliOptions);
1208
1256
  function getCliFlagValue(argsList, flagName) {
1209
1257
  const index = argsList.indexOf(flagName);
1210
1258
  if (index < 0 || index + 1 >= argsList.length) {
@@ -1327,7 +1375,7 @@ const startServer = async (options = {}) => {
1327
1375
  hasCliBuild = true;
1328
1376
  }
1329
1377
  else {
1330
- console.warn("[CLI] Warning: targetPath detected as 'true'. This usually means npm consumed the flag incorrectly. Please check your command syntax.");
1378
+ warnDiagnostic("[CLI] Warning: targetPath detected as 'true'. This usually means npm consumed the flag incorrectly. Please check your command syntax.");
1331
1379
  delete cliOptions.targetPath;
1332
1380
  hasCliBuild = false;
1333
1381
  }
@@ -1352,14 +1400,14 @@ const startServer = async (options = {}) => {
1352
1400
  // If we are required to not block, we should probably default to "Load" if exists, or "Gen" if not.
1353
1401
  const latest = await findLatestCliBuildForKb(kbName);
1354
1402
  if (latest) {
1355
- console.log(`\n[CLI] Found existing build for '${kbName}': ${latest}`);
1403
+ logDiagnostic(`\n[CLI] Found existing build for '${kbName}': ${latest}`);
1356
1404
  // If specific options passed (embedded mode), default to Load to avoid blocking
1357
1405
  // Otherwise use interactive prompt
1358
1406
  if (options.targetPath) {
1359
1407
  useExisting = true;
1360
1408
  const suffix = latest.replace('data_cli_', '').replace('.js', '');
1361
1409
  cliOptions.outputPrefix = suffix;
1362
- console.log(`[CLI] Auto-Loading existing data: ${latest}`);
1410
+ logDiagnostic(`[CLI] Auto-Loading existing data: ${latest}`);
1363
1411
  }
1364
1412
  else {
1365
1413
  const rl = readline.createInterface({
@@ -1378,7 +1426,7 @@ const startServer = async (options = {}) => {
1378
1426
  // suffix = kbName_time
1379
1427
  const suffix = latest.replace('data_cli_', '').replace('.js', '');
1380
1428
  cliOptions.outputPrefix = suffix;
1381
- console.log(`[CLI] Loading existing data: ${latest}`);
1429
+ logDiagnostic(`[CLI] Loading existing data: ${latest}`);
1382
1430
  }
1383
1431
  }
1384
1432
  }
@@ -1386,10 +1434,10 @@ const startServer = async (options = {}) => {
1386
1434
  const now = new Date();
1387
1435
  const timeStr = now.toISOString().replace(/[-:T]/g, '').slice(0, 15);
1388
1436
  cliOptions.outputPrefix = `${kbName}_${timeStr}`;
1389
- console.log(`[CLI] Generating new knowledge graph for: ${cliOptions.targetPath}`);
1437
+ logDiagnostic(`[CLI] Generating new knowledge graph for: ${cliOptions.targetPath}`);
1390
1438
  try {
1391
1439
  await (0, index_1.buildGraph)(cliOptions);
1392
- console.log('[CLI] Generation complete.');
1440
+ logDiagnostic('[CLI] Generation complete.');
1393
1441
  }
1394
1442
  catch (e) {
1395
1443
  console.error('[CLI] Build failed:', e);
@@ -1706,7 +1754,7 @@ const startServer = async (options = {}) => {
1706
1754
  const restoreKey = `restore:${target}`;
1707
1755
  const now = Date.now();
1708
1756
  if (lastRestoreKey === restoreKey && (now - lastRestoreTs) < 3000) {
1709
- console.log(`[Cache] Duplicate restore suppressed for ${target}`);
1757
+ logDiagnostic(`[Cache] Duplicate restore suppressed for ${target}`);
1710
1758
  res.writeHead(200, { 'Content-Type': 'application/json' });
1711
1759
  res.end(JSON.stringify({ success: true, deduped: true }));
1712
1760
  return;
@@ -2554,7 +2602,7 @@ const startServer = async (options = {}) => {
2554
2602
  try {
2555
2603
  const payload = await readJsonBody(req);
2556
2604
  const { target, maxWorkers, enableGPU, enableGPULayout, memorySavingMode, deepDebug } = payload;
2557
- console.log('Received build request for:', target, 'maxWorkers:', maxWorkers, 'enableGPU:', enableGPU, 'enableGPULayout:', enableGPULayout, 'memorySavingMode:', memorySavingMode, 'deepDebug:', deepDebug);
2605
+ logDiagnostic('Received build request for:', target, 'maxWorkers:', maxWorkers, 'enableGPU:', enableGPU, 'enableGPULayout:', enableGPULayout, 'memorySavingMode:', memorySavingMode, 'deepDebug:', deepDebug);
2558
2606
  const buildKey = JSON.stringify({
2559
2607
  target,
2560
2608
  maxWorkers,
@@ -2566,7 +2614,7 @@ const startServer = async (options = {}) => {
2566
2614
  // De-duplicate accidental double-submit from frontend.
2567
2615
  if (activeBuildPromise) {
2568
2616
  if (activeBuildKey === buildKey) {
2569
- console.log('[Build] Duplicate request detected. Waiting for in-flight build.');
2617
+ logDiagnostic('[Build] Duplicate request detected. Waiting for in-flight build.');
2570
2618
  await activeBuildPromise;
2571
2619
  res.writeHead(200, { 'Content-Type': 'application/json' });
2572
2620
  res.end(JSON.stringify({
@@ -2644,7 +2692,7 @@ const startServer = async (options = {}) => {
2644
2692
  return;
2645
2693
  }
2646
2694
  KB_ROOT = resolvedKbPath;
2647
- console.log(`[API] Knowledge Base Root updated to: ${KB_ROOT}`);
2695
+ logDiagnostic(`[API] Knowledge Base Root updated to: ${KB_ROOT}`);
2648
2696
  res.writeHead(200, { 'Content-Type': 'application/json' });
2649
2697
  res.end(JSON.stringify({ success: true, kbPath: KB_ROOT }));
2650
2698
  }
@@ -2675,11 +2723,11 @@ const startServer = async (options = {}) => {
2675
2723
  runtimePort = resolvedPort;
2676
2724
  await ensureRuntimeDataDir();
2677
2725
  await writeSidecarRuntimeManifest(resolvedPort);
2678
- console.log(`[Sidecar] Runtime Manifest: ${SIDECAR_RUNTIME_MANIFEST}`);
2679
- console.log(`Server running at http://${LOOPBACK_HOST}:${resolvedPort}/`);
2680
- console.log(`Knowledge Base Root: ${KB_ROOT}`);
2681
- console.log(`Frontend Root: ${FRONTEND_DIR}`);
2682
- console.log(`Runtime Data Root: ${RUNTIME_DATA_DIR}`);
2726
+ logDiagnostic(`[Sidecar] Runtime Manifest: ${SIDECAR_RUNTIME_MANIFEST}`);
2727
+ logDiagnostic(`Server running at http://${LOOPBACK_HOST}:${resolvedPort}/`);
2728
+ logDiagnostic(`Knowledge Base Root: ${KB_ROOT}`);
2729
+ logDiagnostic(`Frontend Root: ${FRONTEND_DIR}`);
2730
+ logDiagnostic(`Runtime Data Root: ${RUNTIME_DATA_DIR}`);
2683
2731
  // Initialize PathBridge
2684
2732
  try {
2685
2733
  pathBridge = new PathBridge_1.PathBridge({
@@ -2687,20 +2735,20 @@ const startServer = async (options = {}) => {
2687
2735
  host: LOOPBACK_HOST,
2688
2736
  authToken: AUTH_TOKEN,
2689
2737
  });
2690
- console.log(`[Sidecar] PathBridge initialized on ws://${LOOPBACK_HOST}:${PATH_BRIDGE_PORT}`);
2738
+ logDiagnostic(`[Sidecar] PathBridge initialized on ws://${LOOPBACK_HOST}:${PATH_BRIDGE_PORT}`);
2691
2739
  }
2692
2740
  catch (e) {
2693
2741
  console.error(`[Sidecar] Failed to initialize PathBridge:`, e);
2694
2742
  }
2695
2743
  if (hasCliBuild) {
2696
- console.log('[CLI] Ready.');
2744
+ logDiagnostic('[CLI] Ready.');
2697
2745
  }
2698
2746
  };
2699
2747
  const attachListenHandlers = (targetPort) => {
2700
2748
  const onError = (error) => {
2701
2749
  server.off('listening', onListening);
2702
2750
  if (error?.code === 'EADDRINUSE' && allowEphemeralFallback && targetPort === finalPort) {
2703
- console.warn(`[Sidecar] Port ${finalPort} is already in use. Retrying with an ephemeral loopback port.`);
2751
+ warnDiagnostic(`[Sidecar] Port ${finalPort} is already in use. Retrying with an ephemeral loopback port.`);
2704
2752
  attachListenHandlers(0);
2705
2753
  return;
2706
2754
  }
@@ -144,6 +144,12 @@ describe('server ephemeral port fallback policy contract', () => {
144
144
  let tempProject = null;
145
145
  let envRestorers = [];
146
146
  let originalArgv = [];
147
+ test('server installs stdio broken-pipe guards before emitting fallback logs', () => {
148
+ const serverSource = fs.readFileSync(path.join(__dirname, 'server.ts'), 'utf8');
149
+ expect(serverSource).toContain('installBrokenPipeGuard(process.stdout');
150
+ expect(serverSource).toContain('installBrokenPipeGuard(process.stderr');
151
+ expect(serverSource).toContain("error?.code === 'EPIPE'");
152
+ });
147
153
  beforeEach(() => {
148
154
  tempProject = makeTempProject('noteconnection-port-policy');
149
155
  const projectRoot = path.join(tempProject.root, 'project');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noteconnection",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "Hierarchical Knowledge Graph Visualization System",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {