dsh-codegraph-visualizer 0.1.1 → 0.3.0

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.
Files changed (58) hide show
  1. package/README.md +391 -226
  2. package/assets/icon-180.png +0 -0
  3. package/assets/icon.svg +30 -0
  4. package/cordis.patch.yml +22 -17
  5. package/dist/CodeGraphAdapter-C8JvcS7m.js +10 -0
  6. package/dist/CodeGraphAdapter-eyrhs0K0.js +422 -0
  7. package/dist/CodeGraphAdapter-eyrhs0K0.js.map +1 -0
  8. package/dist/FileSystemAdapter-CgasSGbB.js +524 -0
  9. package/dist/FileSystemAdapter-CgasSGbB.js.map +1 -0
  10. package/dist/LensAdapter-C9Saf_i_.js +89 -0
  11. package/dist/LensAdapter-C9Saf_i_.js.map +1 -0
  12. package/dist/Logger-DUIJSeNu.js +80 -0
  13. package/dist/Logger-DUIJSeNu.js.map +1 -0
  14. package/dist/TreeSitterAdapter-C-EWy8br.js +631 -0
  15. package/dist/TreeSitterAdapter-C-EWy8br.js.map +1 -0
  16. package/dist/TreeSitterAdapter-C6FcO2_s.js +8 -0
  17. package/dist/client/index.css +8900 -1777
  18. package/dist/client/index.css.map +1 -0
  19. package/dist/client/index.d.ts +634 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.esm.js +57219 -20978
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/{index.js → worker/layoutWorker.js} +7657 -8787
  24. package/dist/client.js +90067 -0
  25. package/dist/codeGraphDb-BT9nZCEO.js +357 -0
  26. package/dist/codeGraphDb-BT9nZCEO.js.map +1 -0
  27. package/dist/codeGraphDb-Bg7bWud2.js +9 -0
  28. package/dist/codeGraphRows-BhZX34uB.js +6 -0
  29. package/dist/codeGraphRows-C7hqu35E.js +246 -0
  30. package/dist/codeGraphRows-C7hqu35E.js.map +1 -0
  31. package/dist/constants-CQq93l3e.js +399 -0
  32. package/dist/constants-CQq93l3e.js.map +1 -0
  33. package/dist/graphFingerprint-CeARo7jG.js +4 -0
  34. package/dist/graphFingerprint-PuxORZUF.js +277 -0
  35. package/dist/graphFingerprint-PuxORZUF.js.map +1 -0
  36. package/dist/graphMetrics-C0F30730.js +555 -0
  37. package/dist/graphMetrics-C0F30730.js.map +1 -0
  38. package/dist/index.d.ts +572 -9
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +14734 -2003
  41. package/dist/index.js.map +1 -1
  42. package/dist/inheritanceEnhancer-Bi3UoYJS.js +291 -0
  43. package/dist/inheritanceEnhancer-Bi3UoYJS.js.map +1 -0
  44. package/dist/layerUtils-DDm-EsaD.js +72 -0
  45. package/dist/layerUtils-DDm-EsaD.js.map +1 -0
  46. package/dist/nativeIndex-Cx7RLXUp.js +5 -0
  47. package/dist/nativeIndex-DlSpD611.js +347 -0
  48. package/dist/nativeIndex-DlSpD611.js.map +1 -0
  49. package/dist/treeSitterExtractors-B1ybKNrC.js +394 -0
  50. package/dist/treeSitterExtractors-B1ybKNrC.js.map +1 -0
  51. package/dist/types-BUDxI_00.js +23 -0
  52. package/dist/types-BUDxI_00.js.map +1 -0
  53. package/locale/en.json +6 -0
  54. package/locale/zh-CN.json +6 -0
  55. package/locale/zh.json +6 -0
  56. package/package.json +100 -20
  57. package/dist/client-cjs/index.cjs +0 -44283
  58. package/dist/client-cjs/index.css +0 -2404
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["value","Binary","result","a","b","Time","template","options","pattern","value","role","date","name","key","id: string","LEVEL_ORDER: Record<LogLevel, number>","level: LogLevel","scope: string","message: string","data?: unknown","entry: LogEntry","fn: (entries: LogEntry[]) => void","m: string","d?: unknown","log","NODE_KIND_MAP: Record<string, GraphNode['type']>","EDGE_KIND_MAP: Record<string, GraphEdge['type']>","kind: string","workspacePath: string","dbPath: string","repoId: string","invoke: UpstreamInvoker","nodes: GraphNode[]","files: unknown","dbNodes: DbNodeRow[]","dbEdges: DbEdgeRow[]","edges: GraphEdge[]","log","repoId: string","invoke: UpstreamInvoker","nodes: GraphNode[]","edges: GraphEdge[]","cat: string","map: Record<string, GraphNode['type']>","rel: string","map: Record<string, GraphEdge['type']>","results: AdapterResult[]","repoId: RepoId","current: GraphData","delta: AdapterResult","log","repoId: string","data: GraphData","invoke: UpstreamInvoker","source: 'codegraph' | 'lens' | 'both'","tasks: Promise<AdapterResult>[]","results: AdapterResult[]","raw: unknown","symbolId: string","ctx: Context","_args: unknown","value: unknown","args: { repoId: string }","args: { repoId: string; source?: string }","emitUpdate: (event: GraphUpdatedEvent) => void","emitData: (event: GraphDataEvent) => void","deltaSource: AdapterResult['source']","args: { symbolId: string }","options: { requestTimeout?: number }","event: GraphUpdatedEvent","event: GraphDataEvent","DEFAULT_CONFIG: VisualizerConfig","userConfig?: Partial<VisualizerConfig>","config: VisualizerConfig","errors: string[]","positiveFields: Array<[keyof VisualizerConfig, number]>","allowedWorkspaceRoots: string[]","roots: string[]","path: string","log","ctx: Context","paths: string[]","log","data: GraphData","maxNodes: number","ctx: Context","config: VisualizerConfig","tool: string","args: Record<string, unknown>","deps: RouteDeps","service: IGraphVisualizerService","repoId: string","symbolId: string","callback: (data: GraphData) => void","res: ServerResponse","code: number","data: unknown","req: IncomingMessage","maxBodyBytes: number","chunk?: Buffer","body: string","obj: Record<string, unknown>","field: string","_req: IncomingMessage","log","watchTimer: ReturnType<typeof setTimeout> | null","activeWatcher: { close: () => void } | null","ctx: Context","config: VisualizerConfig","invokeUpstream: (tool: string, args: Record<string, unknown>) => Promise<unknown | null>","path: string","_eventType: string","filename: string | null","ctx: Context","userConfig?: Partial<VisualizerConfig>","lastGraphData: GraphData | null","lastInitResult: { success: boolean; path: string; message: string; timestamp: number } | null","scanInFlight: Promise<GraphData> | null","data: GraphData | null","data: { success: boolean; path: string; message: string; timestamp: number } | null","p: Promise<GraphData> | null"],"sources":["../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js","../node_modules/.pnpm/@deepseek-ai+schemastery@3.18.1/node_modules/@deepseek-ai/schemastery/lib/index.mjs","../node_modules/.pnpm/@deepseek-ai+dsh-timeout@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@deepseek-ai+dsh-invariants@0.1_2yxulatypvfilrzvurumr2xwty/node_modules/@deepseek-ai/dsh-timeout/lib/index.js","../node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@deepseek-ai+dsh-attachment@0.1.1-r_x2n35l65jyhywzqyfdn334iwva/node_modules/@deepseek-ai/dsh-llm/lib/index.js","../src/types/index.ts","../src/shared/Logger.ts","../src/adapters/CodeGraphAdapter.ts","../src/adapters/LensAdapter.ts","../src/merger/GraphDataMerger.ts","../src/tools.ts","../src/generated/version.ts","../src/host/config.ts","../src/host/security.ts","../src/host/prerequisites.ts","../src/host/routes.ts","../src/host/watcher.ts","../src/index.ts"],"sourcesContent":["//#region lib/types/misc.js\n/** No-op callback returning `undefined` at runtime and `any` at type level. */\nfunction noop() {}\n/** Return true when a value is `null` or `undefined`. */\nfunction isNullable(value) {\n\treturn value === null || value === void 0;\n}\n/** Return true when a value is neither `null` nor `undefined`. */\nfunction isNonNullable(value) {\n\treturn !isNullable(value);\n}\n/** Return true for non-array object values. */\nfunction isPlainObject(data) {\n\treturn data && typeof data === \"object\" && !Array.isArray(data);\n}\n/** Filter object entries and return a new object. */\nfunction filterKeys(object, filter) {\n\treturn Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));\n}\n/** Map object values while preserving the original key set. */\nfunction mapValues(object, transform) {\n\treturn Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));\n}\n/** Pick selected keys from an object, optionally including `undefined` values. */\nfunction pick(source, keys, forced) {\n\tif (!keys) return { ...source };\n\tconst result = {};\n\tfor (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];\n\treturn result;\n}\n/** Omit selected keys from a shallow object copy. */\nfunction omit(source, keys) {\n\tif (!keys) return { ...source };\n\tconst result = { ...source };\n\tfor (const key of keys) Reflect.deleteProperty(result, key);\n\treturn result;\n}\n/** Define a non-enumerable writable property and return the object. */\nfunction defineProperty(object, key, value) {\n\treturn Object.defineProperty(object, key, {\n\t\twritable: true,\n\t\tvalue,\n\t\tenumerable: false\n\t});\n}\n//#endregion\n//#region lib/types/array.js\n/** Return true when every item in `array2` is present in `array1`. */\nfunction contain(array1, array2) {\n\treturn array2.every((item) => array1.includes(item));\n}\n/** Return items that appear in both arrays. */\nfunction intersection(array1, array2) {\n\treturn array1.filter((item) => array2.includes(item));\n}\n/** Return items from `array1` that do not appear in `array2`. */\nfunction difference(array1, array2) {\n\treturn array1.filter((item) => !array2.includes(item));\n}\n/** Return the set-union of two arrays while preserving first occurrence order. */\nfunction union(array1, array2) {\n\treturn Array.from(new Set([...array1, ...array2]));\n}\n/** Remove duplicate values while preserving first occurrence order. */\nfunction deduplicate(array) {\n\treturn [...new Set(array)];\n}\n/** Remove one item from an array and report whether it was found. */\nfunction remove(list, item) {\n\tconst index = list?.indexOf(item);\n\tif (index >= 0) {\n\t\tlist.splice(index, 1);\n\t\treturn true;\n\t} else return false;\n}\n/** Normalize nullish, scalar, or array input to an array. */\nfunction makeArray(source) {\n\treturn Array.isArray(source) ? source : isNullable(source) ? [] : [source];\n}\n//#endregion\n//#region lib/types/types.js\n/** Test values using `instanceof` with a `toStringTag` fallback. */\nfunction is(type, value) {\n\tif (arguments.length === 1) return (value) => is(type, value);\n\treturn type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;\n}\nfunction isArrayBufferLike(value) {\n\treturn is(\"ArrayBuffer\", value) || is(\"SharedArrayBuffer\", value);\n}\nfunction isArrayBufferSource(value) {\n\treturn isArrayBufferLike(value) || ArrayBuffer.isView(value);\n}\n/** Binary source detection and base64/hex conversion helpers. */\nvar Binary;\n(function(Binary) {\n\tBinary.is = isArrayBufferLike;\n\tBinary.isSource = isArrayBufferSource;\n\tfunction fromSource(source) {\n\t\tif (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);\n\t\telse return source;\n\t}\n\tBinary.fromSource = fromSource;\n\tfunction toBase64(source) {\n\t\tsource = fromSource(source);\n\t\tif (typeof Buffer !== \"undefined\") return Buffer.from(source).toString(\"base64\");\n\t\tlet binary = \"\";\n\t\tconst bytes = new Uint8Array(source);\n\t\tfor (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);\n\t\treturn btoa(binary);\n\t}\n\tBinary.toBase64 = toBase64;\n\tfunction fromBase64(source) {\n\t\tif (typeof Buffer !== \"undefined\") return fromSource(Buffer.from(source, \"base64\"));\n\t\treturn Uint8Array.from(atob(source), (c) => c.charCodeAt(0));\n\t}\n\tBinary.fromBase64 = fromBase64;\n\tfunction toHex(source) {\n\t\tsource = fromSource(source);\n\t\tif (typeof Buffer !== \"undefined\") return Buffer.from(source).toString(\"hex\");\n\t\treturn Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\t}\n\tBinary.toHex = toHex;\n\tfunction fromHex(source) {\n\t\tif (typeof Buffer !== \"undefined\") return fromSource(Buffer.from(source, \"hex\"));\n\t\tconst hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);\n\t\tconst buffer = [];\n\t\tfor (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));\n\t\treturn Uint8Array.from(buffer).buffer;\n\t}\n\tBinary.fromHex = fromHex;\n})(Binary || (Binary = {}));\n/** Decode a base64 string into binary data. */\nconst base64ToArrayBuffer = Binary.fromBase64;\n/** Encode binary data as base64. */\nconst arrayBufferToBase64 = Binary.toBase64;\n/** Decode a hex string into binary data. */\nconst hexToArrayBuffer = Binary.fromHex;\n/** Encode binary data as hex. */\nconst arrayBufferToHex = Binary.toHex;\n/** Deep-clone common JavaScript values while preserving prototypes and cycles. */\nfunction clone(source, refs = /* @__PURE__ */ new Map()) {\n\tif (!source || typeof source !== \"object\") return source;\n\tif (is(\"Date\", source)) return new Date(source.valueOf());\n\tif (is(\"RegExp\", source)) return new RegExp(source.source, source.flags);\n\tif (isArrayBufferLike(source)) return source.slice(0);\n\tif (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);\n\tconst cached = refs.get(source);\n\tif (cached) return cached;\n\tif (Array.isArray(source)) {\n\t\tconst result = [];\n\t\trefs.set(source, result);\n\t\tsource.forEach((value, index) => {\n\t\t\tresult[index] = Reflect.apply(clone, null, [value, refs]);\n\t\t});\n\t\treturn result;\n\t}\n\tconst result = Object.create(Object.getPrototypeOf(source));\n\trefs.set(source, result);\n\tfor (const key of Reflect.ownKeys(source)) {\n\t\tconst descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };\n\t\tif (\"value\" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);\n\t\tReflect.defineProperty(result, key, descriptor);\n\t}\n\treturn result;\n}\n/** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */\nfunction deepEqual(a, b, strict) {\n\tif (a === b) return true;\n\tif (!strict && isNullable(a) && isNullable(b)) return true;\n\tif (typeof a !== typeof b) return false;\n\tif (typeof a !== \"object\") return false;\n\tif (!a || !b) return false;\n\tfunction check(test, then) {\n\t\treturn test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;\n\t}\n\treturn check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is(\"Date\"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is(\"RegExp\"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {\n\t\tif (a.byteLength !== b.byteLength) return false;\n\t\tconst viewA = new Uint8Array(a);\n\t\tconst viewB = new Uint8Array(b);\n\t\tfor (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;\n\t\treturn true;\n\t}) ?? Object.keys({\n\t\t...a,\n\t\t...b\n\t}).every((key) => deepEqual(a[key], b[key], strict));\n}\n//#endregion\n//#region lib/types/string.js\n/** Uppercase the first character of a string. */\nfunction capitalize(source) {\n\treturn source.charAt(0).toUpperCase() + source.slice(1);\n}\n/** Lowercase the first character of a string. */\nfunction uncapitalize(source) {\n\treturn source.charAt(0).toLowerCase() + source.slice(1);\n}\n/** Convert dash or underscore delimited text to camelCase. */\nfunction camelCase(source) {\n\treturn source.replace(/[_-][a-z]/g, (str) => str.slice(1).toUpperCase());\n}\nfunction tokenize(source, delimiters, delimiter) {\n\tconst output = [];\n\tlet state = 0;\n\tfor (let i = 0; i < source.length; i++) {\n\t\tconst code = source.charCodeAt(i);\n\t\tif (code >= 65 && code <= 90) {\n\t\t\tif (state === 1) {\n\t\t\t\tconst next = source.charCodeAt(i + 1);\n\t\t\t\tif (next >= 97 && next <= 122) output.push(delimiter);\n\t\t\t\toutput.push(code + 32);\n\t\t\t} else {\n\t\t\t\tif (state !== 0) output.push(delimiter);\n\t\t\t\toutput.push(code + 32);\n\t\t\t}\n\t\t\tstate = 1;\n\t\t} else if (code >= 97 && code <= 122) {\n\t\t\toutput.push(code);\n\t\t\tstate = 2;\n\t\t} else if (delimiters.includes(code)) {\n\t\t\tif (state !== 0) output.push(delimiter);\n\t\t\tstate = 0;\n\t\t} else output.push(code);\n\t}\n\treturn String.fromCharCode(...output);\n}\n/** Convert text to dash-delimited parameter case. */\nfunction paramCase(source) {\n\treturn tokenize(source, [45, 95], 45);\n}\n/** Convert text to underscore-delimited snake case. */\nfunction snakeCase(source) {\n\treturn tokenize(source, [45, 95], 95);\n}\n/** Runtime alias for `camelCase`. */\nconst camelize = camelCase;\n/** Runtime alias for `paramCase`. */\nconst hyphenate = paramCase;\n/** Format a property key as a JavaScript member access suffix. */\nfunction formatProperty(key) {\n\tif (typeof key !== \"string\") return `[${key.toString()}]`;\n\treturn /^[a-z_$][\\w$]*$/i.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;\n}\n/** Remove one trailing slash from a path string. */\nfunction trimSlash(source) {\n\treturn source.replace(/\\/$/, \"\");\n}\n/** Ensure a path starts with `/` and has no trailing slash. */\nfunction sanitize(source) {\n\tif (!source.startsWith(\"/\")) source = \"/\" + source;\n\treturn trimSlash(source);\n}\n//#endregion\n//#region lib/types/time.js\n/** Time constants plus parsing and formatting helpers. */\nvar Time;\n(function(Time) {\n\tTime.millisecond = 1;\n\tTime.second = 1e3;\n\tTime.minute = Time.second * 60;\n\tTime.hour = Time.minute * 60;\n\tTime.day = Time.hour * 24;\n\tTime.week = Time.day * 7;\n\tlet timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();\n\tfunction setTimezoneOffset(offset) {\n\t\ttimezoneOffset = offset;\n\t}\n\tTime.setTimezoneOffset = setTimezoneOffset;\n\tfunction getTimezoneOffset() {\n\t\treturn timezoneOffset;\n\t}\n\tTime.getTimezoneOffset = getTimezoneOffset;\n\tfunction getDateNumber(date = /* @__PURE__ */ new Date(), offset) {\n\t\tif (typeof date === \"number\") date = new Date(date);\n\t\tif (offset === void 0) offset = timezoneOffset;\n\t\treturn Math.floor((date.valueOf() / Time.minute - offset) / 1440);\n\t}\n\tTime.getDateNumber = getDateNumber;\n\tfunction fromDateNumber(value, offset) {\n\t\tconst date = new Date(value * Time.day);\n\t\tif (offset === void 0) offset = timezoneOffset;\n\t\treturn new Date(+date + offset * Time.minute);\n\t}\n\tTime.fromDateNumber = fromDateNumber;\n\tconst numeric = /\\d+(?:\\.\\d+)?/.source;\n\tconst timeRegExp = new RegExp(`^${[\n\t\t\"w(?:eek(?:s)?)?\",\n\t\t\"d(?:ay(?:s)?)?\",\n\t\t\"h(?:our(?:s)?)?\",\n\t\t\"m(?:in(?:ute)?(?:s)?)?\",\n\t\t\"s(?:ec(?:ond)?(?:s)?)?\"\n\t].map((unit) => `(${numeric}${unit})?`).join(\"\")}$`);\n\tfunction parseTime(source) {\n\t\tconst capture = timeRegExp.exec(source);\n\t\tif (!capture) return 0;\n\t\treturn (parseFloat(capture[1]) * Time.week || 0) + (parseFloat(capture[2]) * Time.day || 0) + (parseFloat(capture[3]) * Time.hour || 0) + (parseFloat(capture[4]) * Time.minute || 0) + (parseFloat(capture[5]) * Time.second || 0);\n\t}\n\tTime.parseTime = parseTime;\n\tfunction parseDate(date) {\n\t\tconst parsed = parseTime(date);\n\t\tif (parsed) date = Date.now() + parsed;\n\t\telse if (/^\\d{1,2}(:\\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;\n\t\telse if (/^\\d{1,2}-\\d{1,2}-\\d{1,2}(:\\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;\n\t\treturn date ? new Date(date) : /* @__PURE__ */ new Date();\n\t}\n\tTime.parseDate = parseDate;\n\tfunction format(ms) {\n\t\tconst abs = Math.abs(ms);\n\t\tif (abs >= Time.day - Time.hour / 2) return Math.round(ms / Time.day) + \"d\";\n\t\telse if (abs >= Time.hour - Time.minute / 2) return Math.round(ms / Time.hour) + \"h\";\n\t\telse if (abs >= Time.minute - Time.second / 2) return Math.round(ms / Time.minute) + \"m\";\n\t\telse if (abs >= Time.second) return Math.round(ms / Time.second) + \"s\";\n\t\treturn ms + \"ms\";\n\t}\n\tTime.format = format;\n\tfunction toDigits(source, length = 2) {\n\t\treturn source.toString().padStart(length, \"0\");\n\t}\n\tTime.toDigits = toDigits;\n\tfunction template(template, time = /* @__PURE__ */ new Date()) {\n\t\treturn template.replace(\"yyyy\", time.getFullYear().toString()).replace(\"yy\", time.getFullYear().toString().slice(2)).replace(\"MM\", toDigits(time.getMonth() + 1)).replace(\"dd\", toDigits(time.getDate())).replace(\"hh\", toDigits(time.getHours())).replace(\"mm\", toDigits(time.getMinutes())).replace(\"ss\", toDigits(time.getSeconds())).replace(\"SSS\", toDigits(time.getMilliseconds(), 3));\n\t}\n\tTime.template = template;\n})(Time || (Time = {}));\n//#endregion\nexport { Binary, Time, arrayBufferToBase64, arrayBufferToHex, base64ToArrayBuffer, camelCase, camelize, capitalize, clone, contain, deduplicate, deepEqual, defineProperty, difference, filterKeys, formatProperty, hexToArrayBuffer, hyphenate, intersection, is, isNonNullable, isNullable, isPlainObject, makeArray, mapValues, mapValues as valueMap, noop, omit, paramCase, pick, remove, sanitize, snakeCase, trimSlash, uncapitalize, union };\n","import { Binary, clone, deepEqual, filterKeys, isNullable, isPlainObject, pick, valueMap } from \"@deepseek-ai/cosmokit\";\n//#region lib/types/index.js\nconst kSchema = Symbol.for(\"schemastery\");\nconst kValidationError = Symbol.for(\"ValidationError\");\nglobalThis.__schemastery_index__ ??= 0;\nglobalThis.__schemastery_refs__ = void 0;\nvar ValidationError = class extends TypeError {\n\toptions;\n\tname = \"ValidationError\";\n\tconstructor(message, options) {\n\t\tlet prefix = \"$\";\n\t\tfor (const segment of options.path || []) if (typeof segment === \"string\") prefix += \".\" + segment;\n\t\telse if (typeof segment === \"number\") prefix += \"[\" + segment + \"]\";\n\t\telse if (typeof segment === \"symbol\") prefix += `[Symbol(${segment.toString()})]`;\n\t\tif (prefix.startsWith(\".\")) prefix = prefix.slice(1);\n\t\tsuper((prefix === \"$\" ? \"\" : `${prefix} `) + message);\n\t\tthis.options = options;\n\t}\n\tstatic is(error) {\n\t\treturn !!error?.[kValidationError];\n\t}\n};\nObject.defineProperty(ValidationError.prototype, kValidationError, { value: true });\nconst Schema = function(options) {\n\tconst schema = function(data, options = {}) {\n\t\treturn Schema.resolve(data, schema, options)[0];\n\t};\n\tif (options.refs) {\n\t\tconst refs = valueMap(options.refs, (options) => new Schema(options));\n\t\tconst getRef = (uid) => refs[uid];\n\t\tfor (const key in refs) {\n\t\t\tconst options = refs[key];\n\t\t\toptions.sKey = getRef(options.sKey);\n\t\t\toptions.inner = getRef(options.inner);\n\t\t\toptions.list = options.list && options.list.map(getRef);\n\t\t\toptions.dict = options.dict && valueMap(options.dict, getRef);\n\t\t}\n\t\treturn refs[options.uid];\n\t}\n\tObject.assign(schema, options);\n\tif (typeof schema.callback === \"string\") try {\n\t\tschema.callback = new Function(\"return \" + schema.callback)();\n\t} catch {}\n\tObject.defineProperty(schema, \"uid\", { value: globalThis.__schemastery_index__++ });\n\tObject.setPrototypeOf(schema, Schema.prototype);\n\tschema.meta ||= {};\n\tschema.toString = schema.toString.bind(schema);\n\treturn schema;\n};\nSchema.prototype = Object.create(Function.prototype);\nSchema.prototype[kSchema] = true;\nObject.defineProperty(Schema.prototype, \"~standard\", { get() {\n\treturn {\n\t\tversion: 1,\n\t\tvendor: \"schemastery\",\n\t\tvalidate: (value) => {\n\t\t\ttry {\n\t\t\t\treturn { value: Schema.resolve(value, this, {})[0] };\n\t\t\t} catch (error) {\n\t\t\t\tif (ValidationError.is(error)) return { issues: [{\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t\tpath: error.options.path\n\t\t\t\t}] };\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n} });\nSchema.ValidationError = ValidationError;\nSchema.prototype.toJSON = function toJSON() {\n\tif (globalThis.__schemastery_refs__) {\n\t\tglobalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));\n\t\treturn this.uid;\n\t}\n\tglobalThis.__schemastery_refs__ = { [this.uid]: { ...this } };\n\tglobalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));\n\tconst result = {\n\t\tuid: this.uid,\n\t\trefs: globalThis.__schemastery_refs__\n\t};\n\tglobalThis.__schemastery_refs__ = void 0;\n\treturn result;\n};\nSchema.prototype.set = function set(key, value) {\n\tthis.dict[key] = value;\n\treturn this;\n};\nSchema.prototype.push = function push(value) {\n\tthis.list.push(value);\n\treturn this;\n};\nfunction mergeDesc(original, messages) {\n\tconst result = typeof original === \"string\" ? { \"\": original } : { ...original };\n\tfor (const locale in messages) {\n\t\tconst value = messages[locale];\n\t\tif (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;\n\t\telse if (typeof value === \"string\") result[locale] = value;\n\t}\n\treturn result;\n}\nfunction getInner(value) {\n\treturn value?.$value ?? value?.$inner;\n}\nfunction extractKeys(data) {\n\treturn filterKeys(data ?? {}, (key) => !key.startsWith(\"$\"));\n}\nSchema.prototype.i18n = function i18n(messages) {\n\tconst schema = Schema(this);\n\tconst desc = mergeDesc(schema.meta.description, messages);\n\tif (Object.keys(desc).length) schema.meta.description = desc;\n\tif (schema.dict) schema.dict = valueMap(schema.dict, (inner, key) => {\n\t\treturn inner.i18n(valueMap(messages, (data) => getInner(data)?.[key] ?? data?.[key]));\n\t});\n\tif (schema.list) schema.list = schema.list.map((inner, index) => {\n\t\treturn inner.i18n(valueMap(messages, (data = {}) => {\n\t\t\tif (Array.isArray(getInner(data))) return getInner(data)[index];\n\t\t\tif (Array.isArray(data)) return data[index];\n\t\t\treturn extractKeys(data);\n\t\t}));\n\t});\n\tif (schema.inner) schema.inner = schema.inner.i18n(valueMap(messages, (data) => {\n\t\tif (getInner(data)) return getInner(data);\n\t\treturn extractKeys(data);\n\t}));\n\tif (schema.sKey) schema.sKey = schema.sKey.i18n(valueMap(messages, (data) => data?.$key));\n\treturn schema;\n};\nSchema.prototype.extra = function extra(key, value) {\n\tconst schema = Schema(this);\n\tschema.meta = {\n\t\t...schema.meta,\n\t\t[key]: value\n\t};\n\treturn schema;\n};\nfor (const key of [\n\t\"required\",\n\t\"disabled\",\n\t\"collapse\",\n\t\"hidden\",\n\t\"loose\"\n]) Object.assign(Schema.prototype, { [key](value = true) {\n\tconst schema = Schema(this);\n\tschema.meta = {\n\t\t...schema.meta,\n\t\t[key]: value\n\t};\n\treturn schema;\n} });\nSchema.prototype.deprecated = function deprecated() {\n\tconst schema = Schema(this);\n\tschema.meta.badges ||= [];\n\tschema.meta.badges.push({\n\t\ttext: \"deprecated\",\n\t\ttype: \"danger\"\n\t});\n\treturn schema;\n};\nSchema.prototype.experimental = function experimental() {\n\tconst schema = Schema(this);\n\tschema.meta.badges ||= [];\n\tschema.meta.badges.push({\n\t\ttext: \"experimental\",\n\t\ttype: \"warning\"\n\t});\n\treturn schema;\n};\nSchema.prototype.pattern = function pattern(regexp) {\n\tconst schema = Schema(this);\n\tconst pattern = pick(regexp, [\"source\", \"flags\"]);\n\tschema.meta = {\n\t\t...schema.meta,\n\t\tpattern\n\t};\n\treturn schema;\n};\nSchema.prototype.simplify = function simplify(value) {\n\tif (deepEqual(value, this.meta.default, this.type === \"dict\")) return null;\n\tif (isNullable(value)) return value;\n\tif (this.type === \"object\" || this.type === \"dict\") {\n\t\tconst result = {};\n\t\tfor (const key in value) {\n\t\t\tconst item = (this.type === \"object\" ? this.dict[key] : this.inner)?.simplify(value[key]);\n\t\t\tif (this.type === \"dict\" || !isNullable(item)) result[key] = item;\n\t\t}\n\t\tif (deepEqual(result, this.meta.default, this.type === \"dict\")) return null;\n\t\treturn result;\n\t} else if (this.type === \"array\" || this.type === \"tuple\") {\n\t\tconst result = [];\n\t\tvalue.forEach((value, index) => {\n\t\t\tconst schema = this.type === \"array\" ? this.inner : this.list[index];\n\t\t\tconst item = schema ? schema.simplify(value) : value;\n\t\t\tresult.push(item);\n\t\t});\n\t\treturn result;\n\t} else if (this.type === \"intersect\") {\n\t\tconst result = {};\n\t\tfor (const item of this.list) Object.assign(result, item.simplify(value));\n\t\treturn result;\n\t} else if (this.type === \"union\") for (const schema of this.list) try {\n\t\tSchema.resolve(value, schema, {});\n\t\treturn schema.simplify(value);\n\t} catch {}\n\treturn value;\n};\nSchema.prototype.toString = function toString(inline) {\n\treturn formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;\n};\nSchema.prototype.role = function role(role, extra) {\n\tconst schema = Schema(this);\n\tschema.meta = {\n\t\t...schema.meta,\n\t\trole,\n\t\textra\n\t};\n\treturn schema;\n};\nfor (const key of [\n\t\"default\",\n\t\"link\",\n\t\"comment\",\n\t\"description\",\n\t\"max\",\n\t\"min\",\n\t\"step\"\n]) Object.assign(Schema.prototype, { [key](value) {\n\tconst schema = Schema(this);\n\tschema.meta = {\n\t\t...schema.meta,\n\t\t[key]: value\n\t};\n\treturn schema;\n} });\nconst resolvers = {};\nSchema.extend = function extend(type, resolve) {\n\tresolvers[type] = resolve;\n};\nSchema.resolve = function resolve(data, schema, options = {}, strict = false) {\n\tif (!schema) return [data];\n\tif (options.ignore?.(data, schema)) return [data];\n\tif (isNullable(data) && schema.type !== \"lazy\") {\n\t\tif (schema.meta.required) throw new ValidationError(`missing required value`, options);\n\t\tlet current = schema;\n\t\tlet fallback = schema.meta.default;\n\t\twhile (current?.type === \"intersect\" && isNullable(fallback)) {\n\t\t\tcurrent = current.list[0];\n\t\t\tfallback = current?.meta.default;\n\t\t}\n\t\tif (isNullable(fallback)) return [data];\n\t\tdata = clone(fallback);\n\t}\n\tconst callback = resolvers[schema.type];\n\tif (!callback) throw new ValidationError(`unsupported type \"${schema.type}\"`, options);\n\ttry {\n\t\treturn callback(data, schema, options, strict);\n\t} catch (error) {\n\t\tif (!schema.meta.loose) throw error;\n\t\treturn [schema.meta.default];\n\t}\n};\nSchema.from = function from(source) {\n\tif (isNullable(source)) return Schema.any();\n\telse if ([\n\t\t\"string\",\n\t\t\"number\",\n\t\t\"boolean\"\n\t].includes(typeof source)) return Schema.const(source).required();\n\telse if (source[kSchema]) return source;\n\telse if (typeof source === \"function\") switch (source) {\n\t\tcase String: return Schema.string().required();\n\t\tcase Number: return Schema.number().required();\n\t\tcase Boolean: return Schema.boolean().required();\n\t\tcase Function: return Schema.function().required();\n\t\tdefault: return Schema.is(source).required();\n\t}\n\telse throw new TypeError(`cannot infer schema from ${source}`);\n};\nSchema.lazy = function lazy(builder) {\n\tconst toJSON = () => {\n\t\tif (!schema.inner[kSchema]) {\n\t\t\tschema.inner = schema.builder();\n\t\t\tschema.inner.meta = {\n\t\t\t\t...schema.meta,\n\t\t\t\t...schema.inner.meta\n\t\t\t};\n\t\t}\n\t\treturn schema.inner.toJSON();\n\t};\n\tconst schema = new Schema({\n\t\ttype: \"lazy\",\n\t\tbuilder,\n\t\tinner: { toJSON }\n\t});\n\treturn schema;\n};\nSchema.natural = function natural() {\n\treturn Schema.number().step(1).min(0);\n};\nSchema.percent = function percent() {\n\treturn Schema.number().step(.01).min(0).max(1).role(\"slider\");\n};\nSchema.date = function date() {\n\treturn Schema.union([Schema.is(Date), Schema.transform(Schema.string().role(\"datetime\"), (value, options) => {\n\t\tconst date = new Date(value);\n\t\tif (isNaN(+date)) throw new ValidationError(`invalid date \"${value}\"`, options);\n\t\treturn date;\n\t}, true)]);\n};\nSchema.regExp = function regExp(flag = \"\") {\n\treturn Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role(\"regexp\", { flag }), (value, options) => {\n\t\ttry {\n\t\t\treturn new RegExp(value, flag);\n\t\t} catch (e) {\n\t\t\tthrow new ValidationError(e.message, options);\n\t\t}\n\t}, true)]);\n};\nSchema.arrayBuffer = function arrayBuffer(encoding) {\n\treturn Schema.union([\n\t\tSchema.is(ArrayBuffer),\n\t\tSchema.is(SharedArrayBuffer),\n\t\tSchema.transform(Schema.any(), (value, options) => {\n\t\t\tif (Binary.isSource(value)) return Binary.fromSource(value);\n\t\t\tthrow new ValidationError(`expected ArrayBufferSource but got ${value}`, options);\n\t\t}, true),\n\t\t...encoding ? [Schema.transform(Schema.string(), (value, options) => {\n\t\t\ttry {\n\t\t\t\treturn encoding === \"base64\" ? Binary.fromBase64(value) : Binary.fromHex(value);\n\t\t\t} catch (e) {\n\t\t\t\tthrow new ValidationError(e.message, options);\n\t\t\t}\n\t\t}, true)] : []\n\t]);\n};\nSchema.extend(\"lazy\", (data, schema, options, strict) => {\n\tif (!schema.inner[kSchema]) {\n\t\tschema.inner = schema.builder();\n\t\tschema.inner.meta = {\n\t\t\t...schema.meta,\n\t\t\t...schema.inner.meta\n\t\t};\n\t}\n\treturn Schema.resolve(data, schema.inner, options, strict);\n});\nSchema.extend(\"any\", (data) => {\n\treturn [data];\n});\nSchema.extend(\"never\", (data, _, options) => {\n\tthrow new ValidationError(`expected nullable but got ${data}`, options);\n});\nSchema.extend(\"const\", (data, { value }, options) => {\n\tif (deepEqual(data, value)) return [value];\n\tthrow new ValidationError(`expected ${value} but got ${data}`, options);\n});\nfunction checkWithinRange(data, meta, description, options, skipMin = false) {\n\tconst { max = Infinity, min = -Infinity } = meta;\n\tif (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);\n\tif (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);\n}\nSchema.extend(\"string\", (data, { meta }, options) => {\n\tif (typeof data !== \"string\") throw new ValidationError(`expected string but got ${data}`, options);\n\tif (meta.pattern) {\n\t\tconst regexp = new RegExp(meta.pattern.source, meta.pattern.flags);\n\t\tif (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);\n\t}\n\tcheckWithinRange(data.length, meta, \"string length\", options);\n\treturn [data];\n});\nfunction decimalShift(data, digits) {\n\tconst str = data.toString();\n\tif (str.includes(\"e\")) return data * Math.pow(10, digits);\n\tconst index = str.indexOf(\".\");\n\tif (index === -1) return data * Math.pow(10, digits);\n\tconst frac = str.slice(index + 1);\n\tconst integer = str.slice(0, index);\n\tif (frac.length <= digits) return +(integer + frac.padEnd(digits, \"0\"));\n\treturn +(integer + frac.slice(0, digits) + \".\" + frac.slice(digits));\n}\nfunction isMultipleOf(data, min, step) {\n\tstep = Math.abs(step);\n\tif (!/^\\d+\\.\\d+$/.test(step.toString())) return (data - min) % step === 0;\n\tconst index = step.toString().indexOf(\".\");\n\tconst digits = step.toString().slice(index + 1).length;\n\treturn Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;\n}\nSchema.extend(\"number\", (data, { meta }, options) => {\n\tif (typeof data !== \"number\") throw new ValidationError(`expected number but got ${data}`, options);\n\tcheckWithinRange(data, meta, \"number\", options);\n\tconst { step } = meta;\n\tif (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);\n\treturn [data];\n});\nSchema.extend(\"boolean\", (data, _, options) => {\n\tif (typeof data === \"boolean\") return [data];\n\tthrow new ValidationError(`expected boolean but got ${data}`, options);\n});\nSchema.extend(\"bitset\", (data, { bits, meta }, options) => {\n\tlet value = 0, keys = [];\n\tif (typeof data === \"number\") {\n\t\tvalue = data;\n\t\tfor (const key in bits) if (data & bits[key]) keys.push(key);\n\t} else if (Array.isArray(data)) {\n\t\tkeys = data;\n\t\tfor (const key of keys) {\n\t\t\tif (typeof key !== \"string\") throw new ValidationError(`expected string but got ${key}`, options);\n\t\t\tif (key in bits) value |= bits[key];\n\t\t}\n\t} else throw new ValidationError(`expected number or array but got ${data}`, options);\n\tif (value === meta.default) return [value];\n\treturn [value, keys];\n});\nSchema.extend(\"function\", (data, _, options) => {\n\tif (typeof data === \"function\") return [data];\n\tthrow new ValidationError(`expected function but got ${data}`, options);\n});\nSchema.extend(\"is\", (data, { constructor }, options) => {\n\tif (typeof constructor === \"function\") {\n\t\tif (data instanceof constructor) return [data];\n\t\tthrow new ValidationError(`expected ${constructor.name} but got ${data}`, options);\n\t} else {\n\t\tif (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);\n\t\tlet prototype = Object.getPrototypeOf(data);\n\t\twhile (prototype) {\n\t\t\tif (prototype.constructor?.name === constructor) return [data];\n\t\t\tprototype = Object.getPrototypeOf(prototype);\n\t\t}\n\t\tthrow new ValidationError(`expected ${constructor} but got ${data}`, options);\n\t}\n});\nfunction property(data, key, schema, options) {\n\ttry {\n\t\tconst [value, adapted] = Schema.resolve(data[key], schema, {\n\t\t\t...options,\n\t\t\tpath: [...options.path || [], key]\n\t\t});\n\t\tif (adapted !== void 0) data[key] = adapted;\n\t\treturn value;\n\t} catch (e) {\n\t\tif (!options?.autofix) throw e;\n\t\tdelete data[key];\n\t\treturn schema.meta.default;\n\t}\n}\nSchema.extend(\"array\", (data, { inner, meta }, options) => {\n\tif (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);\n\tcheckWithinRange(data.length, meta, \"array length\", options, !isNullable(inner.meta.default));\n\treturn [data.map((_, index) => property(data, index, inner, options))];\n});\nSchema.extend(\"dict\", (data, { inner, sKey }, options, strict) => {\n\tif (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);\n\tconst result = {};\n\tfor (const key in data) {\n\t\tlet rKey;\n\t\ttry {\n\t\t\trKey = Schema.resolve(key, sKey, options)[0];\n\t\t} catch (error) {\n\t\t\tif (strict) continue;\n\t\t\tthrow error;\n\t\t}\n\t\tresult[rKey] = property(data, key, inner, options);\n\t\tdata[rKey] = data[key];\n\t\tif (key !== rKey) delete data[key];\n\t}\n\treturn [result];\n});\nSchema.extend(\"tuple\", (data, { list }, options, strict) => {\n\tif (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);\n\tconst result = list.map((inner, index) => property(data, index, inner, options));\n\tif (strict) return [result];\n\tresult.push(...data.slice(list.length));\n\treturn [result];\n});\nfunction merge(result, data) {\n\tfor (const key in data) {\n\t\tif (key in result) continue;\n\t\tresult[key] = data[key];\n\t}\n}\nSchema.extend(\"object\", (data, { dict }, options, strict) => {\n\tif (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);\n\tconst result = {};\n\tfor (const key in dict) {\n\t\tconst value = property(data, key, dict[key], options);\n\t\tif (!isNullable(value) || key in data) result[key] = value;\n\t}\n\tif (!strict) merge(result, data);\n\treturn [result];\n});\nSchema.extend(\"union\", (data, { list, toString }, options, strict) => {\n\tconst messages = [];\n\tfor (const inner of list) try {\n\t\treturn Schema.resolve(data, inner, options, strict);\n\t} catch (error) {\n\t\tmessages.push(error);\n\t}\n\tthrow new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);\n});\nSchema.extend(\"intersect\", (data, { list, toString }, options, strict) => {\n\tif (!list.length) return [data];\n\tlet result;\n\tfor (const inner of list) {\n\t\tconst value = Schema.resolve(data, inner, options, true)[0];\n\t\tif (isNullable(value)) continue;\n\t\tif (isNullable(result)) result = value;\n\t\telse if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);\n\t\telse if (typeof value === \"object\") merge(result ??= {}, value);\n\t\telse if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);\n\t}\n\tif (!strict && isPlainObject(data)) merge(result, data);\n\treturn [result];\n});\nSchema.extend(\"transform\", (data, { inner, callback, preserve }, options) => {\n\tconst [result, adapted = data] = Schema.resolve(data, inner, options, true);\n\tif (preserve) return [callback(result)];\n\telse return [callback(result), callback(adapted)];\n});\nconst formatters = {};\nfunction defineMethod(name, keys, format) {\n\tformatters[name] = format;\n\tObject.assign(Schema, { [name](...args) {\n\t\tconst schema = new Schema({ type: name });\n\t\tkeys.forEach((key, index) => {\n\t\t\tswitch (key) {\n\t\t\t\tcase \"sKey\":\n\t\t\t\t\tschema.sKey = args[index] ?? Schema.string();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"inner\":\n\t\t\t\t\tschema.inner = Schema.from(args[index]);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"list\":\n\t\t\t\t\tschema.list = args[index].map(Schema.from);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"dict\":\n\t\t\t\t\tschema.dict = valueMap(args[index], Schema.from);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bits\":\n\t\t\t\t\tschema.bits = {};\n\t\t\t\t\tfor (const key in args[index]) {\n\t\t\t\t\t\tif (typeof args[index][key] !== \"number\") continue;\n\t\t\t\t\t\tschema.bits[key] = args[index][key];\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"callback\": {\n\t\t\t\t\tconst callback = schema.callback = args[index];\n\t\t\t\t\tcallback[\"toJSON\"] ||= () => callback.toString();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"constructor\": {\n\t\t\t\t\tconst constructor = schema.constructor = args[index];\n\t\t\t\t\tif (typeof constructor === \"function\") constructor[\"toJSON\"] ||= () => constructor[\"name\"];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault: schema[key] = args[index];\n\t\t\t}\n\t\t});\n\t\tif (name === \"object\" || name === \"dict\") schema.meta.default = {};\n\t\telse if (name === \"array\" || name === \"tuple\") schema.meta.default = [];\n\t\telse if (name === \"bitset\") schema.meta.default = 0;\n\t\treturn schema;\n\t} });\n}\ndefineMethod(\"is\", [\"constructor\"], ({ constructor }) => {\n\tif (typeof constructor === \"function\") return constructor.name;\n\telse return constructor;\n});\ndefineMethod(\"any\", [], () => \"any\");\ndefineMethod(\"never\", [], () => \"never\");\ndefineMethod(\"const\", [\"value\"], ({ value }) => typeof value === \"string\" ? JSON.stringify(value) : value);\ndefineMethod(\"string\", [], () => \"string\");\ndefineMethod(\"number\", [], () => \"number\");\ndefineMethod(\"boolean\", [], () => \"boolean\");\ndefineMethod(\"bitset\", [\"bits\"], () => \"bitset\");\ndefineMethod(\"function\", [], () => \"function\");\ndefineMethod(\"array\", [\"inner\"], ({ inner }) => `${inner.toString(true)}[]`);\ndefineMethod(\"dict\", [\"inner\", \"sKey\"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);\ndefineMethod(\"tuple\", [\"list\"], ({ list }) => `[${list.map((inner) => inner.toString()).join(\", \")}]`);\ndefineMethod(\"object\", [\"dict\"], ({ dict }) => {\n\tif (Object.keys(dict).length === 0) return \"{}\";\n\treturn `{ ${Object.entries(dict).map(([key, inner]) => {\n\t\treturn `${key}${inner.meta.required ? \"\" : \"?\"}: ${inner.toString()}`;\n\t}).join(\", \")} }`;\n});\ndefineMethod(\"union\", [\"list\"], ({ list }, inline) => {\n\tconst result = list.map(({ toString: format }) => format()).join(\" | \");\n\treturn inline ? `(${result})` : result;\n});\ndefineMethod(\"intersect\", [\"list\"], ({ list }) => {\n\treturn `${list.map((inner) => inner.toString(true)).join(\" & \")}`;\n});\ndefineMethod(\"transform\", [\n\t\"inner\",\n\t\"callback\",\n\t\"preserve\"\n], ({ inner }, isInner) => inner.toString(isInner));\n//#endregion\nexport { Schema as default };\n","//#region lib/types/index.js\n/**\n* Shared timeout arithmetic, signal fusion, and classification. The library\n* only notifies through abort signals; each capability still owns the mechanism\n* that stops its work and translates timeout reasons into public outcomes.\n* @module @deepseek-ai/dsh-timeout\n*/\n/**\n* Internal abort reason carrying a capability-owned code and elapsed deadline.\n* Providers translate it through {@link timeoutOf} before returning to callers.\n*/\nvar TimeoutReason = class extends Error {\n\tcode;\n\ttimeoutMs;\n\tname = \"TimeoutReason\";\n\t/**\n\t* @param code Capability-owned timeout code (e.g. `BASH_TIMEOUT`).\n\t* @param timeoutMs The deadline that elapsed, in milliseconds.\n\t*/\n\tconstructor(code, timeoutMs) {\n\t\tsuper(`${code} after ${timeoutMs}ms`);\n\t\tthis.code = code;\n\t\tthis.timeoutMs = timeoutMs;\n\t}\n};\n/** Largest delay Node schedules without clamping it to one millisecond. */\nconst MAX_TIMER_DELAY_MS = 2147483647;\nfunction assertTimerDelay(timeoutMs, name) {\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new Error(`${name} must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);\n}\n/**\n* Validate a caller's optional timeout hint, use the backend default, then cap\n* it. Supplied values must be positive and finite; zero is not a public\n* disable-timeout sentinel.\n*\n* @param requested The caller's optional hint; validated when present.\n* @param def The backend default applied when `requested` is absent.\n* @param max The backend upper bound the result is capped to.\n* @param name Field name used in the thrown message (so the caller sees which input was\n* bad).\n* @returns The effective timeout in milliseconds: `min(requested ?? def, max)`.\n*/\nfunction clampTimeout(requested, def, max, name = \"timeoutMs\") {\n\tif (requested !== void 0 && (!Number.isFinite(requested) || requested <= 0)) throw new Error(`${name} must be a positive finite number`);\n\treturn Math.min(requested ?? def, max);\n}\n/**\n* Fuse upstream cancellation with an identifiable timeout. `timeoutMs <= 0` is\n* the internal no-timer sentinel; the returned disposer clears an armed timer.\n* The signal only notifies, so callers must stop their own work.\n*\n* @param upstream The caller's cancellation signal, if any, fused into the result.\n* @param timeoutMs Deadline in milliseconds; `<= 0` means \"no timeout\" (arm no timer).\n* @param code Capability-owned code stamped onto the timeout's {@link TimeoutReason}.\n* @returns The fused {@link Deadline} (signal + timer cleanup).\n*/\nfunction deadline(upstream, timeoutMs, code) {\n\tif (timeoutMs <= 0) return {\n\t\tsignal: upstream ?? new AbortController().signal,\n\t\t[Symbol.dispose]() {}\n\t};\n\tassertTimerDelay(timeoutMs, \"deadline timeoutMs\");\n\tconst timer = new AbortController();\n\tconst id = setTimeout(() => {\n\t\ttimer.abort(new TimeoutReason(code, timeoutMs));\n\t}, timeoutMs);\n\treturn {\n\t\tsignal: upstream !== void 0 ? AbortSignal.any([upstream, timer.signal]) : timer.signal,\n\t\t[Symbol.dispose]() {\n\t\t\tclearTimeout(id);\n\t\t}\n\t};\n}\n/**\n* Create a rearmable idle watchdog for an async iterator. The timer exists only\n* while {@link IdleWatchdog.next} is outstanding, so consumer think time does\n* not count as provider idle time. The returned signal is stable for the whole\n* call and only notifies; the iterator must observe it to terminate its work.\n*\n* @param upstream - caller cancellation fused into the stable signal.\n* @param timeoutMs - positive finite idle interval in milliseconds.\n* @param code - capability-owned code carried by the timeout reason.\n* @returns a stable signal, guarded next operation, and timer disposer.\n*/\nfunction idleWatchdog(upstream, timeoutMs, code) {\n\tassertTimerDelay(timeoutMs, \"idleWatchdog timeoutMs\");\n\tconst timeout = new AbortController();\n\tconst signal = upstream === void 0 ? timeout.signal : AbortSignal.any([upstream, timeout.signal]);\n\tlet timer;\n\tlet outstanding = false;\n\tlet disposed = false;\n\tconst arm = () => {\n\t\tif (timer !== void 0) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimeout.abort(new TimeoutReason(code, timeoutMs));\n\t\t}, timeoutMs);\n\t};\n\treturn {\n\t\tsignal,\n\t\tasync next(iterator) {\n\t\t\tif (disposed) throw new Error(\"idleWatchdog is disposed\");\n\t\t\tif (outstanding) throw new Error(\"idleWatchdog next is already outstanding\");\n\t\t\toutstanding = true;\n\t\t\tarm();\n\t\t\ttry {\n\t\t\t\treturn await iterator.next();\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimer = void 0;\n\t\t\t\toutstanding = false;\n\t\t\t}\n\t\t},\n\t\tpulse() {\n\t\t\tif (disposed || !outstanding) return;\n\t\t\tarm();\n\t\t},\n\t\t[Symbol.dispose]() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tif (timer !== void 0) clearTimeout(timer);\n\t\t\ttimer = void 0;\n\t\t}\n\t};\n}\n/**\n* Recover a timeout reason from a reason-bearing object. Supplying `code`\n* distinguishes this deadline from a nested upstream deadline; a foreign code\n* follows the ordinary cancellation path.\n*\n* @param x An {@link AbortSignal} or any `{ reason }` carrier (e.g. a caught abort error).\n* @param code When provided, only a {@link TimeoutReason} with this exact `code` matches.\n* @returns The matching {@link TimeoutReason}, else `undefined`.\n*/\nfunction timeoutOf(x, code) {\n\tconst reason = x.reason;\n\tif (!(reason instanceof TimeoutReason)) return void 0;\n\treturn code === void 0 || reason.code === code ? reason : void 0;\n}\n//#endregion\nexport { MAX_TIMER_DELAY_MS, TimeoutReason, clampTimeout, deadline, idleWatchdog, timeoutOf };\n","import { createRequire } from \"node:module\";\nimport { Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { MAX_TIMER_DELAY_MS } from \"@deepseek-ai/dsh-timeout\";\n//#region lib/types/brand.js\n/**\n* dsh-llm's owned branded ids: tool-call correlation and provider request\n* diagnostics.\n*\n* The `Branded<B>` primitive itself lives in `@deepseek-ai/dsh-brand` (a\n* zero-dependency type-only package) so every owner of a cross-boundary id can\n* brand it without depending on dsh-llm; see that package's README for the\n* nominal-typing policy.\n*\n* @module @deepseek-ai/dsh-llm/brand\n*/\n/**\n* Brand a message identifier.\n* @param id - the opaque message identifier.\n* @returns the same string, branded; no validation is performed.\n*/\nfunction MessageId(id) {\n\treturn id;\n}\n/**\n* Brand a string as a {@link CallId}.\n* @param id - the provider-issued (or synthesized) call id.\n* @returns the same string, branded; no validation is performed.\n*/\nfunction CallId(id) {\n\treturn id;\n}\n/**\n* Brand a provider-issued request identifier.\n* @param id - the opaque provider-issued string.\n* @returns the same string, branded; no validation is performed.\n*/\nfunction ProviderRequestId(id) {\n\treturn id;\n}\n/**\n* Brand an adapter-owned reasoning-effort identifier.\n* @param id - the opaque identifier exposed by one model capability.\n* @returns the same string, branded; no validation is performed.\n*/\nfunction ReasoningEffortId(id) {\n\treturn id;\n}\n//#endregion\n//#region lib/types/call-config.js\n/**\n* Conversation call configuration and freeze utilities. Provider routing,\n* model, reasoning effort, and sampling values are request-header state that\n* can affect cache reuse; request waterfalls replace them and the loop logs\n* changed snapshots instead of allowing silent per-call drift.\n* @module dsh-llm/call-config\n*/\n/** Process-local identities of request objects assembled by dsh-agent-loop. */\nconst AGENT_LOOP_REQUESTS = /* @__PURE__ */ new WeakSet();\n/**\n* Field-wise equality over {@link LlmCallConfig} — the comparison a caller\n* runs to decide whether a proposed configuration is a real change (worth a\n* logged header snapshot) or the held one restated.\n* @param a - one configuration.\n* @param b - the other.\n* @returns whether every field (including the `stop` list, element-wise) matches.\n*/\nfunction callConfigEquals(a, b) {\n\tif (a.provider !== b.provider || a.model !== b.model || a.reasoningEffort !== b.reasoningEffort || a.temperature !== b.temperature || a.maxTokens !== b.maxTokens) return false;\n\tif (a.stop === void 0 || b.stop === void 0) return a.stop === b.stop;\n\treturn a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);\n}\n/**\n* Mark one exact request object as assembled by dsh-agent-loop.\n* @param request - loop-owned request envelope before LLM dispatch.\n* @returns the same request object marked as created by the process-local agent loop.\n*/\nfunction markAgentLoopRequest(request) {\n\tAGENT_LOOP_REQUESTS.add(request);\n\treturn request;\n}\n/**\n* Test whether the exact request object was assembled by dsh-agent-loop.\n* @param request - request envelope observed at the LLM waterfall.\n* @returns whether {@link markAgentLoopRequest} recorded this object.\n*/\nfunction isAgentLoopRequest(request) {\n\treturn AGENT_LOOP_REQUESTS.has(request);\n}\n/**\n* Deep-freeze a value in place with an iterative traversal, guarding cycles,\n* so later mutation throws without imposing a JavaScript call-stack depth cap.\n* {@link AbortSignal} objects are deliberately skipped because they are the\n* request's live cancellation channel and freezing them breaks abort.\n* @param value - the value to freeze in place.\n* @returns the same value, frozen.\n*/\nfunction deepFreeze(value) {\n\tconst seen = /* @__PURE__ */ new WeakSet();\n\tconst pending = [{\n\t\tkind: \"visit\",\n\t\tnode: value\n\t}];\n\twhile (pending.length > 0) {\n\t\tconst task = pending.pop();\n\t\t/* v8 ignore next -- the loop condition guarantees one pending task. */\n\t\tif (task === void 0) continue;\n\t\tif (task.kind === \"property\") {\n\t\t\tpending.push({\n\t\t\t\tkind: \"visit\",\n\t\t\t\tnode: task.source[task.key]\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst node = task.node;\n\t\tif (node === null || typeof node !== \"object\") continue;\n\t\tif (node instanceof AbortSignal) continue;\n\t\tif (seen.has(node)) continue;\n\t\tseen.add(node);\n\t\tObject.freeze(node);\n\t\tconst keys = Object.keys(node);\n\t\tfor (let index = keys.length - 1; index >= 0; index--) {\n\t\t\tconst key = keys[index];\n\t\t\t/* v8 ignore next -- the loop is bounded by the captured key count. */\n\t\t\tif (key === void 0) continue;\n\t\t\tpending.push({\n\t\t\t\tkind: \"property\",\n\t\t\t\tsource: node,\n\t\t\t\tkey\n\t\t\t});\n\t\t}\n\t}\n\treturn value;\n}\n//#endregion\n//#region lib/types/message.js\n/** Message value types, identity, and immutable construction helpers. */\n/**\n* Bound for a `notice` summary. The account rides a collapsed transcript row\n* and is committed to the durable log, while its inputs — task labels, goal\n* objectives, tool arguments — are caller text with no length of their own.\n*/\nconst CONTEXT_SUMMARY_MAX_CHARS = 120;\n/**\n* Bound one `notice` summary to {@link CONTEXT_SUMMARY_MAX_CHARS}.\n* @param summary - the producer's one-line account, of any length.\n* @returns the account, ellipsized when it exceeds the bound.\n*/\nfunction boundContextSummary(summary) {\n\treturn summary.length <= 120 ? summary : `${summary.slice(0, 119)}…`;\n}\n/**\n* Detach and deep-freeze a message whose identity already exists.\n* @param message - complete message, including its stable identity.\n* @returns an immutable snapshot that preserves the identity.\n*/\nfunction freezeMessage(message) {\n\treturn deepFreeze(structuredClone(message));\n}\n/**\n* Create one identified message and freeze it before publication.\n* @param input - complete role, content, and source for a new message.\n* @returns an immutable message with a fresh stable identity.\n*/\nfunction createMessage(input) {\n\treturn freezeMessage({\n\t\t...input,\n\t\tid: MessageId(crypto.randomUUID())\n\t});\n}\n/**\n* Create one identified user-role message and freeze it before publication.\n* @param input - complete content and source for a new user message.\n* @returns an immutable user message with a fresh stable identity.\n*/\nfunction createUserMessage(input) {\n\treturn createMessage({\n\t\t...input,\n\t\trole: \"user\"\n\t});\n}\n/**\n* Create one identified model-produced assistant message and freeze it before publication.\n* @param input - complete content plus the provider, model, and optional replay state for a new assistant message.\n* @returns an immutable assistant message with fixed role/source tags and a fresh stable identity.\n*/\nfunction createAssistantMessage(input) {\n\treturn createMessage({\n\t\trole: \"assistant\",\n\t\tcontent: input.content,\n\t\tsource: {\n\t\t\tkind: \"model\",\n\t\t\t...input.source\n\t\t}\n\t});\n}\n/**\n* Create and freeze one identified tool-result message.\n* @param input - call identity, raw result blocks, and outcome.\n* @returns an immutable user-role tool-result message.\n*/\nfunction createToolResultMessage(input) {\n\treturn createUserMessage({\n\t\tsource: {\n\t\t\tkind: \"tool\",\n\t\t\tcallId: input.callId\n\t\t},\n\t\tcontent: [{\n\t\t\ttype: \"tool-result\",\n\t\t\ttoolCallId: input.callId,\n\t\t\tcontent: input.content,\n\t\t\tisError: input.isError\n\t\t}]\n\t});\n}\n/**\n* Whether a stream chunk carries visible model output (the first-token\n* boundary shared by client step timing and the whole-log sessionStats\n* projection). Empty deltas (heartbeats, empty tool-call frames) do not count\n* as a first token.\n* @param chunk - the stream chunk to test.\n* @returns true when the chunk contains a non-empty text/reasoning/tool delta.\n*/\nfunction isTokenDelta(chunk) {\n\tswitch (chunk.type) {\n\t\tcase \"text-delta\":\n\t\tcase \"reasoning-delta\": return chunk.text !== \"\";\n\t\tcase \"tool-call-delta\": return chunk.argumentsDelta !== \"\" || chunk.name !== void 0;\n\t\tdefault: return false;\n\t}\n}\n//#endregion\n//#region lib/types/error.js\n/**\n* Harness error base with a stable machine-routable code and chained cause.\n* Package errors extend it so tool results and replay can retain failure class.\n* @module @deepseek-ai/dsh-llm/error\n*/\n/**\n* Base class for all harness errors. Carries a `code` (stable, programmatic —\n* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the\n* human-readable `message`, and supports `cause` chaining via the standard\n* `ErrorOptions`. `name` defaults to the subclass constructor name.\n*/\nvar HarnessError = class extends Error {\n\t/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */\n\tcode;\n\tconstructor(message, code, options) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.name = new.target.name;\n\t}\n};\n/** Canonical provider-neutral code for a model request rejected because its context window was exceeded. */\nconst CONTEXT_WINDOW_EXCEEDED_CODE = \"CONTEXT_WINDOW_EXCEEDED\";\n/** Canonical provider-neutral code for an exhausted account quota or balance. */\nconst QUOTA_EXCEEDED_CODE = \"QUOTA\";\n/**\n* Canonical provider-neutral code for a response that completed normally but\n* carried no content blocks at all. Providers occasionally emit a degenerate\n* completion (a terminal stop with zero output); adapters classify it as this\n* failure instead of yielding an empty assistant message, because an empty\n* message silently ends the turn with nothing for the user or the loop to act\n* on. The attempt produced nothing durable, so retry policy treats it as safe\n* to repeat.\n*/\nconst EMPTY_RESPONSE_CODE = \"EMPTY_RESPONSE\";\n/**\n* Canonical provider-neutral code for a credential that was supplied but\n* cannot be used — malformed rather than absent. Distinct from\n* `MISSING_CREDENTIAL` because the fix differs: correct the stored value\n* rather than supply one. Deliberately outside the default retryable set —\n* a malformed credential fails identically on every attempt.\n*/\nconst INVALID_CREDENTIAL_CODE = \"INVALID_CREDENTIAL\";\n/** Structured codes and plain phrases that explicitly name a context bound being exceeded. */\nconst STRUCTURED_CONTEXT_OVERFLOW = new RegExp(String.raw`(?:^|[^a-z0-9])context[\\s_-](?:length|window)[\\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\\s_-]exceeded)(?:$|[^a-z0-9])`, \"i\");\n/** Request-size wording that ties \"too large\" directly to model context capacity. */\nconst TOO_LARGE_FOR_CONTEXT = new RegExp(String.raw`\\b(?:request|prompt|input|messages?)\\s+(?:is\\s+|are\\s+)?` + String.raw`too\\s+(?:large|long)\\s+for\\s+(?:(?:this|the)\\s+)?` + String.raw`(?:model(?:'s)?\\s+)?context(?:\\s+window)?\\b`, \"i\");\n/** \"Exceeds\" wording is safe only when its object is explicitly the model context. */\nconst EXCEEDS_MODEL_CONTEXT = new RegExp(String.raw`\\b(?:input|prompt|request|messages?)\\b.{0,40}` + String.raw`\\b(?:exceed(?:s|ed)?|overflows?|is\\s+larger\\s+than)\\b.{0,40}` + String.raw`\\b(?:the\\s+)?(?:model(?:'s)?\\s+)?context(?:\\s+(?:length|window))?\\b`, \"i\");\n/**\n* Recognize the context-overflow wording used by OpenAI-compatible providers\n* and library adapters. Adapters pass all available provider code, type, and\n* message text so both thrown and in-band delivery styles share one classifier.\n* @param detail - provider error code/type/message text joined into one string.\n* @returns true when the detail identifies a request exceeding the model context window.\n*/\nfunction isContextWindowExceededError(detail) {\n\treturn STRUCTURED_CONTEXT_OVERFLOW.test(detail) || /\\b(?:maximum|max)(?:\\s+(?:allowed|supported))?\\s+context\\s+(?:length|window)\\b/i.test(detail) || TOO_LARGE_FOR_CONTEXT.test(detail) || /\\b(?:input|prompt|request)\\s+(?:is\\s+)?too\\s+(?:long|large)\\s+for\\s+(?:this|the)\\s+model\\b/i.test(detail) || EXCEEDS_MODEL_CONTEXT.test(detail);\n}\n/**\n* Recognize provider wording that identifies an exhausted account quota rather\n* than a transient request-rate limit.\n* @param detail - provider error code/type/message text joined into one string.\n* @returns true only for terminal quota, balance, credit, budget, or usage-limit wording.\n*/\nfunction isQuotaExceededError(detail) {\n\treturn /\\binsufficient[\\s_-]+(?:quota|balance|credits?)\\b/i.test(detail) || /\\b(?:quota|usage[\\s_-]+limit)[\\s_-]+(?:exceeded|exhausted|reached)\\b/i.test(detail) || /\\bexceed(?:ed|s)?[\\s_-]+(?:(?:your|the)[\\s_-]+)?(?:current[\\s_-]+)?quota\\b/i.test(detail) || /\\b(?:balance|credits?)[\\s_-]+(?:exhausted|depleted)\\b/i.test(detail) || /\\bout[\\s_-]+of[\\s_-]+(?:credits?|budget)\\b/i.test(detail);\n}\n/**\n* Render a thrown value with its full `cause` chain and AggregateError\n* members, so transport wrappers like undici's `TypeError: fetch failed`\n* surface the underlying failure instead of masking it. Plain structured\n* failures render their own data-backed `message`. Diagnostic-surface\n* rendering only (messages, notices, logs) — never parse the result; route on\n* {@link HarnessError.code}.\n* @param value - the caught value (`unknown` in catch clauses).\n* @returns the outermost message first, each cause appended with `: ` (skipped\n* when it repeats the wrapper message verbatim), and AggregateError members\n* bracketed and `; `-joined.\n*/\nfunction errorChain(value) {\n\tconst path = /* @__PURE__ */ new Set();\n\tconst render = (current) => {\n\t\tif (path.has(current)) return \"<circular cause>\";\n\t\tpath.add(current);\n\t\ttry {\n\t\t\tif (!(current instanceof Error)) {\n\t\t\t\tif (typeof current === \"object\" && current !== null) {\n\t\t\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(current, \"message\");\n\t\t\t\t\tif (descriptor !== void 0 && \"value\" in descriptor && typeof descriptor.value === \"string\") return descriptor.value;\n\t\t\t\t}\n\t\t\t\treturn String(current);\n\t\t\t}\n\t\t\tconst message = current.message === \"\" ? current.name : current.message;\n\t\t\tconst members = current instanceof AggregateError && current.errors.length > 0 ? ` [${current.errors.map(render).join(\"; \")}]` : \"\";\n\t\t\tconst causeText = current.cause === void 0 || current.cause === null ? \"\" : render(current.cause);\n\t\t\treturn `${message}${members}${causeText === \"\" || causeText === message ? \"\" : `: ${causeText}`}`;\n\t\t} catch {\n\t\t\treturn \"<unrenderable value>\";\n\t\t} finally {\n\t\t\tpath.delete(current);\n\t\t}\n\t};\n\treturn render(value);\n}\n/**\n* Narrow an arbitrary thrown value to a HarnessError (for `instanceof` at runtime boundaries).\n* @param value - the caught value (`unknown` in catch clauses).\n* @returns true only for real instances; duck-typed or cross-realm errors do not narrow.\n*/\nfunction isHarnessError(value) {\n\treturn value instanceof HarnessError;\n}\n//#endregion\n//#region lib/types/retry-policy.js\n/**\n* Provider-owned request-retry policy configuration and resolution.\n*\n* Adapters expose one resolved policy per registered provider route; the\n* optional dsh-llm-retry plugin executes it on the agent's failed-step extension point.\n*\n* @module @deepseek-ai/dsh-llm/retry-policy\n*/\nconst DEFAULT_MAX_RETRIES = 5;\nconst DEFAULT_INITIAL_DELAY_MS = 500;\nconst DEFAULT_MAX_DELAY_MS = 1e4;\nconst DEFAULT_JITTER_RATIO = .1;\nconst DEFAULT_RETRYABLE_CODES = Object.freeze([\n\tEMPTY_RESPONSE_CODE,\n\t\"RATE_LIMIT\",\n\t\"SERVER\",\n\t\"TIMEOUT\",\n\t\"TRANSPORT\"\n]);\nconst backoffSchema = z.object({\n\tinitialDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),\n\tmaxDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),\n\tjitterRatio: z.number().min(0).max(1).default(DEFAULT_JITTER_RATIO)\n});\nconst normalPolicySchema = z.object({\n\tmode: z.const(\"normal\").required(),\n\tmaxRetries: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),\n\tretryableCodes: z.array(z.string()).default([...DEFAULT_RETRYABLE_CODES]),\n\tbackoff: backoffSchema\n});\nconst alwaysPolicySchema = z.object({\n\tmode: z.const(\"always\").required(),\n\tbackoff: backoffSchema\n});\n/** Cordis schema embedded by each concrete provider configuration. */\nconst RetryPolicySchema = z.union([normalPolicySchema, alwaysPolicySchema]);\nconst NORMAL_POLICY_KEYS = new Set([\n\t\"mode\",\n\t\"maxRetries\",\n\t\"retryableCodes\",\n\t\"backoff\"\n]);\nconst ALWAYS_POLICY_KEYS = new Set([\n\t\"mode\",\n\t\"maxRetries\",\n\t\"retryableCodes\",\n\t\"backoff\"\n]);\nconst BACKOFF_KEYS = new Set([\n\t\"initialDelayMs\",\n\t\"maxDelayMs\",\n\t\"jitterRatio\"\n]);\nfunction validateKeys(value, allowed, path) {\n\tfor (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`${path}: unknown key \"${key}\"`);\n}\nfunction resolveBackoff(config, path) {\n\tif (config !== void 0) validateKeys(config, BACKOFF_KEYS, path);\n\tconst initialDelayMs = config?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n\tconst maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n\tconst jitterRatio = config?.jitterRatio ?? DEFAULT_JITTER_RATIO;\n\tif (!Number.isFinite(initialDelayMs) || initialDelayMs <= 0 || initialDelayMs > MAX_TIMER_DELAY_MS) throw new Error(`${path}.initialDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);\n\tif (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0 || maxDelayMs > MAX_TIMER_DELAY_MS) throw new Error(`${path}.maxDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);\n\tif (initialDelayMs > maxDelayMs) throw new Error(`${path}.initialDelayMs must be less than or equal to maxDelayMs`);\n\tif (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) throw new Error(`${path}.jitterRatio must be between 0 and 1`);\n\treturn Object.freeze({\n\t\tinitialDelayMs,\n\t\tmaxDelayMs,\n\t\tjitterRatio\n\t});\n}\n/**\n* Validate, default, and detach one provider-owned retry policy.\n* @param config - optional provider configuration; omission selects normal defaults.\n* @param path - diagnostic path naming the provider config that owns the value.\n* @returns an immutable policy safe to capture in provider registration state.\n*/\nfunction resolveRetryPolicy(config, path) {\n\tif (config === void 0) return Object.freeze({\n\t\tmode: \"normal\",\n\t\tmaxRetries: DEFAULT_MAX_RETRIES,\n\t\tretryableCodes: DEFAULT_RETRYABLE_CODES,\n\t\t...resolveBackoff(void 0, `${path}.backoff`)\n\t});\n\tswitch (config.mode) {\n\t\tcase \"normal\": {\n\t\t\tvalidateKeys(config, NORMAL_POLICY_KEYS, path);\n\t\t\tconst maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\t\tconst retryableCodes = config.retryableCodes ?? [...DEFAULT_RETRYABLE_CODES];\n\t\t\tif (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new Error(`${path}.maxRetries must be a non-negative safe integer`);\n\t\t\tif (retryableCodes.length === 0) throw new Error(`${path}.retryableCodes must not be empty`);\n\t\t\tif (retryableCodes.some((code) => typeof code !== \"string\" || code.length === 0)) throw new Error(`${path}.retryableCodes must contain only non-empty strings`);\n\t\t\tif (new Set(retryableCodes).size !== retryableCodes.length) throw new Error(`${path}.retryableCodes must not contain duplicates`);\n\t\t\treturn Object.freeze({\n\t\t\t\tmode: \"normal\",\n\t\t\t\tmaxRetries,\n\t\t\t\tretryableCodes: Object.freeze([...retryableCodes]),\n\t\t\t\t...resolveBackoff(config.backoff, `${path}.backoff`)\n\t\t\t});\n\t\t}\n\t\tcase \"always\":\n\t\t\tvalidateKeys(config, ALWAYS_POLICY_KEYS, path);\n\t\t\treturn Object.freeze({\n\t\t\t\tmode: \"always\",\n\t\t\t\t...resolveBackoff(config.backoff, `${path}.backoff`)\n\t\t\t});\n\t\tdefault: throw new Error(`${path}.mode must be \"normal\" or \"always\"`);\n\t}\n}\n//#endregion\n//#region lib/types/adapter-failure.js\n/**\n* Normalization for values thrown by a final LLM adapter boundary.\n*\n* @module @deepseek-ai/dsh-llm/adapter-failure\n*/\n/**\n* Detach serializable provider facts from a value thrown by an adapter.\n* @param value - arbitrary value thrown during adapter dispatch or iteration.\n* @returns immutable provider-neutral facts suitable for a terminal finish chunk.\n* @internal\n*/\nfunction normalizeLlmFailure(value) {\n\tconst error = value instanceof Error ? value : new HarnessError(thrownMessage(value), \"UNKNOWN\", { cause: value });\n\tconst carried = ownFailureSnapshot(error);\n\tif (carried !== void 0 && carried.code === ownErrorCode(error)) return carried;\n\treturn Object.freeze({\n\t\tmessage: errorMessage(error),\n\t\tcode: harnessErrorCode(error)\n\t});\n}\n/** Render a non-Error throw without letting hostile coercion escape normalization. */\nfunction thrownMessage(value) {\n\ttry {\n\t\tconst message = String(value);\n\t\treturn message.length > 0 ? message : \"LLM adapter failed\";\n\t} catch (_hostileThrownValue) {\n\t\treturn \"LLM adapter failed\";\n\t}\n}\n/** Read a foreign error's own data-backed `code` without invoking accessors. */\nfunction ownErrorCode(error) {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(error, \"code\");\n\t\treturn descriptor !== void 0 && \"value\" in descriptor ? descriptor.value : void 0;\n\t} catch (_sdkPropertyTrap) {\n\t\treturn;\n\t}\n}\n/** Snapshot an own data property without invoking an SDK-defined accessor. */\nfunction ownFailureSnapshot(error) {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(error, \"failure\");\n\t\treturn descriptor !== void 0 && \"value\" in descriptor ? failureSnapshot(descriptor.value) : void 0;\n\t} catch (_sdkPropertyTrap) {\n\t\treturn;\n\t}\n}\n/** Validate and detach an arbitrary serializable failure payload. */\nfunction failureSnapshot(value) {\n\tif (typeof value !== \"object\" || value === null) return void 0;\n\ttry {\n\t\tconst candidate = value;\n\t\tconst message = candidate.message;\n\t\tconst code = candidate.code;\n\t\tconst status = candidate.status;\n\t\tconst providerRetryAfterMs = candidate.providerRetryAfterMs;\n\t\tconst requestId = candidate.requestId;\n\t\tif (typeof message !== \"string\" || message.length === 0 || typeof code !== \"string\" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== \"string\" || requestId.length === 0)) return void 0;\n\t\treturn Object.freeze({\n\t\t\tmessage,\n\t\t\tcode,\n\t\t\t...status === void 0 ? {} : { status },\n\t\t\t...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },\n\t\t\t...requestId === void 0 ? {} : { requestId }\n\t\t});\n\t} catch (_sdkFailureGetter) {\n\t\treturn;\n\t}\n}\n/** Read an SDK error message without letting an accessor replace the primary failure. */\nfunction errorMessage(error) {\n\ttry {\n\t\tconst message = error.message;\n\t\tif (typeof message === \"string\" && message.length > 0) return message;\n\t} catch (_sdkMessageGetter) {}\n\treturn \"LLM adapter failed\";\n}\n/** Trust only Harness-owned codes; third-party SDK codes are not our taxonomy. */\nfunction harnessErrorCode(error) {\n\treturn error instanceof HarnessError ? error.code : \"UNKNOWN\";\n}\n//#endregion\n//#region lib/types/api-key.js\n/**\n* The one definition of a well-formed provider API key, shared by every\n* adapter that puts one in an HTTP header.\n* @module @deepseek-ai/dsh-llm/api-key\n*/\n/**\n* Characters an HTTP header value carries verbatim and every known provider\n* key uses: printable ASCII, space excluded. A key outside this set cannot\n* reach any provider — `fetch` refuses to build the header — so this is a\n* transport invariant rather than one provider's policy. Latin-1 is excluded\n* deliberately: a header could carry it, but no provider issues it, and\n* admitting it trades a local explained refusal for an opaque 401.\n*/\nconst LEGAL_API_KEY = /^[\\x21-\\x7E]+$/;\n/**\n* Judge one *supplied* API key, trimming surrounding whitespace first.\n*\n* Trimming is silent because a padded key has one unambiguous reading; every\n* other defect is reported. Absence is a configuration state this function\n* never sees — a profile naming no credential authenticates through the\n* provider's own ambient discovery or OAuth — so callers decide whether a\n* value was supplied before asking.\n* @param raw - the key exactly as configured, stored, or typed.\n* @returns the trimmed key, or why it cannot be used.\n*/\nfunction normalizeApiKey(raw) {\n\tconst value = raw.trim();\n\tif (value.length === 0) return {\n\t\tok: false,\n\t\treason: \"empty\"\n\t};\n\tif (!LEGAL_API_KEY.test(value)) return {\n\t\tok: false,\n\t\treason: \"illegalCharacters\"\n\t};\n\treturn {\n\t\tok: true,\n\t\tvalue\n\t};\n}\n//#endregion\n//#region lib/types/content.js\n/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */\n/** Model-facing stand-in for an image removed to fit a provider request bound. */\nconst OFFLOADED_IMAGE_TEXT = \"[image omitted to keep the request within its image limit; older images are omitted first. If this image is still needed, read its file again when a path is available; otherwise ask the user to attach it again.]\";\n/**\n* Stable text shown to a model that cannot accept one durable image reference.\n* @param ref - durable master reference omitted from the request.\n* @returns deterministic text-only placeholder.\n*/\nfunction textOnlyImageText(ref) {\n\treturn `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;\n}\n/**\n* Stable model-facing handle for one exact request image.\n* @param version - exact request image shown beside the text.\n* @returns attachment handle and request-image dimensions.\n*/\nfunction requestImageHandleText(version) {\n\treturn `Image ${version.attachment.attachmentId}; request image ${version.width}x${version.height}px.`;\n}\n/**\n* True when typed model content contains an image block, walking nested\n* tool-result content. This is the one recursive image walk shared by every\n* image policy (capability gating, text-only serialization, compaction\n* survey), so a consumer cannot silently diverge on nesting depth.\n* @param content - typed model content blocks.\n* @returns whether any nested block is an image.\n*/\nfunction contentHasImage(content) {\n\treturn content.some((block) => block.type === \"image\" || block.type === \"tool-result\" && contentHasImage(block.content));\n}\n/** Base64 length of raw image bytes, including padding. */\nfunction base64Length(bytes) {\n\treturn Math.ceil(bytes / 3) * 4;\n}\n/** Collect represented image lengths in request and nested-block order. */\nfunction collectImageLengths(blocks, lengths, policy) {\n\tfor (const block of blocks) if (block.type === \"image\") {\n\t\tconst bytes = policy.byteLength === void 0 ? block.attachment.bytes : policy.byteLength(block.attachment);\n\t\tlengths.push(policy.representation === \"base64\" ? base64Length(bytes) : bytes);\n\t} else if (block.type === \"tool-result\") collectImageLengths(block.content, lengths, policy);\n}\n/** Replace the first `remaining.count` image occurrences without mutating durable messages. */\nfunction replaceOldestImages(blocks, remaining) {\n\tlet next;\n\tfor (const [index, block] of blocks.entries()) {\n\t\tif (block.type === \"image\" && remaining.count > 0) {\n\t\t\tremaining.count -= 1;\n\t\t\tnext ??= blocks.slice(0, index);\n\t\t\tnext.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: OFFLOADED_IMAGE_TEXT\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"tool-result\") {\n\t\t\tconst content = replaceOldestImages(block.content, remaining);\n\t\t\tif (content !== block.content) {\n\t\t\t\tnext ??= blocks.slice(0, index);\n\t\t\t\tnext.push({\n\t\t\t\t\t...block,\n\t\t\t\t\tcontent\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tnext?.push(block);\n\t}\n\treturn next ?? blocks;\n}\n/** Replace every image occurrence, including nested tool results, for a text-only model. */\nfunction replaceImagesForTextModel(blocks) {\n\tlet next;\n\tfor (const [index, block] of blocks.entries()) {\n\t\tif (block.type === \"image\") {\n\t\t\tnext ??= blocks.slice(0, index);\n\t\t\tnext.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: textOnlyImageText(block.attachment)\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"tool-result\") {\n\t\t\tconst content = replaceImagesForTextModel(block.content);\n\t\t\tif (content !== block.content) {\n\t\t\t\tnext ??= blocks.slice(0, index);\n\t\t\t\tnext.push({\n\t\t\t\t\t...block,\n\t\t\t\t\tcontent\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tnext?.push(block);\n\t}\n\treturn next ?? blocks;\n}\n/**\n* Project durable image history into deterministic text for an exact text-only model.\n* @param messages - complete request history.\n* @returns the original list without images, otherwise shallow message copies with stable placeholders.\n*/\nfunction projectImagesForTextModel(messages) {\n\tif (!messages.some((message) => contentHasImage(message.content))) return messages;\n\treturn messages.map((message) => {\n\t\tconst content = replaceImagesForTextModel(message.content);\n\t\treturn content === message.content ? message : {\n\t\t\t...message,\n\t\t\tcontent\n\t\t};\n\t});\n}\n/**\n* Return transient request messages whose oldest images are replaced until\n* their accumulated base64 payload fits the configured bound. The selection\n* is deterministic from durable message order and attachment metadata; a\n* provider can serialize the returned messages without reading omitted bytes.\n* @param messages - complete request history, oldest first.\n* @param maxRequestImageBytes - positive bound on total base64 image payload; undefined preserves every image.\n* @returns the original messages when they already fit, otherwise shallow message copies with replaced content trees.\n*/\nfunction offloadRequestImages(messages, maxRequestImageBytes) {\n\treturn offloadRequestImagesWithPolicy(messages, {\n\t\trepresentation: \"base64\",\n\t\t...maxRequestImageBytes === void 0 ? {} : { maxBytes: maxRequestImageBytes },\n\t\tbyteQuantum: 1\n\t});\n}\n/**\n* Return a deterministic transient projection whose oldest images are replaced\n* in whole count and byte quanta after a route budget is exceeded. The target\n* depends only on complete durable history: at 129 one-megabyte images under\n* a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so\n* 64 MiB remain; that removed prefix stays fixed until total history exceeds\n* 192 MiB.\n* @param messages - complete request history, oldest first.\n* @param policy - route representation, budgets, and removal quanta.\n* @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.\n*/\nfunction offloadRequestImagesWithPolicy(messages, policy) {\n\tconst lengths = [];\n\tfor (const message of messages) collectImageLengths(message.content, lengths, policy);\n\tconst total = lengths.reduce((sum, bytes) => sum + bytes, 0);\n\tconst excessCount = policy.maxImages === void 0 ? 0 : Math.max(0, lengths.length - policy.maxImages);\n\tconst excessBytes = policy.maxBytes === void 0 ? 0 : Math.max(0, total - policy.maxBytes);\n\tif (excessCount === 0 && excessBytes === 0) return messages;\n\tconst countQuantum = policy.countQuantum ?? 1;\n\tconst byteQuantum = policy.byteQuantum ?? 1;\n\tconst removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;\n\tconst removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;\n\tlet count = 0;\n\tlet removedBytes = 0;\n\tfor (const imageBytes of lengths) {\n\t\tif (count >= removeCount && (removeBytes === 0 || (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes))) break;\n\t\tremovedBytes += imageBytes;\n\t\tcount += 1;\n\t}\n\tconst remaining = { count };\n\treturn messages.map((message) => {\n\t\tconst content = replaceOldestImages(message.content, remaining);\n\t\treturn content === message.content ? message : {\n\t\t\t...message,\n\t\t\tcontent\n\t\t};\n\t});\n}\n//#endregion\n//#region lib/types/attribution.js\n/**\n* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping\n* adapters from drifting. See\n* `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.\n*\n* App-attribution vocabulary for provider requests.\n* @module @deepseek-ai/dsh-llm/attribution\n*/\nconst { version } = createRequire(import.meta.url)(\"../package.json\");\n/**\n* The harness's own identity: the default every adapter sends. Deployments\n* that need a white-label identity pass their own {@link AppIdentity} to\n* {@link attributionHeaders} — omission falls back to this default; nothing\n* can suppress attribution entirely.\n*/\nconst APP_IDENTITY = {\n\tproduct: \"deepseek-harness\",\n\tversion,\n\turl: \"https://github.com/deepseek-ai/deepseek-harness\"\n};\n/**\n* The standard `User-Agent` value: `product/version (+url)`. The\n* parenthesized `+url` comment is the conventional self-identification form\n* (RFC 9110 §10.1.5 product + comment syntax).\n* @param identity - the identity to render; defaults to {@link APP_IDENTITY}.\n* @returns the ready-to-send header value.\n*/\nfunction userAgent(identity = APP_IDENTITY) {\n\treturn `${identity.product}/${identity.version} (+${identity.url})`;\n}\n/**\n* Build the attribution headers an adapter must send on every provider\n* request. Header names are lowercase (HTTP field names are case-insensitive\n* on the wire).\n* @param identity - the identity to send; defaults to {@link APP_IDENTITY} — omission cannot suppress attribution.\n* @returns headers to merge into the provider request (currently just `user-agent`).\n*/\nfunction attributionHeaders(identity = APP_IDENTITY) {\n\treturn { \"user-agent\": userAgent(identity) };\n}\n//#endregion\n//#region lib/types/never.js\n/**\n* Exhaustiveness helper for closed core unions. Use {@link assertNever} at the default branch so a\n* new variant fails compilation at every required handler. Do not use it for declaration-merged\n* unions such as session events or content blocks: handle known variants and explicitly fall\n* through because plugins may add valid unknown cases.\n* @module @deepseek-ai/dsh-llm/never\n*/\n/**\n* Mark an unreachable closed-union branch. A newly unhandled typed variant fails at the call site;\n* a value that escaped its type throws with diagnostics at runtime.\n* @param value - the impossible value; typed `never` so an unhandled variant fails compilation at the call site.\n* @param context - optional label (e.g. the switch site) prefixed into the throw message.\n* @returns never — it always throws, with the offending value JSON-rendered in the message.\n*/\nfunction assertNever(value, context) {\n\tconst rendered = JSON.stringify(value) ?? String(value);\n\tthrow new Error(`unreachable variant${context ? ` in ${context}` : \"\"}: ${rendered}`);\n}\n//#endregion\n//#region lib/types/assembler.js\n/**\n* Incremental chunk-to-message assembler. This is the single canonical assembly\n* algorithm used by the agent loop to build an assistant message from a chunk\n* stream while logging the raw chunks for replay fidelity.\n*\n* @module @deepseek-ai/dsh-llm/assembler\n*/\n/**\n* Incrementally assembles raw {@link StreamChunk}s into complete\n* {@link ContentBlock}s and a final assistant {@link Message}.\n*\n* The agent loop feeds it while logging raw chunks for replay fidelity, then\n* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,\n* or `interruptedBlocks()` when cancellation cut the stream short.\n*\n* Tolerant of delta-only protocols (no block-start/end); deltas arriving for\n* an index already closed by `block-end` are ignored (malformed stream) so a\n* misbehaving adapter cannot grow memory or corrupt a completed block.\n*/\nvar BlockAssembler = class {\n\tpartials = /* @__PURE__ */ new Map();\n\torder = [];\n\t_usage;\n\t_finish;\n\t_replayState;\n\t/**\n\t* Feed one chunk into the assembly state.\n\t* @param chunk - the next raw chunk, in stream order.\n\t*/\n\tpush(chunk) {\n\t\tswitch (chunk.type) {\n\t\t\tcase \"block-start\":\n\t\t\t\tif (!this.partials.has(chunk.index)) {\n\t\t\t\t\tthis.order.push(chunk.index);\n\t\t\t\t\tthis.partials.set(chunk.index, {\n\t\t\t\t\t\tblockType: chunk.blockType,\n\t\t\t\t\t\ttext: \"\",\n\t\t\t\t\t\ttoolCallArguments: \"\"\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\tcase \"text-delta\":\n\t\t\tcase \"reasoning-delta\": {\n\t\t\t\tconst partial = this.ensure(chunk.index, chunk.type === \"text-delta\" ? \"text\" : \"reasoning\");\n\t\t\t\tif (partial.block) return;\n\t\t\t\tpartial.text += chunk.text;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"tool-call-delta\": {\n\t\t\t\tconst partial = this.ensure(chunk.index, \"tool-call\");\n\t\t\t\tif (partial.block) return;\n\t\t\t\tpartial.toolCallId = chunk.id;\n\t\t\t\tif (chunk.name) partial.toolCallName = chunk.name;\n\t\t\t\tpartial.toolCallArguments += chunk.argumentsDelta;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"block-end\": {\n\t\t\t\tconst partial = this.ensure(chunk.index, chunk.block.type);\n\t\t\t\tif (partial.block) return;\n\t\t\t\tpartial.block = chunk.block;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"usage\":\n\t\t\t\tthis._usage = chunk.usage;\n\t\t\t\treturn;\n\t\t\tcase \"finish\":\n\t\t\t\tthis._finish = chunk.reason;\n\t\t\t\tthis._replayState = chunk.replayState;\n\t\t\t\treturn;\n\t\t\tdefault: return assertNever(chunk, \"BlockAssembler.push\");\n\t\t}\n\t}\n\tensure(index, blockType) {\n\t\tlet partial = this.partials.get(index);\n\t\tif (!partial) {\n\t\t\tpartial = {\n\t\t\t\tblockType,\n\t\t\t\ttext: \"\",\n\t\t\t\ttoolCallArguments: \"\"\n\t\t\t};\n\t\t\tthis.partials.set(index, partial);\n\t\t\tthis.order.push(index);\n\t\t}\n\t\treturn partial;\n\t}\n\tassemble(partial, index) {\n\t\tif (partial.block) return partial.block;\n\t\tswitch (partial.blockType) {\n\t\t\tcase \"text\": return {\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: partial.text\n\t\t\t};\n\t\t\tcase \"reasoning\": return {\n\t\t\t\ttype: \"reasoning\",\n\t\t\t\ttext: partial.text\n\t\t\t};\n\t\t\tcase \"tool-call\": return {\n\t\t\t\ttype: \"tool-call\",\n\t\t\t\tid: partial.toolCallId ?? CallId(`call-${index}`),\n\t\t\t\tname: partial.toolCallName ?? \"\",\n\t\t\t\targuments: partial.toolCallArguments\n\t\t\t};\n\t\t\tdefault: throw new Error(`cannot assemble incomplete block of type \"${partial.blockType}\"`);\n\t\t}\n\t}\n\t/** Invariant accessor: every index in `order` has a partial. */\n\tmustGet(index) {\n\t\tconst partial = this.partials.get(index);\n\t\tif (!partial) throw new Error(`BlockAssembler invariant violated: no partial for index ${index}`);\n\t\treturn partial;\n\t}\n\t/**\n\t* The one shared keep/drop decision over all seen blocks: max-token\n\t* truncation drops tool calls that cannot be executed safely. Emitted blocks\n\t* and replay metadata both derive from this result, so they cannot disagree.\n\t*/\n\tassembled() {\n\t\tconst all = this.order.map((index) => this.assemble(this.mustGet(index), index));\n\t\tconst kept = this.finish.kind === \"max-tokens\" ? all.map((block) => block.type !== \"tool-call\") : void 0;\n\t\tconst blocks = kept === void 0 ? all : all.filter((_, position) => kept[position]);\n\t\tconst envelope = this._replayState;\n\t\tif (envelope?.blocks === void 0) return {\n\t\t\tblocks,\n\t\t\treplay: envelope\n\t\t};\n\t\tif (envelope.blocks.length !== all.length) return {\n\t\t\tblocks,\n\t\t\treplay: void 0\n\t\t};\n\t\treturn {\n\t\t\tblocks,\n\t\t\treplay: kept === void 0 || blocks.length === all.length ? envelope : {\n\t\t\t\tresponse: envelope.response,\n\t\t\t\tblocks: envelope.blocks.filter((_, position) => kept[position])\n\t\t\t}\n\t\t};\n\t}\n\t/**\n\t* Assemble all blocks seen so far, in stream order.\n\t* @returns one block per seen index, except that max-token truncation drops\n\t* tool calls that cannot be executed safely; an open block assembles from\n\t* its accumulated deltas (an unknown block type never closed by `block-end` throws).\n\t*/\n\tblocks() {\n\t\treturn this.assembled().blocks;\n\t}\n\t/**\n\t* Assemble the prefix an interrupted stream can safely finalize: closed and\n\t* open text/reasoning blocks with non-whitespace content, in stream order.\n\t* Tool calls are omitted because interruption precedes dispatch; retaining\n\t* one would require a fabricated result. Open unknown blocks are also omitted.\n\t* @returns the kept blocks; empty when nothing streamed before the interruption.\n\t*/\n\tinterruptedBlocks() {\n\t\treturn this.order.map((index) => {\n\t\t\tconst partial = this.mustGet(index);\n\t\t\tconst type = partial.block?.type ?? partial.blockType;\n\t\t\tif (type !== \"text\" && type !== \"reasoning\") return void 0;\n\t\t\treturn this.assemble(partial, index);\n\t\t}).filter((block) => (block?.type === \"text\" || block?.type === \"reasoning\") && block.text.trim() !== \"\");\n\t}\n\t/** Usage from the `usage` chunk; undefined until one arrives. */\n\tget usage() {\n\t\treturn this._usage;\n\t}\n\t/** Finish reason from the `finish` chunk; `{kind: 'stop'}` when the stream ended without one. */\n\tget finish() {\n\t\treturn this._finish ?? { kind: \"stop\" };\n\t}\n\t/**\n\t* Replay metadata from the terminal finish chunk, if any, with per-block\n\t* entries pruned in step with {@link blocks}. Undefined when the envelope's\n\t* entries do not align with the emitted blocks.\n\t*/\n\tget replayState() {\n\t\treturn this.assembled().replay;\n\t}\n\t/**\n\t* The assembled assistant message.\n\t* @param source - producer attribution for the assembled message.\n\t* @returns a frozen assistant-role message over `blocks()` (same open-block assembly rules).\n\t*/\n\tmessage(source = {\n\t\tkind: \"plugin\",\n\t\tplugin: \"dsh-llm/assembler\"\n\t}) {\n\t\treturn createMessage({\n\t\t\trole: \"assistant\",\n\t\t\tcontent: this.blocks(),\n\t\t\tsource\n\t\t});\n\t}\n};\n//#endregion\n//#region lib/types/index.js\n/**\n* LLM service: adapter registry with a waterfall-interceptable streaming call\n* API. Exports the `LlmRuntime` default, the abstract `LlmAdapter` for\n* provider backends, and `BlockAssembler` for chunk assembly.\n*\n* @module @deepseek-ai/dsh-llm\n*/\n/**\n* Typed error for LLM-related failures. Extends {@link HarnessError}, so the\n* `code` string (e.g. `AUTH`, `RATE_LIMIT`, `NO_ADAPTER`) is shared taxonomy.\n*/\nvar LlmError = class extends HarnessError {\n\t/** Serializable facts retained beside this live Error. */\n\tfailure;\n\t/**\n\t* @param message - non-empty human-readable failure summary.\n\t* @param code - non-empty stable provider-neutral machine code.\n\t* @param options - optional cause and validated serializable provider facts.\n\t*/\n\tconstructor(message, code, options) {\n\t\tif (typeof message !== \"string\" || message.length === 0) throw new Error(\"LlmError message must be a non-empty string\");\n\t\tif (typeof code !== \"string\" || code.length === 0) throw new Error(\"LlmError code must be a non-empty string\");\n\t\tif (options?.status !== void 0 && (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) throw new Error(\"LlmError status must be an integer from 100 through 599\");\n\t\tif (options?.providerRetryAfterMs !== void 0 && (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) throw new Error(\"LlmError providerRetryAfterMs must be a positive finite number\");\n\t\tif (options?.requestId !== void 0 && (typeof options.requestId !== \"string\" || options.requestId.length === 0)) throw new Error(\"LlmError requestId must be a non-empty string\");\n\t\tsuper(message, code, options);\n\t\tthis.name = \"LlmError\";\n\t\tthis.failure = Object.freeze({\n\t\t\tmessage,\n\t\t\tcode,\n\t\t\t...options?.status === void 0 ? {} : { status: options.status },\n\t\t\t...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },\n\t\t\t...options?.requestId === void 0 ? {} : { requestId: options.requestId }\n\t\t});\n\t}\n};\n/**\n* Accept one supplied credential, or refuse it as unusable.\n*\n* A stored key arrives from the credentials seam, a `.env` line, or a shell\n* export, all of which pick up surrounding whitespace, so trimming is silent.\n* Anything else fails here rather than inside `fetch`, whose ByteString\n* refusal names a UTF-16 code point instead of the setting to change. The key\n* never enters the message: `ref` names where to fix it, and echoing any part\n* of a secret into a log or a UI is the failure this diagnosis avoids.\n*\n* Lives beside {@link LlmError} rather than in `./api-key.ts` so the predicate\n* module stays dependency-free; both adapters share this one diagnosis instead\n* of keeping near-identical local copies.\n* @param raw - the credential exactly as supplied.\n* @param pkg - the refusing package name, prefixed to the diagnostic.\n* @param ref - the credential reference the value resolved through.\n* @returns the trimmed, usable key.\n*/\nfunction assertUsableApiKey(raw, pkg, ref) {\n\tconst checked = normalizeApiKey(raw);\n\tif (checked.ok) return checked.value;\n\tthrow new LlmError(checked.reason === \"empty\" ? `${pkg}: the API key resolved from ${ref} is blank; set ${ref} to the raw key (the web Models page writes it) or export it in the launching environment` : `${pkg}: the API key resolved from ${ref} contains characters no HTTP header can carry; set ${ref} to the raw key alone (the web Models page writes it)`, INVALID_CREDENTIAL_CODE);\n}\n/**\n* Provider-wire adapter for the harness message and stream vocabulary. Register implementations\n* with `ctx.llm.registerAdapter(providers, adapter)`. Every provider HTTP request must include\n* `attributionHeaders()`; prove the headers are added in the wire request or library header hook. The direct-fetch\n* DeepSeek and library-backed pi-ai adapters meet this contract through different internals.\n*/\nvar LlmAdapter = class {\n\t/**\n\t* Describe one provider route owned by this adapter.\n\t* @param provider - a route passed to `registerAdapter()` for this instance.\n\t* @returns detached display metadata whose id must equal `provider`.\n\t*/\n\tproviderInfo(provider) {\n\t\treturn {\n\t\t\tid: provider,\n\t\t\tname: provider\n\t\t};\n\t}\n\t/**\n\t* Return the provider-owned retry policy captured with this route.\n\t* @param _provider - a route passed to `registerAdapter()` for this instance.\n\t* @returns a resolved policy, or `undefined` to use the normal defaults.\n\t*/\n\tproviderRetryPolicy(_provider) {}\n\t/**\n\t* List models this adapter can currently advertise for one owned provider.\n\t* The result is advisory: an adapter may accept unlisted model ids, and\n\t* consumers must not turn absence into request rejection.\n\t* @param _provider - one provider route owned by this adapter.\n\t* @returns discoverable models in adapter-preferred order.\n\t*/\n\tlistModels(_provider) {\n\t\treturn Promise.resolve([]);\n\t}\n\t/**\n\t* Resolve all metadata available for one exact model. This query is\n\t* independent of the advisory catalog and does not validate request routing.\n\t* @param provider - one provider route owned by this adapter.\n\t* @param model - exact model id passed to {@link GenerateOptions.model}.\n\t* @param _signal - cancellation for this exact-model lookup; asynchronous\n\t* implementations must settle promptly after it aborts.\n\t* @returns provider/model identity plus any context, call-default, and reasoning metadata.\n\t*/\n\tresolveModel(provider, model, _signal) {\n\t\treturn Promise.resolve({\n\t\t\tprovider,\n\t\t\tid: model,\n\t\t\tname: model\n\t\t});\n\t}\n\t/**\n\t* Bind exact model metadata and the eventual request dispatch to one adapter generation.\n\t* Dynamic adapters override this so settings changes between preparation and\n\t* dispatch cannot combine one generation's capabilities with another's endpoint.\n\t* @param provider - registered provider route.\n\t* @param model - exact model id.\n\t* @param signal - cancellation for model resolution.\n\t* @returns model metadata and a one-generation stream entry point.\n\t*/\n\tasync prepareCall(provider, model, signal) {\n\t\treturn {\n\t\t\tmodel: await this.resolveModel(provider, model, signal),\n\t\t\tstream: (options) => this.stream(options)\n\t\t};\n\t}\n};\n/**\n* The abstract `llm` service: an adapter registry plus a streaming model-call\n* API, interceptable via the `llm/stream` waterfall.\n*/\nvar LlmRuntime = class extends Service {\n\tadapters = /* @__PURE__ */ new Map();\n\tdirectory = /* @__PURE__ */ new Map();\n\tdiscoveries = /* @__PURE__ */ new Map();\n\tconstructor(ctx) {\n\t\tsuper(ctx, \"llm\");\n\t}\n\t/** Notify topology observers without letting one broken listener veto the commit. */\n\temitAdaptersUpdated() {\n\t\tlet invariantFailure;\n\t\tfor (const listener of this.ctx.events.dispatch(\"emit\", [\"llm/adapters-updated\"])) try {\n\t\t\tconst returned = listener();\n\t\t\tif (returned != null && typeof returned.then === \"function\") Promise.resolve(returned).then(void 0, (error) => {\n\t\t\t\tthis.warnAdaptersListenerFailure(error);\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (error?.code === \"INVARIANT\") {\n\t\t\t\tinvariantFailure ??= error;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.warnAdaptersListenerFailure(error);\n\t\t}\n\t\tif (invariantFailure !== void 0) throw invariantFailure;\n\t}\n\t/** Contained-listener diagnostic shared by the sync and async failure paths. */\n\twarnAdaptersListenerFailure(error) {\n\t\tthis.ctx.logger.warn(\"llm: an llm/adapters-updated listener failed\");\n\t\tthis.ctx.logger.warn(error);\n\t}\n\t/**\n\t* Register an adapter for the given provider routes. Throws `LlmError` with code\n\t* `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing).\n\t* Disposed with the fiber.\n\t* @param providers - every provider route this adapter should serve.\n\t* @param adapter - the adapter that streams calls for those providers.\n\t* @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.\n\t*/\n\tregisterAdapter(providers, adapter) {\n\t\tconst owned = /* @__PURE__ */ new Set();\n\t\tlet released = false;\n\t\tconst dispose = this.ctx.effect(function* () {\n\t\t\tif (providers.length === 0) throw new LlmError(\"an adapter must register at least one provider\", \"INVALID_ADAPTER\");\n\t\t\tthis.commitRoutes(owned, this.prepareRoutes(providers, adapter, owned));\n\t\t\tyield () => {\n\t\t\t\treleased = true;\n\t\t\t\tfor (const provider of owned) this.adapters.delete(provider);\n\t\t\t\towned.clear();\n\t\t\t\tthis.emitAdaptersUpdated();\n\t\t\t};\n\t\t}.bind(this), \"llm.registerAdapter()\");\n\t\tconst handle = (() => void dispose());\n\t\thandle.replace = (next) => {\n\t\t\tif (released) throw new LlmError(\"a disposed adapter registration cannot replace its routes\", \"REGISTRATION_DISPOSED\");\n\t\t\tthis.commitRoutes(owned, this.prepareRoutes(next, adapter, owned));\n\t\t};\n\t\treturn handle;\n\t}\n\t/**\n\t* Validate one candidate route set for `adapter`, treating routes this\n\t* registration already holds as available. Nothing is mutated: a rejected\n\t* candidate leaves the registry exactly as it was.\n\t*/\n\tprepareRoutes(providers, adapter, owned) {\n\t\tconst unique = /* @__PURE__ */ new Set();\n\t\tconst registrations = [];\n\t\tfor (const provider of providers) {\n\t\t\tif (provider.length === 0) throw new LlmError(\"adapter provider names must be non-empty\", \"INVALID_ADAPTER\");\n\t\t\tif (unique.has(provider) || this.adapters.has(provider) && !owned.has(provider)) throw new LlmError(`an adapter for provider \"${provider}\" is already registered`, \"DUPLICATE_ADAPTER\");\n\t\t\tconst info = adapter.providerInfo(provider);\n\t\t\tif (typeof info.id !== \"string\" || info.id !== provider || typeof info.name !== \"string\" || info.name.length === 0) throw new LlmError(`adapter metadata for provider \"${provider}\" must preserve its id and have a non-empty name`, \"INVALID_ADAPTER\");\n\t\t\tunique.add(provider);\n\t\t\tconst retryPolicy = adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(void 0, `llm: provider \"${provider}\" retryPolicy`);\n\t\t\tregistrations.push({\n\t\t\t\tadapter,\n\t\t\t\tprovider: {\n\t\t\t\t\tid: info.id,\n\t\t\t\t\tname: info.name\n\t\t\t\t},\n\t\t\t\tretryPolicy\n\t\t\t});\n\t\t}\n\t\treturn registrations;\n\t}\n\t/**\n\t* Swap this registration's routes for the prepared ones in one synchronous\n\t* section, so no observer can see the registry between the release and the\n\t* re-registration. The route set's one mutation point is also where\n\t* `llm/adapters-updated` is published, so a `replace` announces itself\n\t* exactly like a first registration.\n\t*/\n\tcommitRoutes(owned, registrations) {\n\t\tfor (const provider of owned) this.adapters.delete(provider);\n\t\towned.clear();\n\t\tfor (const registration of registrations) {\n\t\t\tthis.adapters.set(registration.provider.id, registration);\n\t\t\towned.add(registration.provider.id);\n\t\t}\n\t\tthis.emitAdaptersUpdated();\n\t}\n\t/**\n\t* Describe provider routes with a registered adapter.\n\t* @returns detached provider metadata in registration order.\n\t*/\n\tlistProviders() {\n\t\treturn [...this.adapters.values()].map(({ provider }) => ({ ...provider }));\n\t}\n\t/**\n\t* Declare provider routes an adapter plugin can activate through\n\t* configuration. Registration is all-or-nothing: an empty list, invalid\n\t* entry, or a provider already declared by any registration throws\n\t* `LlmError` without registering the rest. Disposed with the fiber.\n\t* @param entries - every configurable provider this plugin owns.\n\t* @returns a handle that withdraws all of them, and can atomically replace them.\n\t*/\n\tregisterConfigurableProviders(entries) {\n\t\tlet held = [];\n\t\tlet disposed = false;\n\t\t/**\n\t\t* Validate a candidate set in full against everything this registration\n\t\t* does not already hold, then publish it. Nothing is written until the\n\t\t* whole set passes, so a refused candidate leaves the current entries in\n\t\t* place — the property that makes `replace` a swap rather than a\n\t\t* delete-then-add that can strand the directory empty.\n\t\t*/\n\t\tconst commit = (candidates) => {\n\t\t\tconst detached = [];\n\t\t\tconst own = new Set(held.map((entry) => entry.provider));\n\t\t\tfor (const entry of candidates) {\n\t\t\t\tif (entry.provider.length === 0 || entry.displayName.length === 0 || entry.settingsNs.length === 0) throw new LlmError(\"configurable providers need a non-empty provider, displayName, and settingsNs\", \"INVALID_DIRECTORY\");\n\t\t\t\tif (entry.settingsPath.some((segment) => segment.length === 0)) throw new LlmError(`configurable provider \"${entry.provider}\" has an empty settingsPath segment`, \"INVALID_DIRECTORY\");\n\t\t\t\tif (this.directory.has(entry.provider) && !own.has(entry.provider) || detached.some((seen) => seen.provider === entry.provider)) throw new LlmError(`configurable provider \"${entry.provider}\" is already declared`, \"DUPLICATE_DIRECTORY\");\n\t\t\t\tdetached.push({\n\t\t\t\t\t...entry,\n\t\t\t\t\tsettingsPath: [...entry.settingsPath]\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const entry of held) this.directory.delete(entry.provider);\n\t\t\tfor (const entry of detached) this.directory.set(entry.provider, entry);\n\t\t\theld = detached;\n\t\t\tthis.emitAdaptersUpdated();\n\t\t};\n\t\tconst dispose = this.ctx.effect(function* () {\n\t\t\tif (entries.length === 0) throw new LlmError(\"a configurable-provider registration must declare at least one provider\", \"INVALID_DIRECTORY\");\n\t\t\tcommit(entries);\n\t\t\tyield () => {\n\t\t\t\tdisposed = true;\n\t\t\t\tfor (const entry of held) this.directory.delete(entry.provider);\n\t\t\t\theld = [];\n\t\t\t\tthis.emitAdaptersUpdated();\n\t\t\t};\n\t\t}.bind(this), \"llm.registerConfigurableProviders()\");\n\t\tconst handle = (() => void dispose());\n\t\thandle.replace = (next) => {\n\t\t\tif (disposed) throw new LlmError(\"this configurable-provider registration was disposed\", \"REGISTRATION_DISPOSED\");\n\t\t\tcommit(next);\n\t\t};\n\t\treturn handle;\n\t}\n\t/**\n\t* List every declared configurable provider, registered or dormant.\n\t* @returns detached directory entries in declaration order.\n\t*/\n\tlistConfigurableProviders() {\n\t\treturn [...this.directory.values()].map((entry) => ({\n\t\t\t...entry,\n\t\t\tsettingsPath: [...entry.settingsPath]\n\t\t}));\n\t}\n\t/**\n\t* Offer to interrogate provider endpoints on behalf of the settings\n\t* namespace this plugin owns. The namespace is the key because that is what\n\t* a configuration surface already holds from the configurable-provider\n\t* directory, and because a provider being *added* has no route to name yet.\n\t* Disposed with the fiber.\n\t* @param settingsNs - the namespace whose profiles this discovery serves.\n\t* @param discover - interrogates one endpoint; must honor `request.signal`.\n\t* @returns the disposer that withdraws the offer.\n\t*/\n\tregisterModelDiscovery(settingsNs, discover) {\n\t\tconst dispose = this.ctx.effect(function* () {\n\t\t\tif (settingsNs.length === 0) throw new LlmError(\"model discovery needs a non-empty settings namespace\", \"INVALID_DISCOVERY\");\n\t\t\tif (this.discoveries.has(settingsNs)) throw new LlmError(`model discovery for \"${settingsNs}\" is already registered`, \"DUPLICATE_DISCOVERY\");\n\t\t\tthis.discoveries.set(settingsNs, discover);\n\t\t\tyield () => {\n\t\t\t\tthis.discoveries.delete(settingsNs);\n\t\t\t};\n\t\t}.bind(this), \"llm.registerModelDiscovery()\");\n\t\treturn () => void dispose();\n\t}\n\t/**\n\t* Interrogate one provider endpoint for the models it advertises. The\n\t* request describes a draft, not a stored route, so nothing here reads or\n\t* writes settings or credentials — the caller owns both, and the reply is\n\t* candidate metadata a surface may offer for adoption.\n\t* @param settingsNs - namespace whose registered discovery serves this draft.\n\t* @param request - the endpoint, protocol, and one-shot credential to use.\n\t* @returns the advertised models, deduplicated in endpoint order.\n\t*/\n\tasync discoverModels(settingsNs, request) {\n\t\tconst discover = this.discoveries.get(settingsNs);\n\t\tif (discover === void 0) throw new LlmError(`no model discovery is registered for \"${settingsNs}\"`, \"NO_DISCOVERY\");\n\t\tif ((request.provider ?? \"\").length === 0 && (request.baseURL ?? \"\").length === 0) throw new LlmError(\"model discovery needs a provider route or a baseURL\", \"INVALID_DISCOVERY\");\n\t\tconst discovered = await discover(request);\n\t\tconst seen = /* @__PURE__ */ new Set();\n\t\tconst models = [];\n\t\tfor (const model of discovered) {\n\t\t\tif (typeof model.id !== \"string\" || model.id.length === 0 || seen.has(model.id)) continue;\n\t\t\tseen.add(model.id);\n\t\t\tmodels.push({\n\t\t\t\tid: model.id,\n\t\t\t\t...model.name === void 0 ? {} : { name: model.name },\n\t\t\t\t...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },\n\t\t\t\t...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }\n\t\t\t});\n\t\t}\n\t\treturn models;\n\t}\n\t/**\n\t* Resolve the retry policy captured when one provider route was registered.\n\t* @param provider - registered provider route to inspect.\n\t* @returns the provider-owned policy, with normal defaults already resolved.\n\t*/\n\tproviderRetryPolicy(provider) {\n\t\treturn this.registration(provider).retryPolicy;\n\t}\n\t/** Detach typed adapter-owned modality metadata. */\n\tdetachedModalities(modalities) {\n\t\treturn modalities === void 0 ? void 0 : [...modalities];\n\t}\n\t/**\n\t* Discover models advertised by one registered provider. Catalog membership\n\t* is advisory and never changes routing or request validation.\n\t* @param provider - registered provider route to inspect.\n\t* @returns detached model metadata in adapter-preferred order.\n\t*/\n\tasync listModels(provider) {\n\t\tconst models = await this.registration(provider).adapter.listModels(provider);\n\t\tconst seen = /* @__PURE__ */ new Set();\n\t\treturn models.map((model) => {\n\t\t\tif (typeof model.provider !== \"string\" || model.provider !== provider || typeof model.id !== \"string\" || model.id.length === 0 || typeof model.name !== \"string\" || model.name.length === 0 || model.description !== void 0 && typeof model.description !== \"string\" || seen.has(model.id)) throw new LlmError(`adapter returned invalid or duplicate model metadata for provider \"${provider}\"`, \"INVALID_CATALOG\");\n\t\t\tseen.add(model.id);\n\t\t\tconst inputModalities = this.detachedModalities(model.inputModalities);\n\t\t\treturn {\n\t\t\t\tprovider: model.provider,\n\t\t\t\tid: model.id,\n\t\t\t\tname: model.name,\n\t\t\t\t...model.description === void 0 ? {} : { description: model.description },\n\t\t\t\t...inputModalities === void 0 ? {} : { inputModalities }\n\t\t\t};\n\t\t});\n\t}\n\t/**\n\t* Resolve and validate all metadata from the adapter that owns one exact\n\t* route. The result is detached from adapter-owned objects; catalog\n\t* membership remains advisory and does not control request routing.\n\t* @param provider - registered provider route to inspect.\n\t* @param model - exact model id passed to the adapter.\n\t* @param signal - optional cancellation for adapter-owned asynchronous lookup.\n\t* @returns exact model identity plus available context and reasoning metadata.\n\t*/\n\tasync resolveModelInfo(provider, model, signal) {\n\t\treturn this.resolveModelInfoFor(this.registration(provider), model, signal);\n\t}\n\tasync resolveModelInfoFor(registration, model, signal) {\n\t\tconst resolved = await registration.adapter.resolveModel(registration.provider.id, model, signal);\n\t\treturn this.normalizeModelInfo(registration, model, resolved);\n\t}\n\t/** Validate and detach one adapter-returned exact model result. */\n\tnormalizeModelInfo(registration, model, resolved) {\n\t\tconst provider = registration.provider.id;\n\t\tif (typeof resolved.provider !== \"string\" || resolved.provider !== provider || typeof resolved.id !== \"string\" || resolved.id !== model || typeof resolved.name !== \"string\" || resolved.name.length === 0 || resolved.description !== void 0 && typeof resolved.description !== \"string\") throw new LlmError(`adapter returned invalid exact model metadata for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_INFO\");\n\t\tconst context = resolved.context;\n\t\tif (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_CONTEXT\");\n\t\tconst inputModalities = this.detachedModalities(resolved.inputModalities);\n\t\tconst defaultMaxTokens = resolved.defaultMaxTokens;\n\t\tif (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_MAX_TOKENS\");\n\t\tconst info = {\n\t\t\tprovider,\n\t\t\tid: model,\n\t\t\tname: resolved.name,\n\t\t\t...resolved.description === void 0 ? {} : { description: resolved.description },\n\t\t\t...inputModalities === void 0 ? {} : { inputModalities },\n\t\t\t...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },\n\t\t\t...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens }\n\t\t};\n\t\tconst reasoning = resolved.reasoning;\n\t\tif (reasoning === void 0) return info;\n\t\tif (reasoning.efforts.length === 0) throw new LlmError(`adapter returned invalid reasoning metadata for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_REASONING\");\n\t\tconst seen = /* @__PURE__ */ new Set();\n\t\tconst efforts = reasoning.efforts.map((effort) => {\n\t\t\tif (typeof effort.id !== \"string\" || effort.id.length === 0 || typeof effort.name !== \"string\" || effort.name.length === 0 || effort.description !== void 0 && typeof effort.description !== \"string\" || seen.has(effort.id)) throw new LlmError(`adapter returned invalid or duplicate reasoning effort metadata for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_REASONING\");\n\t\t\tseen.add(effort.id);\n\t\t\treturn {\n\t\t\t\tid: effort.id,\n\t\t\t\tname: effort.name,\n\t\t\t\t...effort.description === void 0 ? {} : { description: effort.description }\n\t\t\t};\n\t\t});\n\t\tif (reasoning.defaultEffort !== void 0 && !seen.has(reasoning.defaultEffort)) throw new LlmError(`adapter returned an unknown default reasoning effort for provider \"${provider}\" model \"${model}\"`, \"INVALID_MODEL_REASONING\");\n\t\treturn {\n\t\t\t...info,\n\t\t\treasoning: {\n\t\t\t\tefforts,\n\t\t\t\t...reasoning.defaultEffort === void 0 ? {} : { defaultEffort: reasoning.defaultEffort }\n\t\t\t}\n\t\t};\n\t}\n\t/**\n\t* Validate a conversation call config against its exact model capability and\n\t* materialize adapter-configured defaults. Unsupported explicit efforts\n\t* reject before provider I/O; no clamping or aliasing is performed. This\n\t* standalone query does not bind a later dispatch; use {@link prepareCall}\n\t* when logging and streaming must share one adapter registration.\n\t* @param config - provider/model route and optional request controls.\n\t* @param signal - optional cancellation for adapter-owned capability lookup.\n\t* @returns a detached config only when a default must be materialized.\n\t*/\n\tasync resolveCallConfig(config, signal) {\n\t\treturn (await this.resolveCallFor(this.registration(config.provider), config, signal)).config;\n\t}\n\tasync resolveCallFor(registration, config, signal) {\n\t\tconst info = await this.resolveModelInfoFor(registration, config.model, signal);\n\t\treturn this.resolveCallWithInfo(config, info);\n\t}\n\t/** Validate request controls against one already-bound exact model result. */\n\tresolveCallWithInfo(config, info) {\n\t\tconst defaulted = config.maxTokens === void 0 && info.defaultMaxTokens !== void 0 ? {\n\t\t\t...config,\n\t\t\tmaxTokens: info.defaultMaxTokens\n\t\t} : config;\n\t\tconst reasoning = info.reasoning;\n\t\tconst requested = defaulted.reasoningEffort;\n\t\tlet resolvedConfig = defaulted;\n\t\tif (reasoning === void 0) {\n\t\t\tif (requested !== void 0) throw new LlmError(`provider \"${config.provider}\" model \"${config.model}\" does not support reasoning effort \"${requested}\"`, \"UNSUPPORTED_REASONING_EFFORT\");\n\t\t} else {\n\t\t\tconst effective = requested ?? reasoning.defaultEffort;\n\t\t\tif (effective !== void 0) {\n\t\t\t\tif (!reasoning.efforts.some((effort) => effort.id === effective)) throw new LlmError(`provider \"${config.provider}\" model \"${config.model}\" does not support reasoning effort \"${effective}\"`, \"UNSUPPORTED_REASONING_EFFORT\");\n\t\t\t\tif (requested !== effective) resolvedConfig = {\n\t\t\t\t\t...defaulted,\n\t\t\t\t\treasoningEffort: effective\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tconfig: resolvedConfig,\n\t\t\t...info.context === void 0 ? {} : { context: info.context },\n\t\t\tmodelInfo: info\n\t\t};\n\t}\n\t/**\n\t* Resolve one call under its current adapter registration. The returned\n\t* one-shot handle keeps that registration across header logging and dispatch,\n\t* so HMR cannot combine one adapter's capability result with another adapter.\n\t* @param config - provider/model route and optional request controls.\n\t* @param signal - optional cancellation for adapter-owned capability lookup.\n\t* @returns a prepared config and its registration-bound stream entry point.\n\t*/\n\tasync prepareCall(config, signal) {\n\t\tconst registration = this.registration(config.provider);\n\t\tconst adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);\n\t\tconst modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model);\n\t\tconst resolved = this.resolveCallWithInfo(config, modelInfo);\n\t\tconst resolvedConfig = deepFreeze(structuredClone(resolved.config));\n\t\tconst context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));\n\t\tconst adapterDefaults = deepFreeze({\n\t\t\t...config.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},\n\t\t\t...config.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}\n\t\t});\n\t\tlet dispatched = false;\n\t\treturn Object.freeze({\n\t\t\tconfig: resolvedConfig,\n\t\t\tretryPolicy: registration.retryPolicy,\n\t\t\tadapterDefaults,\n\t\t\t...context === void 0 ? {} : { context },\n\t\t\t...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },\n\t\t\tstream: (options) => {\n\t\t\t\tif (dispatched) throw new LlmError(\"a prepared LLM call can only be dispatched once\", \"INVALID_PREPARED_CALL\");\n\t\t\t\tif (!callConfigEquals(options, resolvedConfig)) throw new LlmError(\"prepared LLM call config changed before adapter dispatch\", \"INVALID_PREPARED_CALL\");\n\t\t\t\tdispatched = true;\n\t\t\t\treturn this.streamWithRegistration(options, {\n\t\t\t\t\tregistration,\n\t\t\t\t\tconfig: resolvedConfig,\n\t\t\t\t\tmodelInfo,\n\t\t\t\t\tdispatch: (options) => adapterCall.stream(options)\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\tregistration(provider) {\n\t\tconst registration = this.adapters.get(provider);\n\t\tif (!registration) throw new LlmError(`no adapter registered for provider \"${provider}\"`, \"NO_ADAPTER\");\n\t\treturn registration;\n\t}\n\t/** Remove replay state whose historical route is owned by another adapter. */\n\tforAdapter(options, adapter) {\n\t\tconst messages = options.messages.map((message) => {\n\t\t\tconst source = message.source;\n\t\t\tif (message.role !== \"assistant\" || source.kind !== \"model\" || source.replayState === void 0) return message;\n\t\t\tif (this.adapters.get(source.provider)?.adapter === adapter) return message;\n\t\t\treturn freezeMessage({\n\t\t\t\t...message,\n\t\t\t\tsource: {\n\t\t\t\t\tkind: \"model\",\n\t\t\t\t\tprovider: source.provider,\n\t\t\t\t\tmodel: source.model\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\tif (messages.every((message, index) => message === options.messages[index])) return options;\n\t\tconst filtered = {\n\t\t\t...options,\n\t\t\tmessages\n\t\t};\n\t\treturn Object.isFrozen(options) ? deepFreeze(filtered) : filtered;\n\t}\n\t/**\n\t* Final adapter boundary. Adapter selection, dispatch, iterator construction,\n\t* and iteration failures become one terminal failure chunk. Middleware and\n\t* downstream consumer failures remain thrown plugin or consumer errors.\n\t*/\n\tasync *adapterStream(options, prepared) {\n\t\tlet iterator;\n\t\ttry {\n\t\t\tconst registration = prepared?.registration ?? this.registration(options.provider);\n\t\t\tconst adapter = registration.adapter;\n\t\t\tlet modelInfo;\n\t\t\tlet resolvedConfig;\n\t\t\tlet dispatch;\n\t\t\tif (prepared === void 0) {\n\t\t\t\tconst adapterCall = await adapter.prepareCall(options.provider, options.model, options.signal);\n\t\t\t\tmodelInfo = this.normalizeModelInfo(registration, options.model, adapterCall.model);\n\t\t\t\tresolvedConfig = this.resolveCallWithInfo(options, modelInfo).config;\n\t\t\t\tdispatch = (options) => adapterCall.stream(options);\n\t\t\t} else {\n\t\t\t\tmodelInfo = prepared.modelInfo;\n\t\t\t\tresolvedConfig = prepared.config;\n\t\t\t\tdispatch = prepared.dispatch;\n\t\t\t}\n\t\t\tif (prepared !== void 0 && !callConfigEquals(options, resolvedConfig)) throw new LlmError(\"prepared LLM call config changed before adapter dispatch\", \"INVALID_PREPARED_CALL\");\n\t\t\tconst resolvedOptions = callConfigEquals(options, resolvedConfig) ? options : Object.isFrozen(options) ? deepFreeze({\n\t\t\t\t...options,\n\t\t\t\t...resolvedConfig\n\t\t\t}) : {\n\t\t\t\t...options,\n\t\t\t\t...resolvedConfig\n\t\t\t};\n\t\t\tconst projectedOptions = modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes(\"image\") && resolvedOptions.messages.some((message) => contentHasImage(message.content)) ? Object.isFrozen(resolvedOptions) ? deepFreeze({\n\t\t\t\t...resolvedOptions,\n\t\t\t\tmessages: projectImagesForTextModel(resolvedOptions.messages)\n\t\t\t}) : {\n\t\t\t\t...resolvedOptions,\n\t\t\t\tmessages: projectImagesForTextModel(resolvedOptions.messages)\n\t\t\t} : resolvedOptions;\n\t\t\titerator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();\n\t\t} catch (error) {\n\t\t\tyield adapterFailureChunk(error, options.signal);\n\t\t\treturn;\n\t\t}\n\t\tlet completed = false;\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tlet item;\n\t\t\t\ttry {\n\t\t\t\t\tconst next = await iterator.next();\n\t\t\t\t\titem = next.done ? { done: true } : {\n\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\tvalue: next.value\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\tcompleted = true;\n\t\t\t\t\tyield adapterFailureChunk(error, options.signal);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (item.done) {\n\t\t\t\t\tcompleted = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tyield item.value;\n\t\t\t}\n\t\t} finally {\n\t\t\tif (!completed) {\n\t\t\t\tconst close = iterator.return?.bind(iterator);\n\t\t\t\tif (close) await close();\n\t\t\t}\n\t\t}\n\t}\n\t/**\n\t* Stream one model call as raw chunks (token-level deltas). Replay state is\n\t* retained only when the same adapter instance owns its historical provider\n\t* and the target provider. Final adapter selection remains fixed through\n\t* asynchronous exact-model resolution and dispatch. Adapter selection,\n\t* dispatch, and iteration failures become terminal `error` or `aborted`\n\t* finish chunks; middleware, nested-call, cleanup, and consumer failures\n\t* remain thrown.\n\t* @param options - the full request; `options.provider` selects the adapter.\n\t* @returns the chunk stream, possibly wrapped by `llm/stream` listeners.\n\t*/\n\tstream(options) {\n\t\treturn this.streamWithRegistration(options);\n\t}\n\tstreamWithRegistration(options, prepared) {\n\t\treturn this.ctx.waterfall(this, \"llm/stream\", options, () => this.adapterStream(options, prepared));\n\t}\n};\n/** Convert one adapter throw into the stream protocol's terminal outcome. */\nfunction adapterFailureChunk(error, signal) {\n\tconst failure = normalizeLlmFailure(error);\n\treturn {\n\t\ttype: \"finish\",\n\t\treason: signal?.aborted || failure.code === \"ABORTED\" ? {\n\t\t\tkind: \"aborted\",\n\t\t\tfailure\n\t\t} : {\n\t\t\tkind: \"error\",\n\t\t\tfailure\n\t\t}\n\t};\n}\n//#endregion\nexport { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmError, LlmRuntime, LlmRuntime as default, MessageId, OFFLOADED_IMAGE_TEXT, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, assertNever, assertUsableApiKey, attributionHeaders, boundContextSummary, callConfigEquals, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, deepFreeze, errorChain, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, markAgentLoopRequest, normalizeApiKey, offloadRequestImages, offloadRequestImagesWithPolicy, projectImagesForTextModel, requestImageHandleText, resolveRetryPolicy, textOnlyImageText, userAgent };\n","// Branded types and graph data structures shared across the Host/Client boundary.\n// Graph heat-update events are declared here via Cordis declaration merging.\n\nexport type Branded<T, Brand> = T & { __brand: Brand };\n\nexport type RepoId = Branded<string, 'RepoId'>;\nexport type SymbolId = Branded<string, 'SymbolId'>;\nexport type NodeId = Branded<string, 'NodeId'>;\nexport type EdgeId = Branded<string, 'EdgeId'>;\n\nexport const RepoId = (id: string): RepoId => id as RepoId;\nexport const SymbolId = (id: string): SymbolId => id as SymbolId;\nexport const NodeId = (id: string): NodeId => id as NodeId;\nexport const EdgeId = (id: string): EdgeId => id as EdgeId;\n\nexport interface GraphNode {\n id: NodeId;\n label: string;\n type: 'function' | 'class' | 'variable' | 'module' | 'interface' | 'type';\n filePath: string;\n lineNumber: number;\n properties: Record<string, unknown>;\n parentId?: NodeId;\n}\n\nexport interface GraphEdge {\n id: EdgeId;\n source: NodeId;\n target: NodeId;\n type: 'call' | 'import' | 'extend' | 'implement' | 'dependency';\n properties: Record<string, unknown>;\n}\n\nexport interface GraphData {\n nodes: GraphNode[];\n edges: GraphEdge[];\n metadata: {\n repoId: RepoId;\n timestamp: number;\n nodeCount: number;\n edgeCount: number;\n truncated?: boolean;\n totalNodeCount?: number;\n totalEdgeCount?: number;\n };\n}\n\nexport type DataSourceType = 'codegraph' | 'lens';\n\nexport interface AdapterResult {\n nodes: GraphNode[];\n edges: GraphEdge[];\n source: DataSourceType;\n timestamp: number;\n error?: string;\n}\n\n// ---- Graph heat-update event payloads -------------------------------------\n\nexport interface GraphUpdatedEvent {\n repoId: string;\n nodeCount: number;\n edgeCount: number;\n timestamp: number;\n}\n\nexport interface GraphDataEvent {\n repoId: string;\n nodes: GraphNode[];\n edges: GraphEdge[];\n timestamp: number;\n}\n\nexport interface RepoImportedEvent {\n repoId: string;\n path: string;\n timestamp: number;\n}\n\nexport interface RepoScannedEvent {\n repoId: string;\n fileCount: number;\n timestamp: number;\n}\n\nexport interface RepoRequestScanEvent {\n path: string;\n timestamp: number;\n}\n\nexport interface SourceOpenEvent {\n filePath: string;\n lineNumber: number;\n}\n\nexport interface PrerequisiteStatusEvent {\n codegraph: boolean;\n lens: boolean;\n timestamp: number;\n}\n\nexport interface GraphInitEvent {\n path: string;\n timestamp: number;\n}\n\nexport interface GraphInitResultEvent {\n success: boolean;\n path: string;\n message: string;\n timestamp: number;\n}\n\nexport interface WatchToggleEvent {\n enabled: boolean;\n path: string;\n timestamp: number;\n}\n\n// ---- Cordis event declaration merging ------------------------------------\n// These are emitted by the host plugin; Client/host listeners subscribe via\n// `ctx.on(...)`. Keep them writer-only so consumers never redefine the contract.\ndeclare module '@deepseek-ai/cordis' {\n interface Events {\n 'codegraph/repo/imported'(event: RepoImportedEvent): void;\n 'codegraph/repo/scanned'(event: RepoScannedEvent): void;\n 'codegraph/repo/request-scan'(event: RepoRequestScanEvent): void;\n 'codegraph/graph/updated'(event: GraphUpdatedEvent): void;\n 'codegraph/graph/data'(event: GraphDataEvent): void;\n 'codegraph/source/open'(event: SourceOpenEvent): void;\n 'codegraph/prerequisite/status'(event: PrerequisiteStatusEvent): void;\n 'codegraph/prerequisite/request'(event: { timestamp: number }): void;\n 'codegraph/graph/init'(event: GraphInitEvent): void;\n 'codegraph/graph/init-result'(event: GraphInitResultEvent): void;\n 'codegraph/watch/toggle'(event: WatchToggleEvent): void;\n }\n interface Context {\n webServer: WebServerService;\n effect(execute: () => (() => void) | void, label?: string): () => void;\n emit<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): void;\n on<K extends keyof Events>(name: K, listener: Events[K]): () => boolean;\n get(name: string, strict?: boolean): unknown;\n provide(name: string, service: unknown): () => void;\n slots: {\n inject(slotName: string, callback: () => void): void;\n register(options: { name: string; id: string; order?: number; label?: () => string }, component?: unknown): (() => void) | void;\n };\n betterSidebar?: {\n registerTab(descriptor: {\n id: string;\n title: string | (() => string);\n icon?: unknown;\n order?: number;\n single?: boolean;\n component: (props: { visible: boolean }) => unknown;\n }): () => void;\n openTab(seed: { type: string }): void;\n };\n spotlight?: {\n registerCommand(cmd: { id: string; title: string; handler: () => void }): (() => void) | void;\n };\n }\n}\n\n// ---- DSH WebServer service (Host-side HTTP route registration) -----------\n\nexport interface IGraphVisualizerService {\n getCurrentGraph(): GraphData | null;\n getGraphData(repoId: string): Promise<GraphData | null>;\n getSymbolDetail(symbolId: string): Promise<unknown>;\n getImpactAnalysis(symbolId: string): Promise<unknown>;\n onGraphUpdate(callback: (data: GraphData) => void): () => void;\n}\n\nexport interface WebServerRoute {\n kind: 'exact' | 'prefix';\n path: string;\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;\n}\n\nexport interface WebServerService {\n register(route: WebServerRoute): () => void;\n registerFallback(handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>): () => void;\n}\n\n// Minimal Node.js HTTP types (avoid pulling in @types/node for client builds)\nexport interface IncomingMessage {\n url?: string;\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n on(event: 'data', handler: (chunk: Buffer) => void): void;\n on(event: 'end', handler: () => void): void;\n on(event: 'error', handler: (err: Error) => void): void;\n}\n\nexport interface ServerResponse {\n writeHead(code: number, headers?: Record<string, string>): void;\n end(data?: string): void;\n}\n\n// ---- DSH Module Loader (runtime global) ----------------------------------\n// Injected by the DSH web app boot script. Client bundles register themselves\n// via __ModuleLoader__.load({ id, factory }) so the shell can wire them into\n// the page (slots, overlays, etc.). The factory receives a `require` function\n// for resolving platform externals (react, react-dom, cordis, etc.).\ninterface ModuleLoader {\n load(registration: { id: string; factory: (require: (id: string) => unknown) => Record<string, unknown> }): void;\n}\n\ndeclare const __ModuleLoader__: ModuleLoader | undefined;","// Lightweight structured logger with an in-memory ring buffer for UI surfacing.\n// Ponytail: no deps, ~60 lines. Levels respect host console when available.\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogEntry {\n ts: number;\n level: LogLevel;\n scope: string;\n message: string;\n data?: unknown;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\nconst RING_SIZE = 200;\n\nclass LoggerImpl {\n private buffer: LogEntry[] = [];\n private listeners = new Set<(entries: LogEntry[]) => void>();\n minLevel: LogLevel = (typeof globalThis !== 'undefined' &&\n (globalThis as unknown as { __CG_DEBUG?: boolean }).__CG_DEBUG) ? 'debug' : 'info';\n\n log(level: LogLevel, scope: string, message: string, data?: unknown): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel]) return;\n const entry: LogEntry = { ts: Date.now(), level, scope, message, data };\n this.buffer.push(entry);\n if (this.buffer.length > RING_SIZE) this.buffer.splice(0, this.buffer.length - RING_SIZE);\n this.emit();\n const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;\n try { fn(`[codegraph:${scope}] ${message}`, data ?? ''); } catch { /* console unavailable */ }\n }\n\n debug(scope: string, message: string, data?: unknown): void { this.log('debug', scope, message, data); }\n info(scope: string, message: string, data?: unknown): void { this.log('info', scope, message, data); }\n warn(scope: string, message: string, data?: unknown): void { this.log('warn', scope, message, data); }\n error(scope: string, message: string, data?: unknown): void { this.log('error', scope, message, data); }\n\n entries(): LogEntry[] { return this.buffer.slice(); }\n clear(): void { this.buffer = []; this.emit(); }\n\n subscribe(fn: (entries: LogEntry[]) => void): () => void {\n this.listeners.add(fn);\n fn(this.entries());\n return () => { this.listeners.delete(fn); };\n }\n\n private emit(): void {\n const snapshot = this.entries();\n for (const fn of this.listeners) { try { fn(snapshot); } catch { /* listener fault */ } }\n }\n}\n\nexport const logger = new LoggerImpl();\n\n// Convenience scoped logger factory.\nexport function scoped(scope: string) {\n return {\n debug: (m: string, d?: unknown) => logger.debug(scope, m, d),\n info: (m: string, d?: unknown) => logger.info(scope, m, d),\n warn: (m: string, d?: unknown) => logger.warn(scope, m, d),\n error: (m: string, d?: unknown) => logger.error(scope, m, d),\n };\n}","// CodeGraphAdapter - adapter for the optional dsh-codegraph data source.\n//\n// Primary path: read the .codegraph/codegraph.db SQLite index directly\n// (node:sqlite, Node >= 22.5). One O(N+E) query returns the whole graph —\n// no N+1 tool calls, works even when dsh-codegraph's tool surface is 'core'\n// (which exposes no full-graph query tool).\n//\n// Fallback path: upstream tools (codegraph_query / codegraph_callers /\n// codegraph_callees) when the DB cannot be read (e.g. remote sandboxed fs).\nimport { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport type { GraphNode, GraphEdge, AdapterResult, DataSourceType } from '../types/index.ts';\nimport { NodeId, EdgeId } from '../types/index.ts';\nimport { scoped } from '../shared/Logger.ts';\n\nconst log = scoped('codegraph-adapter');\n\nconst require = createRequire(import.meta.url);\n\nexport type UpstreamInvoker = (tool: string, args: Record<string, unknown>) => Promise<unknown | null>;\n\n// ---- codegraph.db schema (upstream @colbymchenry/codegraph) ----------------\n\ninterface DbNodeRow {\n id: string;\n kind: string;\n name: string;\n file_path: string;\n start_line: number;\n signature: string | null;\n docstring: string | null;\n is_exported: number;\n}\n\ninterface DbEdgeRow {\n id: number;\n source: string;\n target: string;\n kind: string;\n line: number | null;\n}\n\nconst NODE_KIND_MAP: Record<string, GraphNode['type']> = {\n function: 'function',\n method: 'function',\n class: 'class',\n interface: 'interface',\n type_alias: 'type',\n constant: 'variable',\n variable: 'variable',\n property: 'variable',\n file: 'module',\n import: 'module',\n module: 'module',\n};\n\nconst EDGE_KIND_MAP: Record<string, GraphEdge['type']> = {\n call: 'call',\n calls: 'call',\n import: 'import',\n imports: 'import',\n extend: 'extend',\n extends: 'extend',\n implement: 'implement',\n implements: 'implement',\n contains: 'dependency',\n dependency: 'dependency',\n depends: 'dependency',\n};\n\nexport function mapNodeKind(kind: string): GraphNode['type'] {\n return NODE_KIND_MAP[kind] ?? 'module';\n}\n\nexport function mapEdgeKind(kind: string): GraphEdge['type'] {\n return EDGE_KIND_MAP[kind] ?? 'dependency';\n}\n\n/** Locate the .codegraph/codegraph.db for a workspace path. */\nexport function resolveDbPath(workspacePath: string): string {\n const root = workspacePath && workspacePath !== '.' ? workspacePath : process.cwd();\n return join(root, '.codegraph', 'codegraph.db');\n}\n\n/** Read the whole graph from a codegraph.db. Returns null when unreadable. */\nexport function readGraphFromDb(dbPath: string): { nodes: DbNodeRow[]; edges: DbEdgeRow[] } | null {\n if (!existsSync(dbPath)) return null;\n try {\n // node:sqlite is available on Node >= 22.5 (project engines: >=22.19 || >=24).\n // createRequire keeps the built-in out of the ESM graph while staying sync.\n const { DatabaseSync } = require('node:sqlite') as typeof import('node:sqlite');\n const db = new DatabaseSync(dbPath, { readOnly: true });\n try {\n const nodes = db.prepare(\n 'SELECT id, kind, name, file_path, start_line, signature, docstring, is_exported FROM nodes',\n ).all() as unknown as DbNodeRow[];\n const edges = db.prepare(\n 'SELECT id, source, target, kind, line FROM edges',\n ).all() as unknown as DbEdgeRow[];\n return { nodes, edges };\n } finally {\n db.close();\n }\n } catch (e) {\n log.warn('readGraphFromDb failed', e);\n return null;\n }\n}\n\nexport class CodeGraphAdapter {\n private readonly source: DataSourceType = 'codegraph';\n\n async fetchData(repoId: string, invoke: UpstreamInvoker): Promise<AdapterResult> {\n // Primary: direct SQLite read of the upstream index (single O(N+E) query).\n const dbRows = readGraphFromDb(resolveDbPath(repoId));\n if (dbRows && dbRows.nodes.length > 0) {\n return this.toAdapterResult(dbRows.nodes, dbRows.edges);\n }\n\n // Fallback (DB unreadable): assemble a file-structure skeleton from the\n // upstream `codegraph_files` tool so the panel at least shows the project\n // layout. No relations — the full graph needs the DB.\n const files = await invoke('codegraph_files', { path: repoId, format: 'flat' });\n if (!files || typeof files !== 'object') {\n return { nodes: [], edges: [], source: this.source, timestamp: Date.now() };\n }\n const rawFiles = this.extractFileList(files);\n const nodes: GraphNode[] = rawFiles.map((f, i) => ({\n id: NodeId(`file:${f.path}`),\n label: f.path.split('/').pop() ?? f.path,\n type: 'module',\n filePath: f.path,\n lineNumber: 1,\n properties: { kind: 'file', language: f.language ?? null, index: i },\n }));\n return { nodes, edges: [], source: this.source, timestamp: Date.now() };\n }\n\n private extractFileList(files: unknown): Array<{ path: string; language?: string }> {\n // codegraph_files --json returns either an array of file records or an\n // object with a files/files[] key, depending on CLI version.\n if (Array.isArray(files)) {\n return files.flatMap((f) => {\n if (typeof f === 'string') return [{ path: f }];\n const rec = f as Record<string, unknown>;\n if (typeof rec.path === 'string') return [{ path: rec.path, language: rec.language as string | undefined }];\n return [];\n });\n }\n const obj = files as Record<string, unknown>;\n const inner = obj?.files ?? obj?.items;\n return Array.isArray(inner) ? this.extractFileList(inner) : [];\n }\n\n private toAdapterResult(dbNodes: DbNodeRow[], dbEdges: DbEdgeRow[]): AdapterResult {\n const nodeIds = new Set(dbNodes.map((n) => n.id));\n\n const fileNodeIds = new Map<string, string>();\n for (const n of dbNodes) {\n if (n.kind === 'file' || n.kind === 'module') {\n fileNodeIds.set(n.file_path, n.id);\n }\n }\n\n const nodes: GraphNode[] = dbNodes.map((n) => {\n const parentId = (n.kind !== 'file' && n.kind !== 'module' && n.kind !== 'import')\n ? fileNodeIds.get(n.file_path) ?? undefined\n : undefined;\n return {\n id: NodeId(n.id),\n label: n.name,\n type: mapNodeKind(n.kind),\n filePath: n.file_path,\n lineNumber: n.start_line ?? 1,\n properties: {\n kind: n.kind,\n ...(n.signature ? { signature: n.signature } : {}),\n ...(n.docstring ? { docstring: n.docstring } : {}),\n exported: n.is_exported === 1,\n },\n ...(parentId ? { parentId: NodeId(parentId) } : {}),\n };\n });\n\n const edges: GraphEdge[] = dbEdges\n .filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target))\n .map((e) => ({\n id: EdgeId(String(e.id)),\n source: NodeId(e.source),\n target: NodeId(e.target),\n type: mapEdgeKind(e.kind),\n properties: e.line != null ? { line: e.line } : {},\n }));\n\n return { nodes, edges, source: this.source, timestamp: Date.now() };\n }\n}\n","// LensAdapter - adapter for the optional dsh-tool-lens data source.\nimport type { GraphNode, GraphEdge, AdapterResult, DataSourceType } from '../types/index.ts';\nimport { NodeId, EdgeId } from '../types/index.ts';\nimport type { UpstreamInvoker } from './CodeGraphAdapter.ts';\nimport { scoped } from '../shared/Logger.ts';\n\nconst log = scoped('lens-adapter');\n\ninterface LensToolResult {\n symbols: Array<{\n id: string;\n name: string;\n scope: string;\n file: string;\n line: number;\n category: string;\n }>;\n references: Array<{\n from: string;\n to: string;\n relation: string;\n }>;\n}\n\n\nexport class LensAdapter {\n private readonly source: DataSourceType = 'lens';\n\n async fetchData(repoId: string, invoke: UpstreamInvoker): Promise<AdapterResult> {\n try {\n const raw = (await invoke('lens_analyze', { repoId })) as LensToolResult | null;\n if (!raw) return { nodes: [], edges: [], source: this.source, timestamp: Date.now() };\n\n const symbols = Array.isArray(raw.symbols) ? raw.symbols : [];\n const references = Array.isArray(raw.references) ? raw.references : [];\n\n const fileNodeMap = new Map<string, string>();\n for (const s of symbols) {\n if (s.category === 'module' || s.category === 'file') {\n fileNodeMap.set(s.file, s.id);\n }\n }\n\n const nodes: GraphNode[] = symbols.map(s => {\n const parentId = (s.category !== 'module' && s.category !== 'file')\n ? fileNodeMap.get(s.file)\n : undefined;\n return {\n id: NodeId(s.id),\n label: s.name,\n type: this.mapCategory(s.category),\n filePath: s.file,\n lineNumber: s.line,\n properties: { scope: s.scope },\n ...(parentId ? { parentId: NodeId(parentId) } : {}),\n };\n });\n\n const edges: GraphEdge[] = references.map(r => ({\n id: EdgeId(`${r.from}->${r.to}`),\n source: NodeId(r.from),\n target: NodeId(r.to),\n type: this.mapRelation(r.relation),\n properties: {},\n }));\n\n return { nodes, edges, source: this.source, timestamp: Date.now() };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n log.warn('Lens fetchData failed — lens is optional, returning empty', e);\n return { nodes: [], edges: [], source: this.source, timestamp: Date.now(), error: errorMsg };\n }\n }\n\n private mapCategory(cat: string): GraphNode['type'] {\n const map: Record<string, GraphNode['type']> = {\n function: 'function',\n class: 'class',\n variable: 'variable',\n module: 'module',\n interface: 'interface',\n type: 'type',\n };\n return map[cat] ?? 'module';\n }\n\n private mapRelation(rel: string): GraphEdge['type'] {\n const map: Record<string, GraphEdge['type']> = {\n call: 'call',\n reference: 'dependency',\n import: 'import',\n extend: 'extend',\n implement: 'implement',\n };\n return map[rel] ?? 'dependency';\n }\n}","// GraphDataMerger - multi-source aggregation with dedup + delta merge.\nimport type { GraphNode, GraphEdge, GraphData, AdapterResult } from '../types/index.ts';\nimport { RepoId } from '../types/index.ts';\n\nexport class GraphDataMerger {\n merge(results: AdapterResult[], repoId: RepoId): GraphData {\n const nodes = new Map<string, GraphNode>();\n const edges = new Map<string, GraphEdge>();\n\n for (const r of results) {\n for (const node of r.nodes) {\n nodes.set(node.id, {\n ...node,\n properties: { ...node.properties, __source: r.source },\n });\n }\n for (const edge of r.edges) {\n edges.set(edge.id, {\n ...edge,\n properties: { ...edge.properties, __source: r.source },\n });\n }\n }\n\n return {\n nodes: Array.from(nodes.values()),\n edges: Array.from(edges.values()),\n metadata: {\n repoId: RepoId(repoId),\n timestamp: Date.now(),\n nodeCount: nodes.size,\n edgeCount: edges.size,\n },\n };\n }\n\n // Delta merge for incremental heat-updates.\n applyDelta(current: GraphData, delta: AdapterResult): GraphData {\n const nodes = new Map(current.nodes.map(n => [n.id, n]));\n const edges = new Map(current.edges.map(e => [e.id, e]));\n\n for (const node of delta.nodes) {\n const existing = nodes.get(node.id);\n if (existing) {\n nodes.set(node.id, {\n ...existing,\n ...node,\n properties: { ...existing.properties, ...node.properties },\n });\n } else {\n nodes.set(node.id, node);\n }\n }\n for (const edge of delta.edges) {\n const existing = edges.get(edge.id);\n if (existing) {\n edges.set(edge.id, {\n ...existing,\n ...edge,\n properties: { ...existing.properties, ...edge.properties },\n });\n } else {\n edges.set(edge.id, edge);\n }\n }\n\n return {\n nodes: Array.from(nodes.values()),\n edges: Array.from(edges.values()),\n metadata: {\n ...current.metadata,\n timestamp: Date.now(),\n nodeCount: nodes.size,\n edgeCount: edges.size,\n },\n };\n }\n}","// Graph tool definitions: adapters + merger wrapped as DSH `defineTool` tools.\nimport type { Context } from '@deepseek-ai/cordis';\nimport { defineTool } from '@deepseek-ai/dsh-tools';\nimport { CallId } from '@deepseek-ai/dsh-llm';\nimport type { JsonValue } from '@deepseek-ai/dsh-session';\nimport { CodeGraphAdapter } from './adapters/CodeGraphAdapter.ts';\nimport { LensAdapter } from './adapters/LensAdapter.ts';\nimport { GraphDataMerger } from './merger/GraphDataMerger.ts';\nimport type { AdapterResult, GraphData, GraphUpdatedEvent, GraphDataEvent, RepoId } from './types/index.ts';\nimport { RepoId as makeRepoId } from './types/index.ts';\nimport { scoped } from './shared/Logger.ts';\n\nconst log = scoped('tools');\n\nexport type { UpstreamInvoker } from './adapters/CodeGraphAdapter.ts';\nimport type { UpstreamInvoker } from './adapters/CodeGraphAdapter.ts';\n\nconst codegraphAdapter = new CodeGraphAdapter();\nconst lensAdapter = new LensAdapter();\nconst merger = new GraphDataMerger();\n\nconst GRAPH_CACHE_LIMIT = 8;\nconst graphCache = new Map<string, GraphData>();\n\nfunction cacheGraph(repoId: string, data: GraphData): void {\n graphCache.delete(repoId);\n graphCache.set(repoId, data);\n if (graphCache.size > GRAPH_CACHE_LIMIT) {\n const oldest = graphCache.keys().next().value;\n if (oldest !== undefined) graphCache.delete(oldest);\n }\n}\n\nasync function fetchMergedGraph(\n invoke: UpstreamInvoker,\n repoId: string,\n source: 'codegraph' | 'lens' | 'both' = 'both',\n): Promise<GraphData> {\n const tasks: Promise<AdapterResult>[] = [];\n if (source === 'codegraph' || source === 'both') {\n tasks.push(codegraphAdapter.fetchData(repoId, invoke));\n }\n if (source === 'lens' || source === 'both') {\n tasks.push(lensAdapter.fetchData(repoId, invoke));\n }\n const settled = await Promise.allSettled(tasks);\n const results: AdapterResult[] = settled\n .filter((r): r is PromiseFulfilledResult<AdapterResult> => r.status === 'fulfilled')\n .map((r) => r.value);\n return merger.merge(results, makeRepoId(repoId));\n}\n\nfunction summarizeGraph(data: GraphData): string {\n const nodeByType = new Map<string, number>();\n const edgeByType = new Map<string, number>();\n for (const n of data.nodes) nodeByType.set(n.type, (nodeByType.get(n.type) ?? 0) + 1);\n for (const e of data.edges) edgeByType.set(e.type, (edgeByType.get(e.type) ?? 0) + 1);\n\n const nodeStats = [...nodeByType.entries()].map(([t, c]) => `${t}:${c}`).join(', ');\n const edgeStats = [...edgeByType.entries()].map(([t, c]) => `${t}:${c}`).join(', ');\n\n const topNodes = data.nodes.slice(0, 10)\n .map(n => ` • ${n.label} (${n.type}) @ ${n.filePath}:${n.lineNumber}`)\n .join('\\n');\n\n const topEdges = data.edges.slice(0, 10)\n .map(e => ` ${e.source} →${e.type}→ ${e.target}`)\n .join('\\n');\n\n return [\n `Graph: ${data.metadata.nodeCount} nodes [${nodeStats}], ${data.metadata.edgeCount} edges [${edgeStats}]`,\n topNodes ? `\\nTop nodes:\\n${topNodes}` : '',\n topEdges ? `\\nTop edges:\\n${topEdges}` : '',\n ].join('');\n}\n\nexport function normalizeImpact(raw: unknown): { affected: string[]; depth: number } | null {\n if (typeof raw === 'string') {\n try { raw = JSON.parse(raw) as unknown; } catch { log.warn('normalizeImpact: JSON.parse failed'); return null; }\n }\n if (!raw || typeof raw !== 'object') return null;\n const obj = raw as Record<string, unknown>;\n const list = obj.affected ?? obj.affectedNodes ?? obj.nodes;\n if (!Array.isArray(list)) return null;\n return {\n affected: list.map((it) => {\n if (typeof it === 'string') return it;\n const rec = it as Record<string, unknown>;\n return String(rec.name ?? rec.id ?? '?');\n }),\n depth: typeof obj.depth === 'number' ? obj.depth : 2,\n };\n}\n\nexport function pickBestMatch(raw: unknown, symbolId: string): Record<string, unknown> | null {\n let payload = raw;\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload) as unknown; } catch { log.warn('normalizeCallers: JSON.parse failed'); return null; }\n }\n const items = Array.isArray(payload)\n ? payload\n : (payload as Record<string, unknown>)?.results ?? (payload as Record<string, unknown>)?.nodes;\n if (!Array.isArray(items) || items.length === 0) return null;\n const wanted = symbolId.toLowerCase();\n const best = items.find((it) => {\n const rec = it as Record<string, unknown>;\n return String(rec.name ?? '').toLowerCase() === wanted || rec.id === symbolId;\n }) as Record<string, unknown> | undefined;\n const chosen = best ?? (items[0] as Record<string, unknown>);\n return {\n symbolId,\n name: chosen.name ?? chosen.qualified_name ?? symbolId,\n category: chosen.kind ?? 'unknown',\n file: chosen.filePath ?? chosen.file_path ?? '?',\n line: chosen.startLine ?? chosen.start_line ?? 0,\n signature: chosen.signature ?? null,\n };\n}\n\n// ── Extracted execute/render functions for testability ──────────────────\n\nexport function createInvoke(ctx: Context, requestTimeout = 5000): UpstreamInvoker {\n return async (tool, args) => {\n try {\n const result = await ctx.tools.execute({\n callId: CallId(`codegraph:${tool}`),\n name: tool,\n arguments: args,\n signal: AbortSignal.timeout(requestTimeout),\n });\n if (result.isError) return null;\n return result.value ?? null;\n } catch (e) {\n log.warn('createInvoke: tool execution failed', { tool, error: e });\n return null;\n }\n };\n}\n\nexport function renderGraphStatus(_args: unknown, value: unknown) {\n const v = value as { status: string; nodeCount: number; edgeCount: number; sources?: Record<string, boolean> };\n const srcInfo = v.sources ? ` [codegraph:${v.sources.codegraph ? '✓' : '✗'} lens:${v.sources.lens ? '✓' : '✗'}]` : '';\n return [\n { type: 'text' as const, text: `Graph status: ${v.status} (${v.nodeCount} nodes, ${v.edgeCount} edges)${srcInfo}` },\n ];\n}\n\nexport async function executeGraphStatus(args: { repoId: string }, invoke: UpstreamInvoker) {\n const [cgResult, lensResult] = await Promise.allSettled([\n codegraphAdapter.fetchData(args.repoId, invoke),\n lensAdapter.fetchData(args.repoId, invoke),\n ]);\n const results: AdapterResult[] = [cgResult, lensResult]\n .filter((r): r is PromiseFulfilledResult<AdapterResult> => r.status === 'fulfilled')\n .map((r) => r.value);\n const data = merger.merge(results, makeRepoId(args.repoId));\n return {\n status: data.nodes.length > 0 ? 'ready' : 'unavailable',\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n sources: {\n codegraph: cgResult.status === 'fulfilled' && cgResult.value.nodes.length > 0,\n lens: lensResult.status === 'fulfilled' && lensResult.value.nodes.length > 0,\n },\n };\n}\n\nexport function renderGraphData(_args: unknown, value: unknown) {\n const data = value as unknown as GraphData;\n return [{ type: 'text' as const, text: summarizeGraph(data) }];\n}\n\nexport async function executeGraphData(\n args: { repoId: string; source?: string },\n invoke: UpstreamInvoker,\n emitUpdate: (event: GraphUpdatedEvent) => void,\n emitData: (event: GraphDataEvent) => void,\n) {\n const source = (args.source ?? 'both') as 'codegraph' | 'lens' | 'both';\n const fresh = await fetchMergedGraph(invoke, args.repoId, source);\n\n const cached = graphCache.get(args.repoId);\n const deltaSource: AdapterResult['source'] = source === 'lens' ? 'lens' : 'codegraph';\n const merged = cached\n ? merger.applyDelta(cached, { nodes: fresh.nodes, edges: fresh.edges, source: deltaSource, timestamp: fresh.metadata.timestamp })\n : fresh;\n cacheGraph(args.repoId, merged);\n\n emitUpdate({\n repoId: args.repoId,\n nodeCount: merged.metadata.nodeCount,\n edgeCount: merged.metadata.edgeCount,\n timestamp: merged.metadata.timestamp,\n });\n emitData({\n repoId: args.repoId,\n nodes: merged.nodes,\n edges: merged.edges,\n timestamp: merged.metadata.timestamp,\n });\n return merged as unknown as JsonValue;\n}\n\nexport function renderGraphSymbol(_args: unknown, value: unknown) {\n const v = value as { name?: string; file?: string; line?: number; category?: string; symbolId?: string } | null;\n if (!v) return [{ type: 'text' as const, text: 'Symbol not found.' }];\n return [{ type: 'text' as const, text: `${v.name ?? v.symbolId ?? '?'} (${v.category ?? 'unknown'}) @ ${v.file ?? '?'}:${v.line ?? '?'}` }];\n}\n\nexport async function executeGraphSymbol(args: { symbolId: string }, invoke: UpstreamInvoker) {\n const raw = await invoke('codegraph_query', { search: args.symbolId, limit: 1 });\n return pickBestMatch(raw, args.symbolId) as JsonValue;\n}\n\nexport function renderGraphImpact(_args: unknown, value: unknown) {\n const v = value as { affected?: string[]; depth?: number };\n const count = v.affected?.length ?? 0;\n const list = v.affected?.slice(0, 10).join(', ') ?? '';\n return [{ type: 'text' as const, text: `Impact: ${count} symbols affected (depth ${v.depth ?? 0})${list ? `\\n ${list}` : ''}` }];\n}\n\nexport async function executeGraphImpact(args: { symbolId: string }, invoke: UpstreamInvoker) {\n const raw = await invoke('codegraph_impact', { symbol: args.symbolId, depth: 2 });\n const normalized = normalizeImpact(raw);\n if (normalized) return normalized as JsonValue;\n const lens = await invoke('lens_impact', { symbolId: args.symbolId });\n return (lens ?? { affected: [], depth: 0 }) as JsonValue;\n}\n\n// ── Tool factory ────────────────────────────────────────────────────────\n\nexport const createGraphTools = (ctx: Context, options: { requestTimeout?: number } = {}) => {\n const invoke = createInvoke(ctx, options.requestTimeout);\n\n const emitUpdate = (event: GraphUpdatedEvent) => {\n ctx.emit('codegraph/graph/updated', event);\n };\n\n const emitData = (event: GraphDataEvent) => {\n ctx.emit('codegraph/graph/data', event);\n };\n\n const graphStatus = defineTool({\n name: 'graph_status',\n description: 'Check whether an interactive code graph is available for a repository.',\n parameters: {\n repoId: { type: 'string', required: true, description: 'Repository id.' },\n },\n output: {\n schema: { type: 'json' },\n render: renderGraphStatus,\n },\n execute: (args) => executeGraphStatus(args as { repoId: string }, invoke),\n });\n\n const graphData = defineTool({\n name: 'graph_data',\n description: 'Fetch the merged code relationship graph for a repository (calls + dependencies). Uses incremental delta merge on repeat calls.',\n parameters: {\n repoId: { type: 'string', required: true, description: 'Repository id.' },\n source: {\n type: 'string',\n enum: ['codegraph', 'lens', 'both'],\n description: 'Data source; defaults to both.',\n },\n },\n output: {\n schema: { type: 'json' },\n render: renderGraphData,\n },\n execute: (args) => executeGraphData(args as { repoId: string; source?: string }, invoke, emitUpdate, emitData),\n });\n\n const graphSymbol = defineTool({\n name: 'graph_symbol',\n description: 'Resolve the details (file path and line number) of one graph node by symbol id.',\n parameters: {\n symbolId: { type: 'string', required: true, description: 'Symbol id.' },\n },\n output: {\n schema: { type: 'json' },\n render: renderGraphSymbol,\n },\n execute: (args) => executeGraphSymbol(args as { symbolId: string }, invoke),\n });\n\n const graphImpact = defineTool({\n name: 'graph_impact',\n description: 'Analyze the impact of changing one symbol: which symbols are transitively affected.',\n parameters: {\n symbolId: { type: 'string', required: true, description: 'Symbol id to analyze.' },\n },\n output: {\n schema: { type: 'json' },\n render: renderGraphImpact,\n },\n execute: (args) => executeGraphImpact(args as { symbolId: string }, invoke),\n });\n\n return { graphStatus, graphData, graphSymbol, graphImpact };\n};\n\nexport { fetchMergedGraph, summarizeGraph };\nexport const makeRepo = (id: string): RepoId => makeRepoId(id);\n","export const PLUGIN_VERSION = '0.1.1';\n","export interface VisualizerConfig {\n dataSource: 'auto' | 'codegraph' | 'lens';\n requestTimeout: number;\n scanCacheTtl: number;\n scanCacheLimit: number;\n maxBodyBytes: number;\n prerequisiteRetryDelay: number;\n watchDebounce: number;\n maxNodes: number;\n}\n\nexport const DEFAULT_CONFIG: VisualizerConfig = {\n dataSource: 'auto',\n requestTimeout: 5000,\n scanCacheTtl: 30_000,\n scanCacheLimit: 4,\n maxBodyBytes: 1024 * 1024,\n prerequisiteRetryDelay: 3000,\n watchDebounce: 500,\n maxNodes: 10_000,\n};\n\nexport function resolveConfig(userConfig?: Partial<VisualizerConfig>): VisualizerConfig {\n const config: VisualizerConfig = { ...DEFAULT_CONFIG, ...userConfig };\n const errors: string[] = [];\n if (config.dataSource !== 'auto' && config.dataSource !== 'codegraph' && config.dataSource !== 'lens') {\n errors.push(`dataSource must be auto|codegraph|lens, got ${String(config.dataSource)}`);\n }\n const positiveFields: Array<[keyof VisualizerConfig, number]> = [\n ['requestTimeout', config.requestTimeout],\n ['scanCacheTtl', config.scanCacheTtl],\n ['scanCacheLimit', config.scanCacheLimit],\n ['maxBodyBytes', config.maxBodyBytes],\n ['prerequisiteRetryDelay', config.prerequisiteRetryDelay],\n ['watchDebounce', config.watchDebounce],\n ['maxNodes', config.maxNodes],\n ];\n for (const [field, value] of positiveFields) {\n if (!Number.isFinite(value) || value <= 0) {\n errors.push(`${field} must be a positive finite number, got ${String(value)}`);\n }\n }\n if (errors.length > 0) {\n throw new Error(`[dsh-codegraph-visualizer] invalid config: ${errors.join('; ')}`);\n }\n return config;\n}","import { normalize, isAbsolute } from 'node:path';\n\nlet allowedWorkspaceRoots: string[] = [];\n\nexport function setAllowedWorkspaceRoots(roots: string[]): void {\n allowedWorkspaceRoots = roots.map(r => normalize(r));\n}\n\nexport function isPathAllowed(path: string): boolean {\n if (!path || path === '.') return true;\n const normalized = normalize(path);\n if (!isAbsolute(normalized)) return false;\n if (normalized.includes('..')) return false;\n if (allowedWorkspaceRoots.length === 0) return false;\n return allowedWorkspaceRoots.some((root) => normalized === root || normalized.startsWith(root + '\\\\') || normalized.startsWith(root + '/'));\n}","import { spawnSync } from 'node:child_process';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { scoped } from '../shared/Logger.ts';\n\nconst log = scoped('host');\n\nexport interface ContextWithSessions {\n sessions?: { list?: () => Array<{ header?: { cwd?: string } }> };\n workspaceRegistry?: { list?: () => Array<{ path?: string }> };\n}\n\nexport function detectCodegraphCli(): boolean {\n try {\n const cmd = process.platform === 'win32' ? 'codegraph.cmd' : 'codegraph';\n const r = spawnSync(cmd, ['--version'], { encoding: 'utf8', timeout: 3000, shell: process.platform === 'win32' });\n return r.status === 0;\n } catch (e) {\n log.warn('detectCodegraphCli failed', e);\n return false;\n }\n}\n\nexport function checkPrerequisites(ctx: Context): { codegraph: boolean; lens: boolean } {\n try {\n const cg = ctx.tools.get('codegraph_status') ?? ctx.tools.get('codegraph_graph') ?? ctx.tools.get('codegraph_query');\n const lens = ctx.tools.get('lens_analyze');\n return { codegraph: !!cg || detectCodegraphCli(), lens: !!lens };\n } catch (e) {\n log.warn('checkPrerequisites failed', e);\n return { codegraph: detectCodegraphCli(), lens: false };\n }\n}\n\nexport function extractWorkspacePaths(ctx: Context): string[] {\n try {\n const wsr = (ctx as unknown as ContextWithSessions).workspaceRegistry;\n if (wsr?.list) {\n const workspaces = wsr.list();\n const paths = workspaces.map((w) => w.path).filter((p): p is string => !!p);\n if (paths.length > 0) return paths;\n }\n } catch (e) { log.warn('extractWorkspacePaths: workspaceRegistry failed', e); }\n try {\n const sessions = (ctx as unknown as ContextWithSessions).sessions;\n if (sessions?.list) {\n const all = sessions.list();\n const seen = new Set<string>();\n const paths: string[] = [];\n for (const session of all) {\n const cwd = session?.header?.cwd;\n if (cwd && !seen.has(cwd)) {\n seen.add(cwd);\n paths.push(cwd);\n }\n }\n if (paths.length > 0) return paths;\n }\n } catch (e) { log.warn('extractWorkspacePaths: sessions failed', e); }\n return [process.cwd()];\n}\n\nexport function findWorkspacePath(ctx: Context): string {\n try {\n const wsr = (ctx as unknown as ContextWithSessions).workspaceRegistry;\n if (wsr?.list) {\n const workspaces = wsr.list();\n if (workspaces.length > 0 && workspaces[0]?.path) {\n return workspaces[0].path;\n }\n }\n } catch (e) { log.warn('findWorkspacePath: workspaceRegistry failed', e); }\n try {\n const sessions = (ctx as unknown as ContextWithSessions).sessions;\n if (sessions?.list) {\n const all = sessions.list();\n for (const session of all) {\n const cwd = session?.header?.cwd;\n if (cwd) return cwd;\n }\n }\n } catch (e) { log.warn('findWorkspacePath: sessions failed', e); }\n return process.cwd();\n}","import type { Context } from '@deepseek-ai/cordis';\nimport { CallId } from '@deepseek-ai/dsh-llm';\nimport { scoped } from '../shared/Logger.ts';\nimport type { GraphData, GraphNode, GraphEdge, IncomingMessage, ServerResponse, IGraphVisualizerService } from '../types/index.ts';\nimport { fetchMergedGraph } from '../tools.ts';\nimport { isPathAllowed } from './security.ts';\nimport { findWorkspacePath, extractWorkspacePaths, checkPrerequisites } from './prerequisites.ts';\nimport type { VisualizerConfig } from './config.ts';\n\nconst log = scoped('host');\n\nexport interface RouteDeps {\n ctx: Context;\n config: VisualizerConfig;\n getLastGraphData: () => GraphData | null;\n setLastGraphData: (data: GraphData | null) => void;\n getLastInitResult: () => { success: boolean; path: string; message: string; timestamp: number } | null;\n setLastInitResult: (data: { success: boolean; path: string; message: string; timestamp: number } | null) => void;\n getScanCache: () => Map<string, { data: GraphData; timestamp: number }>;\n getScanInFlight: () => Promise<GraphData> | null;\n setScanInFlight: (p: Promise<GraphData> | null) => void;\n}\n\nexport function slimGraphData(data: GraphData, maxNodes: number): GraphData {\n const cap = maxNodes;\n const truncated = data.nodes.length > cap;\n const slimNodes = (truncated ? data.nodes.slice(0, cap) : data.nodes).map((n) => ({\n id: n.id,\n label: n.label,\n type: n.type,\n filePath: n.filePath,\n lineNumber: n.lineNumber,\n properties: n.properties?.exported === true ? { exported: true } : {},\n }));\n const allowedIds = new Set(slimNodes.map((n) => n.id));\n const slimEdges = data.edges\n .filter((e) => allowedIds.has(e.source) && allowedIds.has(e.target))\n .map((e) => ({\n id: e.id,\n source: e.source,\n target: e.target,\n type: e.type,\n properties: {},\n }));\n return {\n nodes: slimNodes as GraphNode[],\n edges: slimEdges as GraphEdge[],\n metadata: {\n ...data.metadata,\n truncated,\n nodeCount: slimNodes.length,\n edgeCount: slimEdges.length,\n totalNodeCount: data.metadata.totalNodeCount ?? data.nodes.length,\n totalEdgeCount: data.metadata.totalEdgeCount ?? data.edges.length,\n },\n };\n}\n\nexport function createInvokeUpstream(ctx: Context, config: VisualizerConfig) {\n return async (tool: string, args: Record<string, unknown>): Promise<unknown | null> => {\n try {\n const result = await ctx.tools.execute({\n callId: CallId(`codegraph:${tool}`),\n name: tool,\n arguments: args,\n signal: AbortSignal.timeout(config.requestTimeout),\n });\n if (result.isError) return null;\n return result.value ?? null;\n } catch (e) {\n log.warn('invokeUpstream failed', { tool, error: e });\n return null;\n }\n };\n}\n\nexport function registerService(deps: RouteDeps): void {\n const { ctx, config } = deps;\n const invokeUpstream = createInvokeUpstream(ctx, config);\n\n ctx.effect(() => {\n const service: IGraphVisualizerService = {\n getCurrentGraph: () => deps.getLastGraphData(),\n getGraphData: async (repoId: string) => {\n try {\n const raw = await fetchMergedGraph(invokeUpstream, repoId, 'both');\n return slimGraphData(raw, config.maxNodes);\n } catch { return null; }\n },\n getSymbolDetail: async (symbolId: string) => {\n return invokeUpstream('codegraph_query', { search: symbolId, limit: 1 });\n },\n getImpactAnalysis: async (symbolId: string) => {\n return invokeUpstream('codegraph_impact', { symbol: symbolId, depth: 2 });\n },\n onGraphUpdate: (callback: (data: GraphData) => void) => {\n return ctx.on('codegraph/graph/data', (event) => {\n callback({\n nodes: event.nodes as GraphNode[],\n edges: event.edges as GraphEdge[],\n metadata: {\n repoId: event.repoId as GraphData['metadata']['repoId'],\n timestamp: event.timestamp,\n nodeCount: event.nodes.length,\n edgeCount: event.edges.length,\n },\n });\n });\n },\n };\n return ctx.provide('graphVisualizer', service);\n }, 'codegraph: service registration');\n}\n\nfunction sendJson(res: ServerResponse, code: number, data: unknown): void {\n res.writeHead(code, { 'content-type': 'application/json' });\n res.end(JSON.stringify(data));\n}\n\nfunction readBody(req: IncomingMessage, maxBodyBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = '';\n let totalBytes = 0;\n req.on('data', (chunk?: Buffer) => {\n if (!chunk) return;\n totalBytes += chunk.length;\n if (totalBytes > maxBodyBytes) {\n reject(new Error('Request body too large'));\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => resolve(body));\n req.on('error', reject);\n });\n}\n\nfunction parseJsonBody(body: string): Record<string, unknown> {\n const parsed = JSON.parse(body || '{}');\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('Invalid JSON body: expected object');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction extractStringField(obj: Record<string, unknown>, field: string): string | undefined {\n const v = obj[field];\n return typeof v === 'string' ? v : undefined;\n}\n\nfunction extractBooleanField(obj: Record<string, unknown>, field: string): boolean | undefined {\n const v = obj[field];\n return typeof v === 'boolean' ? v : undefined;\n}\n\nexport function registerRoutes(deps: RouteDeps): void {\n const { ctx, config } = deps;\n const invokeUpstream = createInvokeUpstream(ctx, config);\n\n // GET /api/codegraph/status\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/status',\n handler: (_req: IncomingMessage, res: ServerResponse) => {\n const status = checkPrerequisites(ctx);\n sendJson(res, 200, { codegraph: status.codegraph, lens: status.lens });\n },\n }), 'codegraph: status route');\n\n // GET /api/codegraph/workspace\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/workspace',\n handler: (_req: IncomingMessage, res: ServerResponse) => {\n const current = findWorkspacePath(ctx);\n const list = extractWorkspacePaths(ctx);\n sendJson(res, 200, { path: current, list });\n },\n }), 'codegraph: workspace route');\n\n // GET /api/codegraph/data\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/data',\n handler: (req: IncomingMessage, res: ServerResponse) => {\n const ifNoneMatch = req.headers['if-none-match'];\n const lastGraphData = deps.getLastGraphData();\n if (ifNoneMatch && lastGraphData) {\n const clientTimestamp = parseInt(String(ifNoneMatch), 10);\n if (clientTimestamp >= lastGraphData.metadata.timestamp) {\n res.writeHead(304, { 'etag': String(lastGraphData.metadata.timestamp) });\n res.end();\n return;\n }\n }\n if (lastGraphData) {\n res.writeHead(200, {\n 'content-type': 'application/json',\n 'etag': String(lastGraphData.metadata.timestamp),\n });\n res.end(JSON.stringify(lastGraphData));\n } else {\n sendJson(res, 200, { nodes: [], edges: [], metadata: { repoId: null, timestamp: 0, nodeCount: 0, edgeCount: 0 } });\n }\n },\n }), 'codegraph: data route');\n\n // POST /api/codegraph/scan\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/scan',\n handler: async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const body = await readBody(req, config.maxBodyBytes);\n const parsed = parseJsonBody(body);\n const path = extractStringField(parsed, 'path');\n if (path && path !== '.' && !isPathAllowed(path)) {\n sendJson(res, 403, { success: false, nodes: [], edges: [], metadata: { repoId: null, timestamp: 0, nodeCount: 0, edgeCount: 0 } });\n return;\n }\n const scanPath = path && path !== '.' ? path : findWorkspacePath(ctx);\n const repoId = scanPath || `workspace-${Date.now()}`;\n const scanCache = deps.getScanCache();\n\n const cached = scanCache.get(scanPath);\n if (cached && Date.now() - cached.timestamp < config.scanCacheTtl) {\n deps.setLastGraphData(cached.data);\n sendJson(res, 200, { success: true, ...cached.data });\n return;\n }\n\n const inFlight = deps.getScanInFlight();\n if (inFlight) {\n const data = await inFlight;\n sendJson(res, 200, { success: true, ...data });\n return;\n }\n\n const scanPromise = fetchMergedGraph(invokeUpstream, repoId, 'both');\n deps.setScanInFlight(scanPromise);\n try {\n const raw = await scanPromise;\n deps.setScanInFlight(null);\n const data = slimGraphData(raw, config.maxNodes);\n deps.setLastGraphData(data);\n scanCache.set(scanPath, { data, timestamp: Date.now() });\n if (scanCache.size > config.scanCacheLimit) {\n const oldest = scanCache.keys().next().value;\n if (oldest !== undefined) scanCache.delete(oldest);\n }\n ctx.emit('codegraph/graph/updated', {\n repoId,\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n timestamp: data.metadata.timestamp,\n });\n ctx.emit('codegraph/graph/data', {\n repoId,\n nodes: data.nodes,\n edges: data.edges,\n timestamp: data.metadata.timestamp,\n });\n log.info('scan completed', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount });\n sendJson(res, 200, { success: true, ...data });\n } catch (e) {\n deps.setScanInFlight(null);\n throw e;\n }\n } catch (e) {\n log.error('scan failed', e);\n sendJson(res, 500, { success: false, nodes: [], edges: [], metadata: { repoId: null, timestamp: 0, nodeCount: 0, edgeCount: 0 } });\n }\n },\n }), 'codegraph: scan route');\n\n // POST /api/codegraph/init\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/init',\n handler: async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const body = await readBody(req, config.maxBodyBytes);\n const parsed = parseJsonBody(body);\n const path = extractStringField(parsed, 'path');\n if (path && path !== '.' && !isPathAllowed(path)) {\n sendJson(res, 403, { success: false, path: '', message: 'Path not allowed', timestamp: Date.now() });\n return;\n }\n const initPath = path && path !== '.' ? path : findWorkspacePath(ctx);\n log.info('init requested', { path: initPath });\n const result = await invokeUpstream('codegraph_init', { path: initPath, force: true });\n const success = result !== null;\n const initResult = {\n success,\n path: initPath,\n message: success ? 'Graph initialized successfully' : 'Initialization failed — is dsh-codegraph installed?',\n timestamp: Date.now(),\n };\n deps.setLastInitResult(initResult);\n ctx.emit('codegraph/graph/init-result', initResult);\n if (success) {\n const repoId = initPath || `workspace-${Date.now()}`;\n const data = await fetchMergedGraph(invokeUpstream, repoId, 'both');\n deps.setLastGraphData(data);\n ctx.emit('codegraph/graph/data', {\n repoId,\n nodes: data.nodes,\n edges: data.edges,\n timestamp: data.metadata.timestamp,\n });\n }\n sendJson(res, 200, initResult);\n } catch (e) {\n const errorResult = {\n success: false,\n path: '',\n message: e instanceof Error ? e.message : String(e),\n timestamp: Date.now(),\n };\n deps.setLastInitResult(errorResult);\n sendJson(res, 500, errorResult);\n }\n },\n }), 'codegraph: init route');\n\n // POST /api/codegraph/watch\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/api/codegraph/watch',\n handler: async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const body = await readBody(req, config.maxBodyBytes);\n const parsed = parseJsonBody(body);\n const enabled = extractBooleanField(parsed, 'enabled');\n const path = extractStringField(parsed, 'path');\n if (path && path !== '.' && !isPathAllowed(path)) {\n sendJson(res, 403, { success: false, message: 'Path not allowed' });\n return;\n }\n const watchPath = path && path !== '.' ? path : findWorkspacePath(ctx);\n ctx.emit('codegraph/watch/toggle', { enabled: !!enabled, path: watchPath, timestamp: Date.now() });\n sendJson(res, 200, { success: true });\n } catch (e) {\n sendJson(res, 500, { success: false, message: e instanceof Error ? e.message : String(e) });\n }\n },\n }), 'codegraph: watch route');\n}","import { watch } from 'node:fs';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { scoped } from '../shared/Logger.ts';\nimport { fetchMergedGraph } from '../tools.ts';\nimport type { VisualizerConfig } from './config.ts';\n\nconst log = scoped('host');\n\nlet watchTimer: ReturnType<typeof setTimeout> | null = null;\nlet activeWatcher: { close: () => void } | null = null;\n\nfunction closeActiveWatcher(): void {\n if (activeWatcher) {\n try { activeWatcher.close(); } catch { /* best-effort */ }\n activeWatcher = null;\n }\n if (watchTimer) { clearTimeout(watchTimer); watchTimer = null; }\n}\n\nexport function registerWatcher(ctx: Context, config: VisualizerConfig, invokeUpstream: (tool: string, args: Record<string, unknown>) => Promise<unknown | null>): void {\n const scanAndPush = async (path: string) => {\n log.info('scan requested', { path });\n try {\n const repoId = path || `workspace-${Date.now()}`;\n const data = await fetchMergedGraph(invokeUpstream, repoId, 'both');\n ctx.emit('codegraph/graph/updated', {\n repoId,\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n timestamp: data.metadata.timestamp,\n });\n ctx.emit('codegraph/graph/data', {\n repoId,\n nodes: data.nodes,\n edges: data.edges,\n timestamp: data.metadata.timestamp,\n });\n log.info('scan completed', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount });\n } catch (e) {\n log.error('scan failed', e);\n }\n };\n\n ctx.effect(() => {\n const dispose = ctx.on('codegraph/watch/toggle', (event) => {\n closeActiveWatcher();\n\n if (!event.enabled) {\n log.info('watch disabled');\n return;\n }\n\n log.info('watch enabled', { path: event.path });\n try {\n activeWatcher = watch(event.path, { recursive: true }, (_eventType: string, filename: string | null) => {\n if (filename && filename.includes('.codegraph')) return;\n if (filename && filename.includes('node_modules')) return;\n if (filename && filename.includes('.git')) return;\n\n if (watchTimer) clearTimeout(watchTimer);\n watchTimer = setTimeout(() => {\n watchTimer = null;\n log.info('file changed, syncing + re-scanning', { filename });\n invokeUpstream('codegraph_sync', { path: event.path })\n .then(() => scanAndPush(event.path))\n .catch((e) => log.error('watch re-scan failed', e));\n }, config.watchDebounce);\n });\n } catch (e) {\n log.error('watch setup failed', e);\n }\n });\n return () => {\n dispose();\n closeActiveWatcher();\n };\n }, 'codegraph: watch toggle listener');\n}\n\nexport function registerEventListeners(ctx: Context, invokeUpstream: (tool: string, args: Record<string, unknown>) => Promise<unknown | null>): void {\n const scanAndPush = async (path: string) => {\n log.info('scan requested', { path });\n try {\n const repoId = path || `workspace-${Date.now()}`;\n const data = await fetchMergedGraph(invokeUpstream, repoId, 'both');\n ctx.emit('codegraph/graph/updated', {\n repoId,\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n timestamp: data.metadata.timestamp,\n });\n ctx.emit('codegraph/graph/data', {\n repoId,\n nodes: data.nodes,\n edges: data.edges,\n timestamp: data.metadata.timestamp,\n });\n log.info('scan completed', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount });\n } catch (e) {\n log.error('scan failed', e);\n }\n };\n\n ctx.effect(() => ctx.on('codegraph/repo/imported', (event) => {\n ctx.emit('codegraph/graph/updated', { repoId: event.repoId, nodeCount: 0, edgeCount: 0, timestamp: event.timestamp });\n }), 'codegraph: repo imported listener');\n\n ctx.effect(() => ctx.on('codegraph/repo/scanned', (event) => {\n ctx.emit('codegraph/graph/updated', { repoId: event.repoId, nodeCount: 0, edgeCount: 0, timestamp: event.timestamp });\n }), 'codegraph: repo scanned listener');\n\n ctx.effect(() => ctx.on('codegraph/repo/request-scan', async (event) => {\n await scanAndPush(event.path);\n }), 'codegraph: repo request-scan listener');\n\n ctx.effect(() => ctx.on('codegraph/graph/init', async (event) => {\n log.info('init requested', { path: event.path });\n try {\n const result = await invokeUpstream('codegraph_init', { path: event.path, force: true });\n const success = result !== null;\n ctx.emit('codegraph/graph/init-result', {\n success,\n path: event.path,\n message: success ? 'Graph initialized successfully' : 'Initialization failed — is dsh-codegraph installed?',\n timestamp: Date.now(),\n });\n if (success) {\n await scanAndPush(event.path);\n }\n } catch (e) {\n ctx.emit('codegraph/graph/init-result', {\n success: false,\n path: event.path,\n message: e instanceof Error ? e.message : String(e),\n timestamp: Date.now(),\n });\n }\n }), 'codegraph: graph init listener');\n}","import type { Context } from '@deepseek-ai/cordis';\nimport { createGraphTools } from './tools.ts';\nimport { scoped } from './shared/Logger.ts';\nimport type { GraphData } from './types/index.ts';\nimport { PLUGIN_VERSION } from './generated/version.ts';\nimport { resolveConfig } from './host/config.ts';\nimport type { VisualizerConfig } from './host/config.ts';\nimport { setAllowedWorkspaceRoots } from './host/security.ts';\nimport { extractWorkspacePaths, checkPrerequisites } from './host/prerequisites.ts';\nimport { createInvokeUpstream, registerRoutes, registerService } from './host/routes.ts';\nimport { registerWatcher, registerEventListeners } from './host/watcher.ts';\n\nexport { PLUGIN_VERSION };\nexport { resolveConfig, DEFAULT_CONFIG } from './host/config.ts';\nexport type { VisualizerConfig } from './host/config.ts';\nexport { isPathAllowed, setAllowedWorkspaceRoots } from './host/security.ts';\n\nconst log = scoped('host');\n\nexport const name = 'dsh-codegraph-visualizer';\nexport const inject = ['tools', 'webServer', 'sessions', 'workspaceRegistry'];\n\nexport function apply(ctx: Context, userConfig?: Partial<VisualizerConfig>) {\n const config = resolveConfig(userConfig);\n setAllowedWorkspaceRoots(extractWorkspacePaths(ctx));\n\n const { graphStatus, graphData, graphSymbol, graphImpact } = createGraphTools(ctx, { requestTimeout: config.requestTimeout });\n\n ctx.effect(() => {\n const d1 = ctx.tools.register(graphStatus);\n const d2 = ctx.tools.register(graphData);\n const d3 = ctx.tools.register(graphSymbol);\n const d4 = ctx.tools.register(graphImpact);\n return () => { d1(); d2(); d3(); d4(); };\n }, 'codegraph: tool registrations');\n\n let lastGraphData: GraphData | null = null;\n let lastInitResult: { success: boolean; path: string; message: string; timestamp: number } | null = null;\n let scanInFlight: Promise<GraphData> | null = null;\n const scanCache = new Map<string, { data: GraphData; timestamp: number }>();\n\n const routeDeps = {\n ctx,\n config,\n getLastGraphData: () => lastGraphData,\n setLastGraphData: (data: GraphData | null) => { lastGraphData = data; },\n getLastInitResult: () => lastInitResult,\n setLastInitResult: (data: { success: boolean; path: string; message: string; timestamp: number } | null) => { lastInitResult = data; },\n getScanCache: () => scanCache,\n getScanInFlight: () => scanInFlight,\n setScanInFlight: (p: Promise<GraphData> | null) => { scanInFlight = p; },\n };\n\n registerService(routeDeps);\n registerRoutes(routeDeps);\n\n const invokeUpstream = createInvokeUpstream(ctx, config);\n\n const emitPrereqStatus = () => {\n const status = checkPrerequisites(ctx);\n ctx.emit('codegraph/prerequisite/status', {\n codegraph: status.codegraph,\n lens: status.lens,\n timestamp: Date.now(),\n });\n log.info('prerequisite status', status);\n };\n emitPrereqStatus();\n const prereqTimer = setTimeout(emitPrereqStatus, config.prerequisiteRetryDelay);\n ctx.effect(() => () => clearTimeout(prereqTimer), 'codegraph: prereq re-check timer');\n\n ctx.effect(() => ctx.on('codegraph/prerequisite/request', () => {\n emitPrereqStatus();\n }), 'codegraph: prerequisite request listener');\n\n registerEventListeners(ctx, invokeUpstream);\n registerWatcher(ctx, config, invokeUpstream);\n}\n"],"x_google_ignoreList":[0,1,2,3],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["NODE_TYPE_ALIASES: Record<string, GraphNode['type']>","EDGE_TYPE_ALIASES: Record<string, GraphEdge['type']>","type: unknown","log","raw: unknown","raw: Record<string, unknown>","issues: GraphIssue[]","path: string","path","t: unknown","node: GraphNode","direction: EdgeDirection","edge: GraphEdge","fixedNodes: GraphNode[]","fixedEdges: GraphEdge[]","root: Record<string, unknown>","nodeCount: number","edgeCount: number","data: GraphData","EDGE_ORIGIN_RANK: Readonly<Record<EdgeOrigin, number>>","EDGE_TYPE_SPECIFICITY: Readonly<Record<GraphEdge['type'], number>>","edge: GraphEdge","a: GraphEdge","b: GraphEdge","a: { id: string }","b: { id: string }","properties: Record<string, unknown>","result: Record<string, unknown>","results: AdapterResult[]","repoId: RepoId","candidate: GraphEdge","i: { level: string }","current: GraphData","delta: AdapterResult","a: unknown","b: unknown","seen?: Map<unknown, unknown>","depth?: number","nextDepth","beforeNodes: GraphNode[]","beforeEdges: GraphEdge[]","afterNodes: GraphNode[]","afterEdges: GraphEdge[]","addedNodes: GraphNode[]","removedNodes: GraphNode[]","modifiedNodes: GraphNode[]","addedEdges: GraphEdge[]","removedEdges: GraphEdge[]","modifiedEdges: GraphEdge[]","filePath: string","maxDepth: number","nodes: GraphNode[]","edges: GraphEdge[]","map: AdjacencyMap","edgeTypes: string[]","EDGE_WEIGHTS: Record<GraphEdge['type'], number>","maxModuleDepth?: number","nodes: GraphNode[]","edges: GraphEdge[]","score: HealthScore","moduleOfNode: Map<string, string>","m: string","cycleEdgeTypes: GraphEdge['type'][]","adjacency: Map<string, string[]>","nodeIds: Set<string>","stack: string[]","sccs: string[][]","callStack: Array<{ v: string; neighborIdx: number; neighbors: string[] }>","scc: string[]","w: string","arr: readonly T[]","s: T[]","adjacency: ReadonlyMap<string, readonly string[]>","frames: Array<{ id: string; neighbors: readonly string[]; next: number }>","adjacency: AdjacencyMap","maxCycles: number","cycles: string[][]","start: string","path: string[]","frames: Array<{ id: string; neighbors: string[]; next: number }>","path","DEFAULT_FORBIDDEN_EDGE_TYPES: readonly string[]","type: RuleType","detector: RuleDetector","rules: RuleDefinition[]","nodes: GraphNode[]","edges: GraphEdge[]","provider: AdjacencyProvider","edgeTypes: readonly string[]","violations: RuleViolation[]","injected: RuleDetector[]","context: AdjacencyProvider | null","rule: RuleDefinition","_nodes: GraphNode[]","source: NodeId","target: NodeId","type: EdgeType","event: ChangeEvent","id: NodeId","attrs: NodeAttrs","edge: XJEdge","types: ReadonlySet<EdgeType>","node: NodeId","dir: 'in' | 'out'","adj: Map<NodeId, Map<EdgeType, NodeId[]>>","result: NodeId[]","other: NodeId","rule: PathRule","knownEdgeTypes: ReadonlySet<EdgeType>","nodeCount?: number","errors: string[]","warnings: string[]","message: string","rule: Pick<PathRule, 'lMax' | 'simple'>","nodeCount: number","nodes: readonly NodeId[]","allowClosedReturn: boolean","graph: GraphSpace","source: NodeId","target: NodeId","index: Map<NodeId, LengthBuckets>","node: NodeId","edgeCount: number","byLength: LengthBuckets | undefined","result: PathEntry[]","entry: PathEntry","edgePair: string","nodes: Iterable<NodeId>","start: NodeId","end: NodeId","n: number","live: PathEntry[]","index: PathIndex","powCache: bigint[]","exp: number","id: string","EMPTY_HASH: HashPart","a: HashPart","b: HashPart","nodes: readonly string[]","part: HashPart","prefix: HashPart","suffix: HashPart","a: readonly NodeId[]","b: readonly NodeId[]","rule: Pick<PathRule, 'normalize' | 'closed'>","nodes: readonly NodeId[]","graphVersion: number","entry: PathEntry","rule: PathRule","graph: GraphSpace","WORK_BUDGET_EXCEEDED: unique symbol","meter: WorkMeter","options?: IncrementalOptions","index?: PathIndex","graph: GraphSpace","types: ReadonlySet<EdgeType>","excluded: EdgeType","source: NodeId","target: NodeId","rule: PathRule","newEdge: XJEdge","head: NodeId","maxPrefixEdges: number","prefixes: NodeId[][]","path: NodeId[]","current: NodeId","edges: number","path","edge: XJEdge","index: PathIndex","added: PathEntry[]","nodes: readonly NodeId[]","nodes: NodeId[]","options?: EdgeRemovalOptions","attrs: NodeAttrs | undefined","index: PathIndex","graph: GraphSpace","rule: PathRule","result: PathEntry[]","entry: PathEntry","thresholds: InvariantThresholds","missing: EdgeType[]","missing: string[]","averages: Array<{ type: EdgeType; avg: number }>","offenders: string[]","items: readonly T[]","k: number","score?: (item: T) => number","item: T","decorated: Array<{ item: T; score: number; index: number }>","result: T[]","value: number | undefined","graph: GraphSpace","rule: PathRule","nodes: readonly NodeId[]","starts: Iterable<NodeId>","index: PathIndex","options: EnumerateOptions","results: PathEntry[]","candidates: NodeId[][]","path: NodeId[]","node: NodeId","depth: number","canExpand: boolean","path","entries: readonly PathEntry[]","options: { readonly cursor?: number; readonly pageSize?: number }","message: string","pattern: string","tokens: Token[]","tokens: readonly Token[]","source: string","kind: TokenKind","options: PatternNode[]","parts: PatternNode[]","from: number","types: ReadonlySet<EdgeType>","to: number","node: PatternNode","b: NfaBuilder","node: Extract<PatternNode, { kind: 'repeat' }>","a","states: ReadonlySet<number>","nfa: readonly NfaState[]","types: Set<EdgeType>","types: readonly EdgeType[]","labelsPerStep: readonly (readonly EdgeType[])[]","steps: readonly (readonly EdgeType[])[]","graph: GraphSpace","from: NodeId","to: NodeId","candidates: ReadonlySet<EdgeType>","present: EdgeType[]","options: PathPatternRuleOptions","rule: PathRule","nodes: readonly NodeId[]","labelsPerStep: EdgeType[][]","error: unknown","DEFAULT_PATH_EDGE_TYPES: readonly AdapterEdgeType[]","KNOWN_ADAPTER_EDGE_TYPES: ReadonlySet<AdapterEdgeType>","pattern: string","error: unknown","DELTA_SKIPPED: PoolDeltaResult","EMPTY_PATH_RESULT: PathEnumerationResult","node: GraphNode","edge: GraphEdge","stored: NodeAttrs","next: NodeAttrs","data: SyncGraphInput","thresholds: InvariantThresholds","delta: GraphDelta","name: string","index: PathIndex","name","ej: XJEdge","degraded: Set<string>","violated: string[]","nodeId: string","edges: XJEdge[]","rule: PathRule","graph?: GraphSpace","source: string","target: string","options: PathEnumerationOptions","options: CycleEnumerationOptions","options: PatternEnumerationOptions","id: string","sources: Iterable<string>","edgeTypes?: readonly AdapterEdgeType[]","queue: string[]","options: { drainChanges?: boolean }","requested: Iterable<AdapterEdgeType>","a: SyncStamp","b: SyncStamp","addedNodes: GraphNode[]","addedEdges: GraphEdge[]","repoId: string","rawPath: string","output: string","byFile: Record<string, number>","raw: string","slices: CommitSlice[]","files: string[]","history: CommitHistory","range: CommitRange","now: number","data: Pick<GraphData, 'nodes' | 'edges'>","commitRange: CommitRange","history?: CommitHistory | null","edges: readonly GraphEdge[]","commitHistory: CommitHistory","nodeFiles?: Readonly<Record<string, string>> | null","reason: string","results: EdgeVolatilityEntry[]","firstSeenAt: number | undefined","spec: TemporalPredicateSpec","fact: TemporalEdgeFact","nodes: readonly Pick<GraphNode, 'id' | 'filePath'>[]","edges: readonly Pick<GraphEdge, 'id' | 'source' | 'target'>[]","commitLog: string","range?: CommitRange","data: GraphData","request: TemporalEnumerationRequest","history: CommitHistory","range: CommitRange","sliced: GraphData","request: EdgeTemporalFilterRequest","text: string","query: string","terms: string[]","term: string","label: string","filePath: string | undefined","nodes: readonly KeywordNode[]","terms: readonly string[]","options: SelectSeedOptions","scored: SeedMatch[]","matched: string[]","guaranteed: SeedMatch[]","best: SeedMatch | undefined","value: string","graph: { readonly nodes: readonly KeywordNode[]; readonly edges: readonly KeywordEdge[] }","seeds: readonly SeedMatch[]","options: RenderOptions","frontier: NodeId[]","text","lines: string[]","adjacency: AdjacencyMap","source: string","target: string","maxNodes: number","queue: string[]","path: string[]","cur: string | undefined","path","reverseAdjacency: AdjacencyMap","startNode: string","depth: number","maxResults: number","log","nodes: GraphNode[]","edges: GraphEdge[]","results: CommunityResult[]","results: CentralityResult[]","nodeId: string","startNodeId: string","maxDepth?: number","queue: Array<{ id: string; depth: number }>","result: string[]","neighbor: string","fromId: string","toId: string","queue: Array<{ id: string; path: string[] }>","found: string[] | null","path","components: string[][]","component: string[]","orphans: string[]","leaves: string[]","hubs: string[]","raw: string","out: BlameLine[]","lines: readonly BlameLine[]","byFile: Record<string, BlameLine[]>","nodes: readonly GraphNode[]","blameByFile: Readonly<Record<string, BlameLine[]>> | null | undefined","nodeLabels: Record<string, string>","VIEW_NAMES: readonly ViewName[]","extra: readonly ViewName[]","input: ViewPartitionInput","viewCounts: Record<ViewName, number>","edgeViewCounts: Record<ViewName, number>","extra: ViewName[]","partition: ViewPartition","log","view: GraphSliceView","granularity: GraphSliceGranularity","reason: string","edges: readonly GraphEdge[]","keep: ReadonlySet<string>","nodes: readonly GraphNode[]","focus: string | undefined","maxNeighbors: number","keep","fileNodes: GraphNode[]","fileEdges: GraphEdge[]","filePath: string","values: readonly string[]","labels: Record<string, string>","domainNames: Record<string, string>","history: CommitHistory","data: Pick<GraphData, 'nodes' | 'edges'>","request: GraphSliceRequest","temporalSlice: TemporalSliceResult | null","metadata: Record<string, unknown>","reason: string | undefined","nodeFiles: Record<string, string>","GRAPH_SLICE_VIEWS: readonly GraphSliceView[]","GRAPH_SLICE_GRANULARITIES: readonly GraphSliceGranularity[]","data: GraphData","raw: unknown","symbolId: string","_args: unknown","value: unknown","log","value: T","PATH_EDGE_TYPE_DOMAIN: ReadonlySet<string>","parseEdgeTypes","raw: string | undefined","clampInt","value: number | undefined","min: number","max: number","fallback: number","nativeAdapterSingleton: NativeGraphAdapter | null | undefined","cache: Map<string, GraphData>","repoId: string","data: GraphData","invoke: UpstreamInvoker","source: 'codegraph' | 'lens' | 'both' | 'auto'","deps?: GraphToolDeps","tasks: Promise<AdapterResult>[]","results: AdapterResult[]","dbNodes: unknown[]","dbEdges: unknown[]","cursor: import('./adapters/codeGraphDb.ts').GraphPageCursor | null","ctx: Context","requestTimeout: number","apiCompat?: ApiCompatLayer","args: { repoId: string }","deps: GraphToolDeps","args: { repoId: string; source?: string }","emitUpdate: (event: GraphUpdatedEvent) => void","emitData: (event: GraphDataEvent) => void","graphCache: Map<string, GraphData>","xuanjiPool?: XuanjiAdapterPool","merged: GraphData","xuanjiSignal: GraphData['metadata']['xuanji'] | undefined","args: { symbolId: string }","args: {\r\n repoId: string;\r\n view: GraphSliceView;\r\n granularity?: GraphSliceGranularity;\r\n focus?: string;\r\n commitLog?: string;\r\n blameLog?: string;\r\n }","options: { requestTimeout?: number; apiCompat?: ApiCompatLayer; /** 原生图谱通道的工作区根(cycle-127);缺省 = 不启用原生通道。 */ nativeWorkspaceRoot?: string }","event: GraphUpdatedEvent","event: GraphDataEvent","_args: unknown","value: unknown","edgeTemporal: ReturnType<typeof filterEdgesByTemporalPredicate>['meta']","rules: RuleDefinition[]","graphSlice","temporalFiles: Set<string> | undefined","range: CommitRange","attributedNodeIds: ReadonlySet<string> | undefined","DEFAULT_CONFIG: VisualizerConfig","userConfig?: Partial<VisualizerConfig>","config: VisualizerConfig","errors: string[]","positiveFields: Array<[keyof VisualizerConfig, number]>","p: string","roots: string[]","source: WorkspaceRootSource","source?: WorkspaceRootSource","path: string","path","log","raw: unknown","content: string","nodes: DependencyNode[]","edges: DependencyEdge[]","depSections: Array<{ key: string; scope: DependencyEdge['scope'] }>","name","pendingTableDep: { name: string; fields: string[] } | null","depGraph: DependencyGraph","nodes: GraphNode[]","edges: GraphEdge[]","log","execFileAsync","astSourceProbe: (() => boolean) | null","probe: (() => boolean) | null","parser: (content: string) => { nodes: unknown[]; edges: unknown[] }","ctx: Context","apiCompat?: ApiCompatLayer","apiCompat: ApiCompatLayer | undefined","key: 'codegraphStatus' | 'lensAnalyze'","evidence: readonly string[]","name","paths: string[]","ip: string","routePath: string","buckets: RateBucketSnapshot[]","a: string | null | undefined","b: string | null | undefined","normalize","value: string","DSH_GENERATION_PROBES: Record<DshKnownGeneration, string>","major: number","minor: number","raw: string","generation: DshGeneration","log","ctx: Context","log","GEN1_FLAGS: FeatureFlags","GEN2_FLAGS: FeatureFlags","GEN3_FLAGS: FeatureFlags","UNKNOWN_FLAGS: FeatureFlags","FEATURE_FLAGS_BY_GENERATION: Record<DshGeneration, FeatureFlags>","info: HostVersionInfo","probe?: CapabilityProbe","flags: FeatureFlags","probedUpgrades: (keyof FeatureFlags)[]","observed: boolean | null | undefined","log","SERVICE_EVIDENCE: Partial<Record<keyof FeatureFlags, readonly string[]>>","TOOL_EVIDENCE: Partial<Record<keyof FeatureFlags, readonly string[]>>","ctx: Context","capability: keyof FeatureFlags","log","GEN1_MAPPING: ToolNameMapping","GEN2_MAPPING: ToolNameMapping","GEN3_MAPPING: ToolNameMapping","UNKNOWN_MAPPING: ToolNameMapping","TOOL_MAPPING_BY_GENERATION: Record<DshGeneration, ToolNameMapping>","info: HostVersionInfo","key: keyof ToolNameMapping","ctx: Context","name","actualName: string","FALLBACK_CHAINS: Partial<Record<keyof ToolNameMapping, (keyof ToolNameMapping)[]>>","delta: GraphDelta","current: GraphSkeleton","retainedRatio","currentSize: number","nextSize: number","options: GraphDeltaGuardOptions","previousSize: number","nextSize: number","previous: SnapshotSize | null | undefined","next: SnapshotSize","options: SnapshotShrinkOptions","repoId: string","fingerprint: string","log","req: IncomingMessage","maxBodyBytes: number","resolve","chunk?: Buffer","body: string","obj: Record<string, unknown>","field: string","maxLength?: number","ref: string","scanPath: string","securityManager: SecurityManager","req: IncomingMessage | undefined","config: CorsConfig","headers: Record<string, string>","res: ServerResponse","code: number","data: unknown","extraHeaders?: ExtraResponseHeaders","httpCode: number","errorCode: string","detail?: string","method: string","routePath: string","rateLimiter: ReturnType<typeof import('./RateLimiter.ts')['createRateLimiter']>","log","value: string","maxLength: number","method: string","routePath: string","corsConfig: CorsConfig","rateLimiter: ReturnType<typeof createRateLimiter>","handler: RouteHandler","ctx: Context","deps: RouteDeps","resolved: string[]","source: WorkspaceRootSource","req: IncomingMessage","bodyWorkspace?: string | undefined","candidate: string | null","workspacePath: string | null | undefined","pathInput: string | undefined","res: ServerResponse","relativePath: string","req?: IncomingMessage","workspacePath: string","serverEtag: string","resource: GraphEtagResource","repoId?: string | null | undefined","requestKey?: string","etag: string | null","data: GraphData","maxNodes: number","selectedNodes: typeof data.nodes","config: VisualizerConfig","apiCompat?: ApiCompatLayer","service: IGraphVisualizerService","repoId: string","symbolId: string","callback: (data: GraphData) => void","invokeUpstream: ReturnType<typeof createInvokeUpstream>","scanPromise: Promise<GraphData>","status: Pick<PrerequisiteStatus, 'codegraph' | 'lens' | 'graphSource'>","status: PrerequisiteStatus","readyChannels: PrerequisiteDiagnosis['readyChannels']","issues: PrerequisiteIssue[]","state: PrerequisiteDiagnosisState","TOOL_ENTRIES: RegistryEntry[]","ROUTE_ENTRIES: RegistryEntry[]","ENTRY_ITEMS: RegistryEntry[]","SKILL_ENTRIES: RegistryEntry[]","deps: RegistryProbeDeps","id: string","basis: string","live: LiveHint['live']","hints: LiveHint[]","out: Record<string, number>","rc: RouteContext","dbNodes: DbNodeRow[]","parentId?: string","dbEdges: DbEdgeRow[]","data: GraphData","rootNodes: GraphNode[]","index: GraphIndex","workspacePath: string","rc: RouteContext","symbols","symbolIds","symbolEdges","allAstNodes","astNodes","nextCursor","hasMore","astNodeIds","astEdges","fromId: string","edges: ReadonlyArray<{ source: string; target: string }>","filePathOf: (nodeId: string) => string | undefined","filteredEdges: GraphEdge[]","key: string","delta: number","cache: 'scan' | 'indexeddb' | 'diagram' | 'cluster'","stage: 'scan' | 'init' | 'import' | 'dbRead' | 'merge' | 'xuanjiDelta' | 'xuanjiDegraded'","stage: string","durationMs: number","rc: RouteContext","event: string","data: unknown","disposers: Array<() => boolean>","event: { repoId?: string; nodeCount?: number; edgeCount?: number; timestamp?: number; changedFiles?: string[] }","event: { repoId?: string; nodes?: unknown[]; edges?: unknown[]; timestamp?: number; changedFiles?: string[] }","event: { repoId?: string; delta?: unknown; timestamp?: number; changedFiles?: string[] }","event: { repoId?: string; timestamp?: number }","event: { success?: boolean; path?: string; message?: string; timestamp?: number }","dbPath: string | null","cursor: string | null","coverage: import('../../adapters/nativeIndex.ts').IndexCoverage | null","rawPath: string","path: string","path","RAW_STATUS_MAP: Record<string, GitFileStatus>","output: string","changes: GitFileChange[]","changes: readonly GitFileChange[]","nodes: Array<{\r\n id: string;\r\n label: string;\r\n type: 'module';\r\n filePath: string;\r\n properties: Record<string, unknown>;\r\n }>","log","execFileAsync","failed","error: string","describeError","e: unknown","workspacePath: string","revision: string | null","log","execFileAsync","workspacePath: string","from: string","to: string","error: string","describeError","e: unknown","changes: GitFileChange[]","workspacePath: string","manifestInputs: string[]","rc: RouteContext","allDepNodes: ReturnType<typeof dependencyGraphToGraphData>['nodes']","allDepEdges: ReturnType<typeof dependencyGraphToGraphData>['edges']","depFiles: Array<{ filename: string; parser: (content: string) => ReturnType<typeof parsePackageJson> }>","revision: string | null","available: boolean","etag","sharedTreeSitterAdapter: TreeSitterAdapter","req: IncomingMessage","res: ServerResponse","corsConfig: RouteContext['corsConfig']","candidates: string[]","rc: RouteContext","input: {\n readonly workspacePath: string;\n readonly query: string;\n readonly depth: number;\n readonly tokenBudget: number;\n readonly revision: number;\n}","text: string","graph: GraphData","nodeIds: string[]","edgeTriples: Array<{ source: string; target: string; type: string }>","nodes: GraphNode[]","edges: GraphEdge[]","req: IncomingMessage","res: ServerResponse","rc: RouteContext","workspacePath: string","input: KeywordQueryInput","source: 'slot' | 'fetched'","v: unknown","min: number","max: number","dflt: number","workspacePath: string","raw: unknown","res: Parameters<typeof sendJson>[0]","pattern: string","req: Parameters<typeof sendJson>[3]","corsConfig: Parameters<typeof sendJson>[4]","graph: GraphData","url: URL","rc: RouteContext","rc: RouteContext","log","reason: string","e: unknown","stdout: string","workspacePath: string","channel: 'commitLog' | 'blameLog'","fileFilter?: ReadonlySet<string>","cacheKey","durationMs","parts: string[]","filePath: string","workspacePath: string","dbPath: string | null","cursor: string | null","allDbNodes: DbNodeRow[]","allDbEdges: DbEdgeRow[]","url: URL","name: string","name","nodes: readonly GraphNode[]","commitLog: string | undefined","range: CommitRange","failureReason?: string | null","blameLog: string | undefined","rc: RouteContext","body: ViewsErrorResponse","body","relNodes: GraphNode[]","domainNodeCounts: Record<string, number>","nodeViewMatrix: NodeViewRow[]","body: ViewsSuccessResponse","deps: RouteDeps","corsConfig: CorsConfig","rc: RouteContext","log","node: GraphNode","changed: Set<string>","nodes: GraphNode[]","edges: GraphEdge[]","previous: GraphData['metadata']","timestamp: number","input: IncrementalUpdateInput","dbPath: string","log","ctx: Context","invokeUpstream: UpstreamInvoker","path: string","changedFiles?: string[]","state?: WatcherGraphState","path","fingerprint","config: VisualizerConfig","watchTimer: ReturnType<typeof setTimeout> | null","activeWatcher: { close: () => void } | null","retryTimer: ReturnType<typeof setTimeout> | null","watchPath: string","_eventType: string","filename: string | null","initResult: { success: boolean; path: string; message: string; errorCode?: string; timestamp: number }","log","tool: McpTool","getGraphData: () => GraphData | null","line: string","err: Error","e","msg: Record<string, unknown>","n: GraphNode","e: GraphEdge","exact: GraphNode[]","partial: GraphNode[]","log","workspacePath: string","repoId: string","hash: string","nodeCount: number","edgeCount: number","data: Buffer","keep: number","type: string","result: string","targetNode?: string","key: string","value: string","name: string","layoutType: string","positions: string","name","log","ctx: Context","outcome: SkillContribution","e: unknown","ctx: Context","userConfig?: Partial<VisualizerConfig>","graphSlice","lastGraphData: GraphData | null","lastInitResult: { success: boolean; path: string; message: string; timestamp: number } | null","data: GraphData | null","data: { success: boolean; path: string; message: string; timestamp: number } | null","path: string","path","p: Promise<GraphData> | null","s: ReturnType<typeof checkPrerequisites>"],"sources":["../src/shared/AliasMapper.ts","../src/shared/GraphValidator.ts","../src/merger/GraphDataMerger.ts","../src/merger/DiffEngine.ts","../src/shared/analysis/modulePath.ts","../src/shared/analysis/AdjacencyList.ts","../src/shared/analysis/HealthScorer.ts","../src/xuanji/ops/booth.ts","../src/xuanji/ops/cycle-existence.ts","../src/shared/analysis/CycleDFS.ts","../src/shared/analysis/RuleEngine.ts","../src/xuanji/core/graph-space.ts","../src/xuanji/core/path-rule.ts","../src/xuanji/index/path-index.ts","../src/xuanji/ops/rolling-hash.ts","../src/xuanji/core/path-entry.ts","../src/xuanji/index/incremental.ts","../src/xuanji/ops/invariants.ts","../src/xuanji/query/topk.ts","../src/xuanji/query/query-engine.ts","../src/xuanji/core/path-pattern.ts","../src/shared/analysis/XuanjiAdapter.ts","../src/shared/churn.ts","../src/shared/temporal.ts","../src/shared/analysis/temporalEnumeration.ts","../src/xuanji/query/keyword-query.ts","../src/shared/analysis/GraphBFS.ts","../src/shared/GraphologyAdapter.ts","../src/shared/ownership.ts","../src/shared/views.ts","../src/shared/domain.ts","../src/graphUtils.ts","../src/tools.ts","../src/generated/version.ts","../src/host/config.ts","../src/host/security.ts","../src/adapters/DependencyAdapter.ts","../src/host/prerequisites.ts","../src/host/RateLimiter.ts","../src/shared/pathUtils.ts","../src/shared/dshGeneration.ts","../src/host/compat/HostVersionDetector.ts","../src/host/compat/FeatureFlags.ts","../src/host/compat/HostCapabilityProbe.ts","../src/host/compat/ApiCompatLayer.ts","../src/shared/graphDelta.ts","../src/host/compat/snapshotShrinkGuard.ts","../src/host/HttpHelpers.ts","../src/host/routes/shared.ts","../src/shared/prerequisiteStatus.ts","../src/host/registry/businessRegistry.ts","../src/host/routes/statusRoutes.ts","../src/host/DbMapper.ts","../src/host/routes/dataRoutes.ts","../src/shared/observability.ts","../src/host/routes/controlRoutes.ts","../src/shared/gitFileDiff.ts","../src/host/gitChurn.ts","../src/host/gitDiff.ts","../src/host/routes/analysisRoutes.ts","../src/host/routes/workerRoutes.ts","../src/host/routes/queryRoute.ts","../src/host/routes/pathsRoute.ts","../src/host/routes/registryRoute.ts","../src/host/gitViewsCollector.ts","../src/host/routes/viewsRoute.ts","../src/host/routes.ts","../src/host/incrementalUpdate.ts","../src/host/watcher.ts","../src/host/McpServer.ts","../src/host/db/GraphRepository.ts","../src/host/skills.ts","../src/index.ts"],"sourcesContent":["import type { GraphNode, GraphEdge } from '../types/index.ts';\r\n\r\nconst NODE_TYPE_ALIASES: Record<string, GraphNode['type']> = {\r\n func: 'function',\r\n fn: 'function',\r\n method: 'function',\r\n cls: 'class',\r\n mod: 'module',\r\n file: 'module',\r\n pkg: 'module',\r\n package: 'module',\r\n iface: 'interface',\r\n typ: 'type',\r\n enum: 'type',\r\n const: 'variable',\r\n var: 'variable',\r\n let: 'variable',\r\n};\r\n\r\nconst EDGE_TYPE_ALIASES: Record<string, GraphEdge['type']> = {\r\n calls: 'call',\r\n invoke: 'call',\r\n invokes: 'call',\r\n imports: 'import',\r\n import_from: 'import',\r\n extends: 'extend',\r\n inherits: 'extend',\r\n inherit: 'extend',\r\n implements: 'implement',\r\n implements_interface: 'implement',\r\n depends: 'dependency',\r\n depends_on: 'dependency',\r\n uses: 'dependency',\r\n contains_node: 'contains',\r\n owns: 'contains',\r\n has: 'contains',\r\n assoc: 'association',\r\n related: 'association',\r\n aggregates: 'aggregation',\r\n aggregate: 'aggregation',\r\n composes: 'composition',\r\n compose: 'composition',\r\n};\r\n\r\nconst VALID_NODE_TYPES = new Set(['function', 'class', 'variable', 'module', 'interface', 'type']);\r\nconst VALID_EDGE_TYPES = new Set(['call', 'import', 'extend', 'implement', 'dependency', 'contains', 'association', 'aggregation', 'composition']);\r\n\r\nexport function normalizeNodeType(type: unknown): GraphNode['type'] | null {\r\n if (typeof type !== 'string') return null;\r\n if (VALID_NODE_TYPES.has(type as GraphNode['type'])) return type as GraphNode['type'];\r\n const alias = NODE_TYPE_ALIASES[type];\r\n if (alias) return alias;\r\n return null;\r\n}\r\n\r\nexport function normalizeEdgeType(type: unknown): GraphEdge['type'] | null {\r\n if (typeof type !== 'string') return null;\r\n if (VALID_EDGE_TYPES.has(type as GraphEdge['type'])) return type as GraphEdge['type'];\r\n const alias = EDGE_TYPE_ALIASES[type];\r\n if (alias) return alias;\r\n return null;\r\n}\r\n\r\nexport function is_valid_node_type(type: string): boolean {\r\n return VALID_NODE_TYPES.has(type as GraphNode['type']) || type in NODE_TYPE_ALIASES;\r\n}\r\n\r\nexport function is_valid_edge_type(type: string): boolean {\r\n return VALID_EDGE_TYPES.has(type as GraphEdge['type']) || type in EDGE_TYPE_ALIASES;\r\n}\r\n\r\nexport { NODE_TYPE_ALIASES, EDGE_TYPE_ALIASES, VALID_NODE_TYPES, VALID_EDGE_TYPES };","import { NodeId, EdgeId, RepoId } from '../types/index.ts';\r\nimport type { GraphNode, GraphEdge, GraphData, GraphDelta, EdgeDirection, Layer } from '../types/index.ts';\r\nimport { scoped } from './Logger.ts';\r\nimport { normalizeNodeType, normalizeEdgeType } from './AliasMapper.ts';\r\n\r\nconst log = scoped('validator');\r\n\r\nexport type IssueLevel = 'auto-corrected' | 'dropped' | 'fatal';\r\n\r\nexport interface GraphIssue {\r\n level: IssueLevel;\r\n category: string;\r\n message: string;\r\n path: string;\r\n}\r\n\r\nexport interface ValidationResult {\r\n data: GraphData;\r\n issues: GraphIssue[];\r\n}\r\n\r\nexport function coerceNode(raw: unknown, i: number): GraphNode | null {\r\n if (!raw || typeof raw !== 'object') return null;\r\n const o = raw as Record<string, unknown>;\r\n const id = typeof o.id === 'string' ? o.id : `node-${i}`;\r\n const label = typeof o.label === 'string' ? o.label : typeof o.name === 'string' ? o.name : id;\r\n const type = normalizeNodeType(o.type) ?? 'variable';\r\n const filePath = typeof o.filePath === 'string' ? o.filePath : typeof o.file === 'string' ? o.file : '';\r\n const lineNumber = typeof o.lineNumber === 'number' ? o.lineNumber : typeof o.line === 'number' ? o.line : 0;\r\n const properties = (o.properties && typeof o.properties === 'object' ? o.properties : {}) as Record<string, unknown>;\r\n return { id: NodeId(id), label, type, filePath, lineNumber, properties };\r\n}\r\n\r\nexport function coerceEdge(raw: unknown, i: number): GraphEdge | null {\r\n if (!raw || typeof raw !== 'object') return null;\r\n const o = raw as Record<string, unknown>;\r\n const id = typeof o.id === 'string' ? o.id : `edge-${i}`;\r\n const source = typeof o.source === 'string' ? o.source : typeof o.from === 'string' ? o.from : null;\r\n const target = typeof o.target === 'string' ? o.target : typeof o.to === 'string' ? o.to : null;\r\n if (!source || !target) return null;\r\n const type = normalizeEdgeType(o.type) ?? 'dependency';\r\n const properties = (o.properties && typeof o.properties === 'object' ? o.properties : {}) as Record<string, unknown>;\r\n const direction: EdgeDirection = o.direction === 'forward' || o.direction === 'backward' || o.direction === 'bidirectional' ? (o.direction as EdgeDirection) : 'forward';\r\n const weight = typeof o.weight === 'number' && o.weight >= 0 && o.weight <= 1 ? o.weight : 1;\r\n return { id: EdgeId(id), source: NodeId(source), target: NodeId(target), type, direction, weight, properties };\r\n}\r\n\r\nexport function coerceGraphData(raw: unknown): GraphData | null {\r\n if (!raw || typeof raw !== 'object') return null;\r\n const o = raw as Record<string, unknown>;\r\n const rawNodes = Array.isArray(o.nodes) ? o.nodes : [];\r\n const rawEdges = Array.isArray(o.edges) ? o.edges : [];\r\n const nodes = rawNodes.map(coerceNode).filter((n): n is GraphNode => n !== null);\r\n if (nodes.length === 0) return null;\r\n const nodeIds = new Set(nodes.map((n) => n.id));\r\n const edges = rawEdges\r\n .map(coerceEdge)\r\n .filter((e): e is GraphEdge => e !== null)\r\n .filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));\r\n const metadata = o.metadata as Record<string, unknown> | undefined;\r\n const repoId = (metadata?.repoId as string | undefined) ?? `workspace-${Date.now()}`;\r\n const timestamp = typeof metadata?.timestamp === 'number' ? metadata.timestamp : Date.now();\r\n const truncated = typeof metadata?.truncated === 'boolean' ? metadata.truncated : false;\r\n const totalNodeCount = typeof metadata?.totalNodeCount === 'number' ? metadata.totalNodeCount : nodes.length;\r\n const totalEdgeCount = typeof metadata?.totalEdgeCount === 'number' ? metadata.totalEdgeCount : edges.length;\r\n // 璇玑图增量引擎健康信号(cycle-122):透传而不是丢弃。\r\n // coerce 是「修形」不是「重造」——把已验证的未知形状字段剥掉,就会重演\r\n // cycle-88「AST budget 的 hasMore 三层各丢一次」的家族缺陷(生产者给了、\r\n // 传输层剥了、消费者永远看不到)。形状校验:applied 必须是布尔才采信。\r\n const rawXuanji = metadata?.xuanji as Record<string, unknown> | undefined;\r\n const xuanji = rawXuanji && typeof rawXuanji.applied === 'boolean'\r\n ? {\r\n applied: rawXuanji.applied,\r\n ...(typeof rawXuanji.addedPaths === 'number' ? { addedPaths: rawXuanji.addedPaths } : {}),\r\n ...(Array.isArray(rawXuanji.degradedRules) ? { degradedRules: rawXuanji.degradedRules as string[] } : {}),\r\n ...(Array.isArray(rawXuanji.invariantViolations) ? { invariantViolations: rawXuanji.invariantViolations as string[] } : {}),\r\n }\r\n : undefined;\r\n return {\r\n nodes,\r\n edges,\r\n metadata: {\r\n repoId: RepoId(repoId),\r\n timestamp,\r\n nodeCount: nodes.length,\r\n edgeCount: edges.length,\r\n truncated,\r\n totalNodeCount,\r\n totalEdgeCount,\r\n ...(xuanji !== undefined ? { xuanji } : {}),\r\n },\r\n };\r\n}\r\n\r\nexport function filterSelfLoopEdges(edges: GraphEdge[]): GraphEdge[] {\r\n return edges.filter((e) => e.source !== e.target);\r\n}\r\n\r\n/**\r\n * Validate a `graph/delta` payload (P2-21).\r\n *\r\n * `coerceNode`/`coerceEdge` drop `parentId` and `layer`, which the renderer needs for an\r\n * incrementally-added node to sit in the same place a full load would have put it, so both are\r\n * re-attached here. Returns `null` when the payload is not an object at all — the caller then falls\r\n * back to a full reload rather than applying a partial graph.\r\n */\r\nexport function coerceGraphDelta(raw: unknown): GraphDelta | null {\r\n if (!raw || typeof raw !== 'object') return null;\r\n const o = raw as Record<string, unknown>;\r\n\r\n const nodeList = (value: unknown): GraphNode[] => {\r\n if (!Array.isArray(value)) return [];\r\n const out: GraphNode[] = [];\r\n value.forEach((entry, i) => {\r\n // `coerceNode` invents `node-${i}` when an entry carries no id. That is tolerable for a full\r\n // load (the graph is self-consistent either way) but not for a delta: an invented id is a node\r\n // the host never described, and `canApplyGraphDelta` cannot notice because an added node needs\r\n // no prior existence. Delta entries therefore have to name themselves.\r\n if (!entry || typeof entry !== 'object') return;\r\n const src = entry as Record<string, unknown>;\r\n if (typeof src.id !== 'string' || !src.id) return;\r\n const node = coerceNode(entry, i);\r\n if (!node) return;\r\n if (typeof src.parentId === 'string' && src.parentId) node.parentId = NodeId(src.parentId);\r\n if (typeof src.layer === 'string' && src.layer) node.layer = src.layer;\r\n out.push(node);\r\n });\r\n return out;\r\n };\r\n\r\n const edgeList = (value: unknown): GraphEdge[] => {\r\n if (!Array.isArray(value)) return [];\r\n const out: GraphEdge[] = [];\r\n value.forEach((entry, i) => {\r\n // Same reasoning as `nodeList`: `coerceEdge` falls back to `edge-${i}`.\r\n if (!entry || typeof entry !== 'object') return;\r\n const src = entry as Record<string, unknown>;\r\n if (typeof src.id !== 'string' || !src.id) return;\r\n const edge = coerceEdge(entry, i);\r\n if (edge) out.push(edge);\r\n });\r\n return out;\r\n };\r\n\r\n return {\r\n addedNodes: nodeList(o.addedNodes),\r\n removedNodes: nodeList(o.removedNodes),\r\n modifiedNodes: nodeList(o.modifiedNodes),\r\n addedEdges: edgeList(o.addedEdges),\r\n removedEdges: edgeList(o.removedEdges),\r\n modifiedEdges: edgeList(o.modifiedEdges),\r\n };\r\n}\r\n\r\nexport function sanitizeTypedGraph(nodes: GraphNode[], edges: GraphEdge[]): { nodes: GraphNode[]; edges: GraphEdge[] } {\r\n const seenNodeIds = new Set<string>();\r\n const validNodes: GraphNode[] = [];\r\n for (const node of nodes) {\r\n if (!node || !node.id || !node.label || !node.type || !node.filePath) continue;\r\n if (seenNodeIds.has(node.id)) continue;\r\n seenNodeIds.add(node.id);\r\n validNodes.push(node);\r\n }\r\n\r\n const nodeIdSet = new Set(validNodes.map((n) => n.id));\r\n const seenEdgeIds = new Set<string>();\r\n const validEdges: GraphEdge[] = [];\r\n for (const edge of edges) {\r\n if (!edge || !edge.id || !edge.source || !edge.target) continue;\r\n if (edge.source === edge.target) continue;\r\n if (!nodeIdSet.has(edge.source) || !nodeIdSet.has(edge.target)) continue;\r\n if (seenEdgeIds.has(edge.id)) continue;\r\n seenEdgeIds.add(edge.id);\r\n validEdges.push(edge);\r\n }\r\n\r\n return { nodes: validNodes, edges: validEdges };\r\n}\r\n\r\n\r\nfunction sanitizeNode(raw: unknown): unknown {\r\n if (raw === null || raw === undefined) return null;\r\n if (typeof raw !== 'object') return null;\r\n const node = raw as Record<string, unknown>;\r\n if (node.tags === null || node.tags === undefined) node.tags = [];\r\n if (node.properties === null || node.properties === undefined) node.properties = {};\r\n if (node.type && typeof node.type === 'string') node.type = node.type.toLowerCase();\r\n if (node.direction && typeof node.direction === 'string') node.direction = node.direction.toLowerCase();\r\n return node;\r\n}\r\n\r\nfunction sanitizeEdge(raw: unknown): unknown {\r\n if (raw === null || raw === undefined) return null;\r\n if (typeof raw !== 'object') return null;\r\n const edge = raw as Record<string, unknown>;\r\n if (edge.properties === null || edge.properties === undefined) edge.properties = {};\r\n if (edge.type && typeof edge.type === 'string') edge.type = edge.type.toLowerCase();\r\n if (edge.direction && typeof edge.direction === 'string') edge.direction = edge.direction.toLowerCase();\r\n return edge;\r\n}\r\n\r\n\r\nfunction autoFixNode(raw: Record<string, unknown>, issues: GraphIssue[], path: string): GraphNode | null {\r\n const id = raw.id;\r\n if (typeof id !== 'string' || id.length === 0) {\r\n issues.push({ level: 'dropped', category: 'node', message: 'missing or invalid id', path });\r\n return null;\r\n }\r\n\r\n const label = typeof raw.label === 'string' ? raw.label : (raw.name as string | undefined) ?? id;\r\n if (raw.label !== label) {\r\n issues.push({ level: 'auto-corrected', category: 'node', message: `label missing, using id/name: ${label}`, path: `${path}.label` });\r\n }\r\n\r\n let type = normalizeNodeType(raw.type);\r\n if (!type) {\r\n type = 'function';\r\n issues.push({ level: 'auto-corrected', category: 'node', message: `unknown type \"${raw.type}\", defaulting to \"function\"`, path: `${path}.type` });\r\n }\r\n\r\n const filePath = typeof raw.filePath === 'string' ? raw.filePath : typeof raw.file === 'string' ? raw.file : '';\r\n if (raw.filePath !== filePath) {\r\n issues.push({ level: 'auto-corrected', category: 'node', message: 'filePath missing, defaulting to empty', path: `${path}.filePath` });\r\n }\r\n\r\n const lineNumber = typeof raw.lineNumber === 'number' && !Number.isNaN(raw.lineNumber) ? raw.lineNumber : typeof raw.line === 'number' && !Number.isNaN(raw.line) ? raw.line : undefined;\r\n const properties = (raw.properties && typeof raw.properties === 'object') ? raw.properties as Record<string, unknown> : {};\r\n const parentId = typeof raw.parentId === 'string' && raw.parentId.length > 0 ? raw.parentId as NodeId : undefined;\r\n const summary = typeof raw.summary === 'string' ? raw.summary : undefined;\r\n const tags = Array.isArray(raw.tags) ? raw.tags.filter((t: unknown) => typeof t === 'string') : [];\r\n const complexity = raw.complexity === 'simple' || raw.complexity === 'moderate' || raw.complexity === 'complex' ? raw.complexity : undefined;\r\n const layer = typeof raw.layer === 'string' ? raw.layer : undefined;\r\n\r\n const node: GraphNode = {\r\n id: id as NodeId,\r\n label,\r\n type,\r\n filePath,\r\n properties,\r\n tags,\r\n };\r\n if (lineNumber !== undefined) node.lineNumber = lineNumber;\r\n if (parentId !== undefined) node.parentId = parentId;\r\n if (summary !== undefined) node.summary = summary;\r\n if (complexity !== undefined) node.complexity = complexity;\r\n if (layer !== undefined) node.layer = layer;\r\n\r\n return node;\r\n}\r\n\r\nfunction autoFixEdge(raw: Record<string, unknown>, issues: GraphIssue[], path: string): GraphEdge | null {\r\n const id = raw.id;\r\n if (typeof id !== 'string' || id.length === 0) {\r\n issues.push({ level: 'dropped', category: 'edge', message: 'missing or invalid id', path });\r\n return null;\r\n }\r\n\r\n const source = raw.source ?? raw.from;\r\n if (typeof source !== 'string' || source.length === 0) {\r\n issues.push({ level: 'dropped', category: 'edge', message: 'missing or invalid source', path });\r\n return null;\r\n }\r\n\r\n const target = raw.target ?? raw.to;\r\n if (typeof target !== 'string' || target.length === 0) {\r\n issues.push({ level: 'dropped', category: 'edge', message: 'missing or invalid target', path });\r\n return null;\r\n }\r\n\r\n let type = normalizeEdgeType(raw.type);\r\n if (!type) {\r\n type = 'association';\r\n issues.push({ level: 'auto-corrected', category: 'edge', message: `unknown type \"${raw.type}\", defaulting to \"association\"`, path: `${path}.type` });\r\n }\r\n\r\n const direction: EdgeDirection = raw.direction === 'forward' || raw.direction === 'backward' || raw.direction === 'bidirectional' ? (raw.direction as EdgeDirection) : 'forward';\r\n if (raw.direction !== direction && raw.direction !== undefined) {\r\n issues.push({ level: 'auto-corrected', category: 'edge', message: `invalid direction \"${raw.direction}\", defaulting to \"forward\"`, path: `${path}.direction` });\r\n }\r\n\r\n let weight = typeof raw.weight === 'number' && !Number.isNaN(raw.weight) ? raw.weight : 1;\r\n if (weight < 0) {\r\n weight = 0;\r\n issues.push({ level: 'auto-corrected', category: 'edge', message: 'weight clamped to 0', path: `${path}.weight` });\r\n }\r\n if (weight > 1) {\r\n weight = 1;\r\n issues.push({ level: 'auto-corrected', category: 'edge', message: 'weight clamped to 1', path: `${path}.weight` });\r\n }\r\n\r\n const properties = (raw.properties && typeof raw.properties === 'object') ? raw.properties as Record<string, unknown> : {};\r\n\r\n const edge: GraphEdge = {\r\n id: id as EdgeId,\r\n source: source as NodeId,\r\n target: target as NodeId,\r\n type,\r\n direction,\r\n weight,\r\n properties,\r\n };\r\n\r\n return edge;\r\n}\r\n\r\nfunction validateReferentialIntegrity(nodes: GraphNode[], edges: GraphEdge[], issues: GraphIssue[]): GraphEdge[] {\r\n const nodeIds = new Set(nodes.map((n) => n.id));\r\n const validEdges: GraphEdge[] = [];\r\n let droppedCount = 0;\r\n\r\n for (const edge of edges) {\r\n if (!nodeIds.has(edge.source)) {\r\n issues.push({ level: 'dropped', category: 'edge', message: `source node \"${edge.source}\" not found`, path: `edge.${edge.id}.source` });\r\n droppedCount++;\r\n continue;\r\n }\r\n if (!nodeIds.has(edge.target)) {\r\n issues.push({ level: 'dropped', category: 'edge', message: `target node \"${edge.target}\" not found`, path: `edge.${edge.id}.target` });\r\n droppedCount++;\r\n continue;\r\n }\r\n validEdges.push(edge);\r\n }\r\n\r\n if (droppedCount > 0) {\r\n log.warn(`referential integrity: dropped ${droppedCount} edges with missing endpoints`);\r\n }\r\n\r\n return validEdges;\r\n}\r\n\r\nfunction validateNoDuplicateIds(nodes: GraphNode[], edges: GraphEdge[], issues: GraphIssue[]): { nodes: GraphNode[]; edges: GraphEdge[] } {\r\n const seenNodeIds = new Set<string>();\r\n const dedupedNodes: GraphNode[] = [];\r\n let nodeDupCount = 0;\r\n\r\n for (const node of nodes) {\r\n if (seenNodeIds.has(node.id)) {\r\n issues.push({ level: 'dropped', category: 'node', message: `duplicate node id \"${node.id}\"`, path: `node.${node.id}` });\r\n nodeDupCount++;\r\n continue;\r\n }\r\n seenNodeIds.add(node.id);\r\n dedupedNodes.push(node);\r\n }\r\n\r\n const seenEdgeIds = new Set<string>();\r\n const dedupedEdges: GraphEdge[] = [];\r\n let edgeDupCount = 0;\r\n\r\n for (const edge of edges) {\r\n if (seenEdgeIds.has(edge.id)) {\r\n issues.push({ level: 'dropped', category: 'edge', message: `duplicate edge id \"${edge.id}\"`, path: `edge.${edge.id}` });\r\n edgeDupCount++;\r\n continue;\r\n }\r\n seenEdgeIds.add(edge.id);\r\n dedupedEdges.push(edge);\r\n }\r\n\r\n if (nodeDupCount > 0) log.warn(`dedup: removed ${nodeDupCount} duplicate node ids`);\r\n if (edgeDupCount > 0) log.warn(`dedup: removed ${edgeDupCount} duplicate edge ids`);\r\n\r\n return { nodes: dedupedNodes, edges: dedupedEdges };\r\n}\r\n\r\ninterface ProcessedInput {\r\n fixedNodes: GraphNode[];\r\n fixedEdges: GraphEdge[];\r\n issues: GraphIssue[];\r\n root: Record<string, unknown>;\r\n}\r\n\r\nfunction processRawInput(raw: unknown): ProcessedInput | ValidationResult {\r\n if (raw === null || raw === undefined || typeof raw !== 'object') {\r\n return {\r\n data: { nodes: [], edges: [], metadata: { repoId: '' as RepoId, timestamp: 0, nodeCount: 0, edgeCount: 0 } },\r\n issues: [{ level: 'fatal', category: 'data', message: 'input is null or not an object', path: 'root' }],\r\n };\r\n }\r\n\r\n const root = raw as Record<string, unknown>;\r\n const issues: GraphIssue[] = [];\r\n\r\n const rawNodes = Array.isArray(root.nodes) ? root.nodes : [];\r\n const rawEdges = Array.isArray(root.edges) ? root.edges : [];\r\n\r\n const sanitizedNodes = rawNodes.map(n => sanitizeNode(n)).filter((n): n is Record<string, unknown> => n !== null);\r\n const sanitizedEdges = rawEdges.map(e => sanitizeEdge(e)).filter((e): e is Record<string, unknown> => e !== null);\r\n\r\n const fixedNodes: GraphNode[] = [];\r\n for (const sanitizedNode of sanitizedNodes) {\r\n const fixed = autoFixNode(sanitizedNode, issues, `node[${fixedNodes.length}]`);\r\n if (fixed) fixedNodes.push(fixed);\r\n }\r\n\r\n const fixedEdges: GraphEdge[] = [];\r\n for (const sanitizedEdge of sanitizedEdges) {\r\n const fixed = autoFixEdge(sanitizedEdge, issues, `edge[${fixedEdges.length}]`);\r\n if (fixed) fixedEdges.push(fixed);\r\n }\r\n\r\n return { fixedNodes, fixedEdges, issues, root };\r\n}\r\n\r\nfunction buildDefaultMetadata(root: Record<string, unknown>, nodeCount: number, edgeCount: number): GraphData['metadata'] {\r\n return (root.metadata && typeof root.metadata === 'object')\r\n ? root.metadata as GraphData['metadata']\r\n : { repoId: '' as RepoId, timestamp: Date.now(), nodeCount, edgeCount };\r\n}\r\n\r\nexport function validateGraphData(raw: unknown): ValidationResult {\r\n const processed = processRawInput(raw);\r\n if ('data' in processed) return processed;\r\n\r\n const { fixedNodes, fixedEdges, issues, root } = processed;\r\n\r\n if (!Array.isArray(root.nodes)) {\r\n issues.push({ level: 'auto-corrected', category: 'data', message: 'nodes is not an array, defaulting to empty', path: 'nodes' });\r\n }\r\n if (!Array.isArray(root.edges)) {\r\n issues.push({ level: 'auto-corrected', category: 'data', message: 'edges is not an array, defaulting to empty', path: 'edges' });\r\n }\r\n\r\n const deduped = validateNoDuplicateIds(fixedNodes, fixedEdges, issues);\r\n const validEdges = validateReferentialIntegrity(deduped.nodes, deduped.edges, issues);\r\n\r\n const metadata = buildDefaultMetadata(root, deduped.nodes.length, validEdges.length);\r\n\r\n if (!root.metadata || typeof root.metadata !== 'object') {\r\n issues.push({ level: 'auto-corrected', category: 'data', message: 'metadata missing, generated default', path: 'metadata' });\r\n }\r\n\r\n const data: GraphData = {\r\n nodes: deduped.nodes,\r\n edges: validEdges,\r\n metadata: {\r\n ...metadata,\r\n nodeCount: deduped.nodes.length,\r\n edgeCount: validEdges.length,\r\n },\r\n };\r\n if (Array.isArray(root.layers) && root.layers.length > 0) {\r\n data.layers = root.layers as unknown as Layer[];\r\n }\r\n\r\n const fatalCount = issues.filter((i) => i.level === 'fatal').length;\r\n const droppedCount = issues.filter((i) => i.level === 'dropped').length;\r\n const autoCorrectedCount = issues.filter((i) => i.level === 'auto-corrected').length;\r\n\r\n if (issues.length > 0) {\r\n log.info(`validation complete: ${autoCorrectedCount} auto-corrected, ${droppedCount} dropped, ${fatalCount} fatal`);\r\n }\r\n\r\n return { data, issues };\r\n}\r\n\r\nexport function sanitizeGraphData(raw: unknown): ValidationResult {\r\n const processed = processRawInput(raw);\r\n if ('data' in processed) return processed;\r\n\r\n const { fixedNodes, fixedEdges, issues, root } = processed;\r\n\r\n const metadata = buildDefaultMetadata(root, fixedNodes.length, fixedEdges.length);\r\n\r\n const data: GraphData = {\r\n nodes: fixedNodes,\r\n edges: fixedEdges,\r\n metadata: {\r\n ...metadata,\r\n nodeCount: fixedNodes.length,\r\n edgeCount: fixedEdges.length,\r\n },\r\n };\r\n\r\n return { data, issues };\r\n}","// GraphDataMerger - multi-source aggregation with dedup + delta merge.\r\nimport type { GraphNode, GraphEdge, GraphData, AdapterResult, RepoId } from '../types/index.ts';\r\nimport { sanitizeGraphData } from '../shared/GraphValidator.ts';\r\nimport { stableStringify } from '../shared/graphFingerprint.ts';\r\n\r\n/**\r\n * 边来源归因的取值 —— 与 `CodeGraphAdapter` / `TreeSitterAdapter` 写进 `properties.edgeOrigin`\r\n * 的那一套同源。\r\n *\r\n * 只有**可证明**的取值在这里:`ast`(语法结构造出来的边,无歧义)、`db`(SQLite 索引的 edges\r\n * 行)、`heuristic`(适配器靠启发式拼出来的边)。上游边表没有 confidence 列,所以这里也\r\n * 刻意不出现 `EXTRACTED`/`INFERRED` —— 那种取值无法由本仓库的任何数据证明。\r\n *\r\n * 生产代码里**实际有两个**档位在产出(cycle-111 收口 P1-N4):\r\n * · `db` —— `src/adapters/codeGraphRows.ts` 的 `readEdges*` 系列,来自 SQLite edges 行;\r\n * · `ast` —— `src/adapters/TreeSitterAdapter.ts` 的 `contains` 边(语法树父子关系的直接投影)。\r\n * 修前该文件写的是 `properties: {}`,于是 `EDGE_ORIGIN_RANK.ast = 3` 这一档\r\n * **从未在真实数据上生效过** —— 一条写着却永不触发的裁决规则。\r\n *\r\n * `heuristic` 至今**仍然没有生产者**(全仓只有 `tests/unit/mergeDeterminism.test.ts` 的 fixture\r\n * 在用它)。保留该档位是因为它描述的是\"适配器靠启发式拼出来的边\"这一**可能**的来源,\r\n * 删掉它会让将来第一个启发式适配器不得不连裁决表一起改;但**不宣称它已在被使用** ——\r\n * 这正是本仓反复记录的\"注释宣称的特性 vs 代码里真实发生的特性\"的区别。若下一轮仍无生产者,\r\n * 应当连同 `EDGE_ORIGIN_RANK` 的这一档一起删掉,而不是继续留着。\r\n *\r\n * 因此 `EDGE_ORIGIN_RANK.ast = 3` 现在是**可被观测**的:同一对端点的 `contains` 边若同时来自\r\n * 索引行与语法树,语法树那份胜出(`compareEdgePreference` 第 1 层)。\r\n * `tests/unit/mergeDeterminism.test.ts` 有一条用例直接钉住这个结果。\r\n */\r\nexport type EdgeOrigin = 'ast' | 'db' | 'heuristic';\r\n\r\n/** 来源强弱:语法结构 > 索引行 > 启发式。缺失/未知来源按 0 处理(最弱)。 */\r\nexport const EDGE_ORIGIN_RANK: Readonly<Record<EdgeOrigin, number>> = Object.freeze({\r\n ast: 3,\r\n db: 2,\r\n heuristic: 1,\r\n});\r\n\r\n/**\r\n * 关系具体度:`dependency` 是 `EDGE_KIND_MAP` 对**未知上游 kind** 的兜底值\r\n * (`CodeGraphAdapter.mapEdgeKind`),所以它必然比具名关系更弱;具名关系之间不再区分强弱。\r\n */\r\nconst EDGE_TYPE_SPECIFICITY: Readonly<Record<GraphEdge['type'], number>> = Object.freeze({\r\n call: 2,\r\n import: 2,\r\n extend: 2,\r\n implement: 2,\r\n contains: 2,\r\n association: 1,\r\n aggregation: 1,\r\n composition: 1,\r\n dependency: 0,\r\n});\r\n\r\nfunction edgeOriginRank(edge: GraphEdge): number {\r\n const origin = edge.properties.edgeOrigin;\r\n if (typeof origin !== 'string') return 0;\r\n return EDGE_ORIGIN_RANK[origin as EdgeOrigin] ?? 0;\r\n}\r\n\r\nfunction hasDeclaredDirection(edge: GraphEdge): number {\r\n return edge.direction === undefined ? 0 : 1;\r\n}\r\n\r\nfunction edgeSpecificity(edge: GraphEdge): number {\r\n return EDGE_TYPE_SPECIFICITY[edge.type] ?? 0;\r\n}\r\n\r\nfunction isLocatable(edge: GraphEdge): number {\r\n return edge.properties.edgeLocatable === true ? 1 : 0;\r\n}\r\n\r\n/**\r\n * 同一条边被多个适配器同时描述时的**总序**:返回值 > 0 表示 `a` 胜出。\r\n *\r\n * 分四层,每层只在上一层打平时参与裁决:\r\n * 1. 来源强弱(`EDGE_ORIGIN_RANK`);\r\n * 2. 是否声明了方向(`direction` 有值 = 信息更完整);\r\n * 3. 关系具体度(具名关系 > `dependency` 兜底值);\r\n * 4. 是否可定位(`edgeLocatable === true`,即该行有 line 可回溯)。\r\n *\r\n * 四层全平时用**规范化序列化**兜底。这一层没有语义偏好,唯一目的是让\"平局\"也有确定答案:\r\n * 没有它,结果就退回\"后写覆盖\",与入参顺序有关(本函数存在的全部理由)。方向(取较大者)\r\n * 是任意选择,已被 `tests/unit/mergeDeterminism.test.ts` 钉死,改动它必须是有意行为。\r\n */\r\nexport function compareEdgePreference(a: GraphEdge, b: GraphEdge): number {\r\n const layers = [\r\n edgeOriginRank(a) - edgeOriginRank(b),\r\n hasDeclaredDirection(a) - hasDeclaredDirection(b),\r\n edgeSpecificity(a) - edgeSpecificity(b),\r\n isLocatable(a) - isLocatable(b),\r\n ];\r\n for (const delta of layers) {\r\n if (delta !== 0) return delta;\r\n }\r\n const sa = stableStringify(a);\r\n const sb = stableStringify(b);\r\n return sa < sb ? -1 : sa > sb ? 1 : 0;\r\n}\r\n\r\n/** 输出数组的稳定次序:按 id 升序。入参排列不能影响合并结果的字节形状。 */\r\nfunction compareById(a: { id: string }, b: { id: string }): number {\r\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\r\n}\r\n\r\n/**\r\n * 属性键排序后重建对象。\r\n *\r\n * `JSON.stringify` 保留键的插入顺序,而\"先铺输家再铺赢家\"的合并顺序会让同名键的插入次序随\r\n * 入参排列变化 —— 值相同、字节不同。属性表没有语义顺序,所以输出前统一排序。\r\n *\r\n * `undefined` 值一并丢弃:它们不会出现在序列化里,留着只影响 `Object.keys()` 的读数。\r\n */\r\nfunction canonicalProperties(properties: Record<string, unknown>): Record<string, unknown> {\r\n const result: Record<string, unknown> = {};\r\n for (const key of Object.keys(properties).sort()) {\r\n const value = properties[key];\r\n if (value !== undefined) result[key] = value;\r\n }\r\n return result;\r\n}\r\n\r\n/** 一条边的裁决状态:胜出记录本身 + 所有来源的属性并集。 */\r\ninterface EdgeSlot {\r\n winner: GraphEdge;\r\n properties: Record<string, unknown>;\r\n}\r\n\r\nexport class GraphDataMerger {\r\n /**\r\n * 多源合并。**结果与 `results` 的排列无关**(同一组输入,任意顺序产出逐字节相同的 GraphData,\r\n * 时间戳除外,它来自 `Date.now()`)。\r\n *\r\n * 边冲突不再\"后写覆盖\":`compareEdgePreference` 给出总序,胜者的标量字段与属性值胜出,\r\n * 输者的独有属性键仍然保留(属性并集)。每个键的取值来自\"偏好顺序最高且含有该键\"的那条记录,\r\n * 因此并集本身也与顺序无关。\r\n *\r\n * 节点冲突仍保持既有的\"后写覆盖\"语义(`GraphDataMerger.test.ts` 的\r\n * \"with second source winning\" 用例把它钉死):节点没有与边对等的可证明归因维度,\r\n * 本轮不去发明一个。输出数组按 id 排序,所以**只要节点集合不冲突**,排列不影响结果。\r\n */\r\n merge(results: AdapterResult[], repoId: RepoId): GraphData {\r\n const nodes = new Map<string, GraphNode>();\r\n const edges = new Map<string, EdgeSlot>();\r\n\r\n for (const r of results) {\r\n for (const node of r.nodes) {\r\n const existing = nodes.get(node.id);\r\n if (existing) {\r\n nodes.set(node.id, {\r\n ...existing,\r\n ...node,\r\n properties: { ...existing.properties, ...node.properties, __source: r.source },\r\n });\r\n } else {\r\n nodes.set(node.id, {\r\n ...node,\r\n properties: { ...node.properties, __source: r.source },\r\n });\r\n }\r\n }\r\n for (const edge of r.edges) {\r\n const candidate: GraphEdge = {\r\n ...edge,\r\n properties: { ...edge.properties, __source: r.source },\r\n };\r\n const existing = edges.get(edge.id as string);\r\n if (!existing) {\r\n edges.set(edge.id as string, {\r\n winner: candidate,\r\n properties: { ...candidate.properties },\r\n });\r\n continue;\r\n }\r\n if (compareEdgePreference(candidate, existing.winner) > 0) {\r\n edges.set(edge.id as string, {\r\n winner: candidate,\r\n properties: { ...existing.properties, ...candidate.properties },\r\n });\r\n } else {\r\n edges.set(edge.id as string, {\r\n winner: existing.winner,\r\n properties: { ...candidate.properties, ...existing.properties },\r\n });\r\n }\r\n }\r\n }\r\n\r\n const rawData = {\r\n nodes: Array.from(nodes.values()).sort(compareById),\r\n edges: Array.from(edges.values())\r\n .map((slot) => ({ ...slot.winner, properties: canonicalProperties(slot.properties) }))\r\n .sort(compareById),\r\n metadata: {\r\n repoId: repoId,\r\n timestamp: Date.now(),\r\n nodeCount: nodes.size,\r\n edgeCount: edges.size,\r\n },\r\n };\r\n\r\n const { data, issues } = sanitizeGraphData(rawData);\r\n if (issues.length > 0) {\r\n const dropped = issues.filter((i: { level: string }) => i.level === 'dropped').length;\r\n const autoCorrected = issues.filter((i: { level: string }) => i.level === 'auto-corrected').length;\r\n if (dropped > 0 || autoCorrected > 0) {\r\n data.metadata.nodeCount = data.nodes.length;\r\n data.metadata.edgeCount = data.edges.length;\r\n }\r\n }\r\n\r\n\r\n return data;\r\n }\r\n\r\n // Delta merge for incremental heat-updates.\r\n //\r\n // 这里刻意**不**套用 `compareEdgePreference`:delta 的冲突是**时间**顺序而不是多源顺序,\r\n // 它是\"同一份图的更新一版\",后者必须胜出。换成偏好裁决会让一次合法的字段更新被旧记录拒绝\r\n // (`GraphDataMerger.test.ts` 的 \"should update existing edges with delta data\" 就是这条契约)。\r\n applyDelta(current: GraphData, delta: AdapterResult): GraphData {\r\n const nodes = new Map(current.nodes.map(n => [n.id, n]));\r\n const edges = new Map(current.edges.map(e => [e.id, e]));\r\n\r\n for (const node of delta.nodes) {\r\n const existing = nodes.get(node.id);\r\n if (existing) {\r\n nodes.set(node.id, {\r\n ...existing,\r\n ...node,\r\n properties: { ...existing.properties, ...node.properties },\r\n });\r\n } else {\r\n nodes.set(node.id, node);\r\n }\r\n }\r\n for (const edge of delta.edges) {\r\n const existing = edges.get(edge.id);\r\n if (existing) {\r\n edges.set(edge.id, {\r\n ...existing,\r\n ...edge,\r\n properties: { ...existing.properties, ...edge.properties },\r\n });\r\n } else {\r\n edges.set(edge.id, edge);\r\n }\r\n }\r\n\r\n return {\r\n nodes: Array.from(nodes.values()),\r\n edges: Array.from(edges.values()),\r\n metadata: {\r\n ...current.metadata,\r\n timestamp: Date.now(),\r\n nodeCount: nodes.size,\r\n edgeCount: edges.size,\r\n },\r\n };\r\n }\r\n}\r\n","import type { GraphNode, GraphEdge } from '../types/index.ts';\r\nimport type { GraphDelta } from '../shared/graphDelta.ts';\r\nimport { DIFF_ENGINE } from '../shared/constants.ts';\r\n\r\n\r\nfunction deepEqual(a: unknown, b: unknown, seen?: Map<unknown, unknown>, depth?: number): boolean {\r\n if (a === b) return true;\r\n if (a === null || b === null) return false;\r\n if (typeof a !== 'object' || typeof b !== 'object') return false;\r\n if (depth !== undefined && depth > DIFF_ENGINE.MAX_DEEP_EQUAL_DEPTH) return false;\r\n if (Array.isArray(a) || Array.isArray(b)) {\r\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\r\n if (a.length !== b.length) return false;\r\n const nextDepth = (depth ?? 0) + 1;\r\n for (let i = 0; i < a.length; i++) {\r\n if (!deepEqual(a[i], b[i], seen, nextDepth)) return false;\r\n }\r\n return true;\r\n }\r\n if (!seen) seen = new Map();\r\n if (seen.has(a)) return seen.get(a) === b;\r\n seen.set(a, b);\r\n const nextDepth = (depth ?? 0) + 1;\r\n const aKeys = Object.keys(a as Record<string, unknown>).sort();\r\n const bKeys = Object.keys(b as Record<string, unknown>).sort();\r\n if (aKeys.length !== bKeys.length) return false;\r\n for (let i = 0; i < aKeys.length; i++) {\r\n if (aKeys[i] !== bKeys[i]) return false;\r\n if (!deepEqual((a as Record<string, unknown>)[aKeys[i]!], (b as Record<string, unknown>)[bKeys[i]!], seen, nextDepth)) return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Host-side diff result. Aliased to the shared `GraphDelta` shape so the incremental watcher can\r\n * publish it verbatim over `graph/delta` and the client can apply it with `applyGraphDelta()`\r\n * without a field-by-field translation.\r\n */\r\nexport type GraphDiff = GraphDelta;\r\n\r\nexport function computeGraphDiff(\r\n beforeNodes: GraphNode[],\r\n beforeEdges: GraphEdge[],\r\n afterNodes: GraphNode[],\r\n afterEdges: GraphEdge[],\r\n): GraphDiff {\r\n const beforeNodeMap = new Map<string, GraphNode>();\r\n for (const n of beforeNodes) beforeNodeMap.set(n.id, n);\r\n const afterNodeMap = new Map<string, GraphNode>();\r\n for (const n of afterNodes) afterNodeMap.set(n.id, n);\r\n\r\n const beforeEdgeMap = new Map<string, GraphEdge>();\r\n for (const e of beforeEdges) beforeEdgeMap.set(e.id, e);\r\n const afterEdgeMap = new Map<string, GraphEdge>();\r\n for (const e of afterEdges) afterEdgeMap.set(e.id, e);\r\n\r\n const addedNodes: GraphNode[] = [];\r\n const removedNodes: GraphNode[] = [];\r\n const modifiedNodes: GraphNode[] = [];\r\n\r\n for (const [id, node] of afterNodeMap) {\r\n if (!beforeNodeMap.has(id)) {\r\n addedNodes.push(node);\r\n } else {\r\n const before = beforeNodeMap.get(id)!;\r\n if (before.label !== node.label || before.type !== node.type || before.filePath !== node.filePath ||\r\n before.lineNumber !== node.lineNumber || before.parentId !== node.parentId ||\r\n !deepEqual(before.properties, node.properties)) {\r\n modifiedNodes.push(node);\r\n }\r\n }\r\n }\r\n for (const [id, node] of beforeNodeMap) {\r\n if (!afterNodeMap.has(id)) removedNodes.push(node);\r\n }\r\n\r\n const addedEdges: GraphEdge[] = [];\r\n const removedEdges: GraphEdge[] = [];\r\n const modifiedEdges: GraphEdge[] = [];\r\n for (const [id, edge] of afterEdgeMap) {\r\n const before = beforeEdgeMap.get(id);\r\n if (!before) {\r\n addedEdges.push(edge);\r\n } else {\r\n if (before.type !== edge.type || before.source !== edge.source || before.target !== edge.target) {\r\n modifiedEdges.push(edge);\r\n }\r\n }\r\n }\r\n for (const [id, edge] of beforeEdgeMap) {\r\n if (!afterEdgeMap.has(id)) removedEdges.push(edge);\r\n }\r\n\r\n return { addedNodes, removedNodes, modifiedNodes, addedEdges, removedEdges, modifiedEdges };\r\n}\r\n\r\nexport function diffToGraphData(diff: GraphDiff): { nodes: GraphNode[]; edges: GraphEdge[] } {\r\n const nodes: GraphNode[] = [];\r\n const edges: GraphEdge[] = [];\r\n\r\n for (const n of diff.addedNodes) {\r\n nodes.push({ ...n, properties: { ...n.properties, diffStatus: 'added' } });\r\n }\r\n for (const n of diff.modifiedNodes) {\r\n nodes.push({ ...n, properties: { ...n.properties, diffStatus: 'modified' } });\r\n }\r\n for (const n of diff.removedNodes) {\r\n nodes.push({ ...n, properties: { ...n.properties, diffStatus: 'removed' } });\r\n }\r\n\r\n for (const e of diff.addedEdges) {\r\n edges.push({ ...e, properties: { ...e.properties, diffStatus: 'added' } });\r\n }\r\n for (const e of diff.modifiedEdges) {\r\n edges.push({ ...e, properties: { ...e.properties, diffStatus: 'modified' } });\r\n }\r\n for (const e of diff.removedEdges) {\r\n edges.push({ ...e, properties: { ...e.properties, diffStatus: 'removed' } });\r\n }\r\n\r\n return { nodes, edges };\r\n}","import type { GraphNode } from '../../types/index.ts';\r\n\r\nexport function normalizeFilePath(filePath: string): string {\r\n return filePath.replace(/^(file|import):/, '');\r\n}\r\n\r\nexport function extractModulePath(filePath: string, maxDepth: number): string {\r\n const rawPath = normalizeFilePath(filePath);\r\n const parts = rawPath.split(/[\\\\/]/);\r\n const dirParts = parts.length > 1 ? parts.slice(0, -1) : [rawPath];\r\n return dirParts.length > maxDepth\r\n ? dirParts.slice(0, maxDepth).join('/')\r\n : dirParts.join('/');\r\n}\r\n\r\nexport function buildModuleMap(nodes: GraphNode[], maxDepth: number): Map<string, string> {\r\n const moduleOfNode = new Map<string, string>();\r\n for (const node of nodes) {\r\n moduleOfNode.set(node.id, extractModulePath(node.filePath, maxDepth));\r\n }\r\n return moduleOfNode;\r\n}","import type { GraphEdge } from '../../types/index.ts';\r\n\r\nexport type AdjacencyMap = Map<string, string[]>;\r\n\r\nexport interface DirectedAdjacencyMaps {\r\n forward: AdjacencyMap;\r\n backward: AdjacencyMap;\r\n}\r\n\r\nexport function buildForwardAdjacency(edges: GraphEdge[]): AdjacencyMap {\r\n const map: AdjacencyMap = new Map();\r\n for (const edge of edges) {\r\n const list = map.get(edge.source);\r\n if (list) list.push(edge.target);\r\n else map.set(edge.source, [edge.target]);\r\n }\r\n return map;\r\n}\r\n\r\nexport function buildBackwardAdjacency(edges: GraphEdge[]): AdjacencyMap {\r\n const map: AdjacencyMap = new Map();\r\n for (const edge of edges) {\r\n const list = map.get(edge.target);\r\n if (list) list.push(edge.source);\r\n else map.set(edge.target, [edge.source]);\r\n }\r\n return map;\r\n}\r\n\r\nexport function buildDirectedAdjacency(edges: GraphEdge[]): DirectedAdjacencyMaps {\r\n const forward: AdjacencyMap = new Map();\r\n const backward: AdjacencyMap = new Map();\r\n for (const edge of edges) {\r\n let fwdList = forward.get(edge.source);\r\n if (!fwdList) { fwdList = []; forward.set(edge.source, fwdList); }\r\n fwdList.push(edge.target);\r\n\r\n let bwdList = backward.get(edge.target);\r\n if (!bwdList) { bwdList = []; backward.set(edge.target, bwdList); }\r\n bwdList.push(edge.source);\r\n }\r\n return { forward, backward };\r\n}\r\n\r\nexport function buildUndirectedAdjacency(edges: GraphEdge[]): Map<string, Set<string>> {\r\n const map = new Map<string, Set<string>>();\r\n for (const edge of edges) {\r\n let neighbors = map.get(edge.source);\r\n if (!neighbors) { neighbors = new Set(); map.set(edge.source, neighbors); }\r\n neighbors.add(edge.target);\r\n\r\n neighbors = map.get(edge.target);\r\n if (!neighbors) { neighbors = new Set(); map.set(edge.target, neighbors); }\r\n neighbors.add(edge.source);\r\n }\r\n return map;\r\n}\r\n\r\nexport function buildFilteredUndirectedAdjacency(\r\n edges: GraphEdge[],\r\n nodeIds: Set<string>,\r\n): Map<string, Set<string>> {\r\n const map = new Map<string, Set<string>>();\r\n for (const id of nodeIds) map.set(id, new Set());\r\n for (const edge of edges) {\r\n if (!nodeIds.has(edge.source) || !nodeIds.has(edge.target)) continue;\r\n let neighbors = map.get(edge.source);\r\n if (!neighbors) { neighbors = new Set(); map.set(edge.source, neighbors); }\r\n neighbors.add(edge.target);\r\n\r\n neighbors = map.get(edge.target);\r\n if (!neighbors) { neighbors = new Set(); map.set(edge.target, neighbors); }\r\n neighbors.add(edge.source);\r\n }\r\n return map;\r\n}\r\n\r\nexport function buildFilteredForwardAdjacency(edges: GraphEdge[], edgeTypes: string[]): AdjacencyMap {\r\n const map: AdjacencyMap = new Map();\r\n for (const edge of edges) {\r\n if (!edgeTypes.includes(edge.type)) continue;\r\n const list = map.get(edge.source);\r\n if (list) list.push(edge.target);\r\n else map.set(edge.source, [edge.target]);\r\n }\r\n return map;\r\n}","import type { GraphNode, GraphEdge } from '../../types/index.ts';\r\nimport { buildModuleMap } from './modulePath.ts';\r\nimport { buildFilteredForwardAdjacency } from './AdjacencyList.ts';\r\nimport { HEALTH_SCORE } from '../constants.ts';\r\n\r\nexport interface HealthScore {\r\n coupling: number;\r\n cohesion: number;\r\n cyclicDependencyRate: number;\r\n modularity: number;\r\n overall: number;\r\n}\r\n\r\nexport interface ScoreDimension {\r\n name: string;\r\n score: number;\r\n description: string;\r\n}\r\n\r\nconst EDGE_WEIGHTS: Record<GraphEdge['type'], number> = {\r\n call: 1.0,\r\n extend: 0.8,\r\n implement: 0.7,\r\n import: 0.5,\r\n dependency: 0.3,\r\n contains: 0.0,\r\n association: 0.6,\r\n aggregation: 0.5,\r\n composition: 0.7,\r\n};\r\n\r\nconst DEFAULT_MAX_MODULE_DEPTH = HEALTH_SCORE.DEFAULT_MAX_MODULE_DEPTH;\r\n\r\nexport class HealthScorer {\r\n private readonly maxModuleDepth: number;\r\n\r\n constructor(maxModuleDepth?: number) {\r\n this.maxModuleDepth = maxModuleDepth ?? DEFAULT_MAX_MODULE_DEPTH;\r\n }\r\n\r\n calculate(nodes: GraphNode[], edges: GraphEdge[]): HealthScore {\r\n if (nodes.length === 0) {\r\n return { coupling: 0, cohesion: 0, cyclicDependencyRate: 0, modularity: 0, overall: 0 };\r\n }\r\n\r\n const moduleOfNode = buildModuleMap(nodes, this.maxModuleDepth);\r\n\r\n const coupling = this.calculateCoupling(edges, moduleOfNode);\r\n const cohesion = this.calculateCohesion(nodes, edges, moduleOfNode);\r\n const cyclicDependencyRate = this.calculateCyclicDependencyRate(edges);\r\n const modularity = this.calculateModularity(nodes, edges, moduleOfNode);\r\n const overall = Math.round(\r\n coupling * HEALTH_SCORE.WEIGHT_COUPLING + cohesion * HEALTH_SCORE.WEIGHT_COHESION + cyclicDependencyRate * HEALTH_SCORE.WEIGHT_CYCLIC + modularity * HEALTH_SCORE.WEIGHT_MODULARITY,\r\n );\r\n\r\n return { coupling, cohesion, cyclicDependencyRate, modularity, overall };\r\n }\r\n\r\n getDimensions(score: HealthScore): ScoreDimension[] {\r\n return [\r\n { name: 'coupling', score: score.coupling, description: 'Lower inter-module coupling is better' },\r\n { name: 'cohesion', score: score.cohesion, description: 'Higher intra-module cohesion is better' },\r\n { name: 'cyclicDependencyRate', score: score.cyclicDependencyRate, description: 'Fewer circular dependencies is better' },\r\n { name: 'modularity', score: score.modularity, description: 'Better module separation is better' },\r\n ];\r\n }\r\n\r\n static findOrphanNodes(nodes: GraphNode[], edges: GraphEdge[]): GraphNode[] {\r\n const connectedIds = new Set<string>();\r\n for (const edge of edges) {\r\n connectedIds.add(edge.source);\r\n connectedIds.add(edge.target);\r\n }\r\n return nodes.filter(n => !connectedIds.has(n.id));\r\n }\r\n\r\n static findLeafNodes(nodes: GraphNode[], edges: GraphEdge[]): GraphNode[] {\r\n const degreeMap = new Map<string, number>();\r\n for (const edge of edges) {\r\n degreeMap.set(edge.source, (degreeMap.get(edge.source) ?? 0) + 1);\r\n degreeMap.set(edge.target, (degreeMap.get(edge.target) ?? 0) + 1);\r\n }\r\n return nodes.filter(n => (degreeMap.get(n.id) ?? 0) === 1);\r\n }\r\n\r\n static findHubNodes(nodes: GraphNode[], edges: GraphEdge[], threshold = 10): GraphNode[] {\r\n const degreeMap = new Map<string, number>();\r\n for (const edge of edges) {\r\n degreeMap.set(edge.source, (degreeMap.get(edge.source) ?? 0) + 1);\r\n degreeMap.set(edge.target, (degreeMap.get(edge.target) ?? 0) + 1);\r\n }\r\n return nodes\r\n .filter(n => (degreeMap.get(n.id) ?? 0) >= threshold)\r\n .sort((a, b) => (degreeMap.get(b.id) ?? 0) - (degreeMap.get(a.id) ?? 0));\r\n }\r\n\r\n static calculateOrphanRate(nodes: GraphNode[], edges: GraphEdge[]): number {\r\n if (nodes.length === 0) return 0;\r\n const orphans = HealthScorer.findOrphanNodes(nodes, edges);\r\n return orphans.length / nodes.length;\r\n }\r\n\r\n private calculateCoupling(edges: GraphEdge[], moduleOfNode: Map<string, string>): number {\r\n if (edges.length === 0) return 100;\r\n\r\n const efferent = new Map<string, Set<string>>();\r\n const afferent = new Map<string, Set<string>>();\r\n\r\n for (const edge of edges) {\r\n if (EDGE_WEIGHTS[edge.type] === 0) continue;\r\n const srcModule = moduleOfNode.get(edge.source);\r\n const tgtModule = moduleOfNode.get(edge.target);\r\n if (!srcModule || !tgtModule || srcModule === tgtModule) continue;\r\n\r\n let eff = efferent.get(srcModule);\r\n if (!eff) { eff = new Set(); efferent.set(srcModule, eff); }\r\n eff.add(tgtModule);\r\n\r\n let aff = afferent.get(tgtModule);\r\n if (!aff) { aff = new Set(); afferent.set(tgtModule, aff); }\r\n aff.add(srcModule);\r\n }\r\n\r\n const modules = new Set<string>();\r\n for (const m of moduleOfNode.values()) modules.add(m);\r\n if (modules.size <= 1) return 100;\r\n\r\n let totalInstability = 0;\r\n for (const mod of modules) {\r\n const ce = (efferent.get(mod)?.size ?? 0);\r\n const ca = (afferent.get(mod)?.size ?? 0);\r\n const instability = ce + ca === 0 ? 0 : ce / (ce + ca);\r\n totalInstability += instability;\r\n }\r\n\r\n if (modules.size === 0) return 100;\r\n const avgInstability = totalInstability / modules.size;\r\n return Math.round(Math.max(0, 100 - avgInstability * 100));\r\n }\r\n\r\n private calculateCohesion(nodes: GraphNode[], edges: GraphEdge[], moduleOfNode: Map<string, string>): number {\r\n const moduleStats = new Map<string, { internal: number; total: number; nodeCount: number }>();\r\n\r\n const ensure = (m: string) => {\r\n let stats = moduleStats.get(m);\r\n if (!stats) { stats = { internal: 0, total: 0, nodeCount: 0 }; moduleStats.set(m, stats); }\r\n return stats;\r\n };\r\n\r\n for (const node of nodes) {\r\n const mod = moduleOfNode.get(node.id);\r\n if (mod) ensure(mod).nodeCount++;\r\n }\r\n\r\n for (const edge of edges) {\r\n if (EDGE_WEIGHTS[edge.type] === 0) continue;\r\n const srcModule = moduleOfNode.get(edge.source);\r\n const tgtModule = moduleOfNode.get(edge.target);\r\n if (!srcModule && !tgtModule) continue;\r\n\r\n if (srcModule && tgtModule && srcModule === tgtModule) {\r\n const stats = ensure(srcModule);\r\n stats.total++;\r\n stats.internal++;\r\n } else {\r\n if (srcModule) ensure(srcModule).total++;\r\n if (tgtModule) ensure(tgtModule).total++;\r\n }\r\n }\r\n\r\n if (moduleStats.size === 0) return 50;\r\n\r\n let weightedCohesion = 0;\r\n let totalNodes = 0;\r\n for (const stats of moduleStats.values()) {\r\n if (stats.nodeCount === 0) continue;\r\n const cohesionRatio = stats.total > 0 ? stats.internal / stats.total : 0;\r\n weightedCohesion += cohesionRatio * stats.nodeCount;\r\n totalNodes += stats.nodeCount;\r\n }\r\n\r\n if (totalNodes === 0) return 50;\r\n return Math.round((weightedCohesion / totalNodes) * 100);\r\n }\r\n\r\n private calculateCyclicDependencyRate(edges: GraphEdge[]): number {\r\n const cycleEdgeTypes: GraphEdge['type'][] = ['call', 'extend', 'implement', 'import'];\r\n const adjacency = buildFilteredForwardAdjacency(edges, cycleEdgeTypes);\r\n const nodeIds = new Set<string>();\r\n\r\n for (const edge of edges) {\r\n if (!cycleEdgeTypes.includes(edge.type)) continue;\r\n nodeIds.add(edge.source);\r\n nodeIds.add(edge.target);\r\n }\r\n\r\n if (nodeIds.size === 0) return 100;\r\n\r\n const sccs = this.tarjanSCC(adjacency, nodeIds);\r\n let cycleNodeCount = 0;\r\n for (const scc of sccs) {\r\n if (scc.length > 1) {\r\n cycleNodeCount += scc.length;\r\n } else if (scc.length === 1) {\r\n const id = scc[0]!;\r\n const neighbors = adjacency.get(id);\r\n if (neighbors && neighbors.includes(id)) {\r\n cycleNodeCount++;\r\n }\r\n }\r\n }\r\n\r\n const cycleRate = cycleNodeCount / nodeIds.size;\r\n return Math.round(Math.max(0, 100 - cycleRate * 100));\r\n }\r\n\r\n private calculateModularity(nodes: GraphNode[], edges: GraphEdge[], moduleOfNode: Map<string, string>): number {\r\n if (nodes.length === 0) return 0;\r\n\r\n const modules = new Set<string>();\r\n for (const m of moduleOfNode.values()) modules.add(m);\r\n\r\n if (modules.size <= 1) return 50;\r\n\r\n const moduleSizes = new Map<string, number>();\r\n for (const m of moduleOfNode.values()) {\r\n moduleSizes.set(m, (moduleSizes.get(m) ?? 0) + 1);\r\n }\r\n\r\n const avgSize = nodes.length / modules.size;\r\n let sizeVariance = 0;\r\n for (const size of moduleSizes.values()) {\r\n sizeVariance += (size - avgSize) ** 2;\r\n }\r\n sizeVariance /= modules.size;\r\n const cv = Math.sqrt(sizeVariance) / Math.max(1, avgSize);\r\n const balanceScore = Math.max(0, 100 - cv * 50);\r\n\r\n let internalEdges = 0;\r\n let totalRelevantEdges = 0;\r\n for (const edge of edges) {\r\n if (EDGE_WEIGHTS[edge.type] === 0) continue;\r\n const srcModule = moduleOfNode.get(edge.source);\r\n const tgtModule = moduleOfNode.get(edge.target);\r\n if (!srcModule || !tgtModule) continue;\r\n totalRelevantEdges++;\r\n if (srcModule === tgtModule) internalEdges++;\r\n }\r\n\r\n if (totalRelevantEdges === 0) return Math.round(balanceScore);\r\n const internalRatio = internalEdges / totalRelevantEdges;\r\n const expectedRatio = 1 / modules.size;\r\n const modularityQ = internalRatio - expectedRatio;\r\n const separationScore = Math.max(0, Math.min(100, modularityQ * 100 * modules.size + 50));\r\n\r\n return Math.round((balanceScore + separationScore) / 2);\r\n }\r\n\r\n private tarjanSCC(\r\n adjacency: Map<string, string[]>,\r\n nodeIds: Set<string>,\r\n ): string[][] {\r\n let index = 0;\r\n const indices = new Map<string, number>();\r\n const lowLinks = new Map<string, number>();\r\n const onStack = new Set<string>();\r\n const stack: string[] = [];\r\n const sccs: string[][] = [];\r\n\r\n for (const startId of nodeIds) {\r\n if (indices.has(startId)) continue;\r\n\r\n const callStack: Array<{ v: string; neighborIdx: number; neighbors: string[] }> = [];\r\n callStack.push({ v: startId, neighborIdx: 0, neighbors: adjacency.get(startId) ?? [] });\r\n\r\n while (callStack.length > 0) {\r\n const frame = callStack[callStack.length - 1]!;\r\n const v = frame.v;\r\n\r\n if (frame.neighborIdx === 0) {\r\n indices.set(v, index);\r\n lowLinks.set(v, index);\r\n index++;\r\n stack.push(v);\r\n onStack.add(v);\r\n }\r\n\r\n if (frame.neighborIdx < frame.neighbors.length) {\r\n const w = frame.neighbors[frame.neighborIdx]!;\r\n frame.neighborIdx++;\r\n\r\n if (!indices.has(w)) {\r\n callStack.push({ v: w, neighborIdx: 0, neighbors: adjacency.get(w) ?? [] });\r\n } else if (onStack.has(w)) {\r\n lowLinks.set(v, Math.min(lowLinks.get(v)!, indices.get(w)!));\r\n }\r\n } else {\r\n if (lowLinks.get(v) === indices.get(v)) {\r\n const scc: string[] = [];\r\n let w: string;\r\n do {\r\n w = stack.pop()!;\r\n onStack.delete(w);\r\n scc.push(w);\r\n } while (w !== v);\r\n sccs.push(scc);\r\n }\r\n\r\n callStack.pop();\r\n if (callStack.length > 0) {\r\n const parentFrame = callStack[callStack.length - 1]!;\r\n lowLinks.set(parentFrame.v, Math.min(lowLinks.get(parentFrame.v)!, lowLinks.get(v)!));\r\n }\r\n }\r\n }\r\n }\r\n\r\n return sccs;\r\n }\r\n}\r\n","/**\n * Booth 最小旋转(O(L) 循环规范代表元)。\n *\n * 璇玑图原型:同一首回文诗从不同字起读仍是同一首,取字典序最小旋转\n * 作为等价类代表元,使「循环等价」在去重时天然成立。\n *\n * 纯函数、零依赖——库的最底层(spec §九:ops 不依赖任何宿主)。\n *\n * @module xuanji/ops/booth\n */\n\n/**\n * 返回数组字典序最小旋转的起始下标。\n *\n * O(L) 时间、O(L) 空间(Booth 1975)。对长度 0/1 直接返回 0。\n */\nexport function minimalRotationIndex<T extends string>(arr: readonly T[]): number {\n const n = arr.length;\n if (n <= 1) return 0;\n\n const s: T[] = [...arr, ...arr];\n const f = new Int32Array(2 * n).fill(-1);\n let k = 0;\n\n for (let j = 1; j < 2 * n; j++) {\n let i = f[j - k - 1]!;\n // s[j] 与 s[k + i + 1] 都在双倍串内,索引有界(j < 2n,k + i + 1 < 2n)\n while (i !== -1 && s[j]! !== s[k + i + 1]!) {\n if (s[j]! < s[k + i + 1]!) k = j - i - 1;\n i = f[i]!;\n }\n if (i === -1 && s[j]! !== s[k + i + 1]!) {\n if (s[j]! < s[k + i + 1]!) k = j;\n f[j - k] = -1;\n } else {\n f[j - k] = i + 1;\n }\n }\n\n return k;\n}\n\n/**\n * 返回字典序最小的循环旋转。\n *\n * 幂等:`booth(booth(x)) === booth(x)`;循环等价:任意旋转的结果相同。\n * 空数组返回空数组(不抛错——退化输入不制造异常,spec §八)。\n */\nexport function boothMinimalRotation<T extends string>(arr: readonly T[]): T[] {\n const n = arr.length;\n if (n <= 1) return [...arr];\n const k = minimalRotationIndex(arr);\n return [...arr.slice(k), ...arr.slice(0, k)];\n}\n\n/**\n * 判断一个序列是否已经是其等价类的最小旋转。\n * 供测试与不变量检查使用(O(L))。\n */\nexport function isMinimalRotation<T extends string>(arr: readonly T[]): boolean {\n return minimalRotationIndex(arr) === 0;\n}\n","/**\n * 有向环存在性判定(早终止变体,spec §5.6 的轻量替代)。\n *\n * 璇玑图原型:只想确认「这方锦缎里有没有回文」,不必把所有诗都读一遍。\n * O(V+E),遇到第一个 back edge 立即返回 true,不枚举环列表。\n *\n * @module xuanji/ops/cycle-existence\n */\n\nimport type { EdgeType, GraphSpace } from '../core/graph-space.ts';\n\n/**\n * 邻接表上的环存在性判定。\n * adjacency 允许邻居数组有重复(不要求去重)。\n */\nexport function hasCycleInAdjacency(\n adjacency: ReadonlyMap<string, readonly string[]>,\n): boolean {\n const visited = new Set<string>();\n const inStack = new Set<string>();\n\n for (const start of adjacency.keys()) {\n if (visited.has(start)) continue;\n const frames: Array<{ id: string; neighbors: readonly string[]; next: number }> = [\n { id: start, neighbors: adjacency.get(start) ?? [], next: 0 },\n ];\n visited.add(start);\n inStack.add(start);\n\n while (frames.length > 0) {\n const frame = frames[frames.length - 1]!;\n if (frame.next >= frame.neighbors.length) {\n inStack.delete(frame.id);\n frames.pop();\n continue;\n }\n const target = frame.neighbors[frame.next++]!;\n if (inStack.has(target)) return true;\n if (!visited.has(target)) {\n visited.add(target);\n inStack.add(target);\n frames.push({ id: target, neighbors: adjacency.get(target) ?? [], next: 0 });\n }\n }\n }\n return false;\n}\n\n/** 图空间上的环存在性判定(按边类型过滤)。 */\nexport function hasDirectedCycle(\n graph: GraphSpace,\n types: ReadonlySet<EdgeType>,\n): boolean {\n const adjacency = new Map<string, string[]>();\n for (const id of graph.nodeIds()) adjacency.set(id, graph.outNeighbors(id, types));\n return hasCycleInAdjacency(adjacency);\n}\n","import type { AdjacencyMap } from './AdjacencyList.ts';\nimport { boothMinimalRotation } from '../../xuanji/ops/booth.ts';\n\n/**\n * Booth 最小旋转 / 最小旋转判定由璇玑库提供,此处**只做 re-export**。\n *\n * 契约(cycle-107 · C-1):宿主不得复制库实现。引用相等性由\n * `tests/unit/CycleDFS.test.ts` 直接断言(`behaves === libBooth`),\n * 任何「顺手复制一份」的回归都会让该用例立刻变红。\n */\nexport {\n boothMinimalRotation,\n isMinimalRotation,\n minimalRotationIndex,\n} from '../../xuanji/ops/booth.ts';\n\n/**\n * 有向环存在性判定同样由璇玑库提供(早终止变体,O(V+E))。\n *\n * 语义与旧宿主实现逐字等价:遇到第一个 back edge 立即返回 true,不枚举环列表。\n * `RuleEngine` 用它做「无环即早退」,避免在无环图上白跑一次全量 DFS。\n */\nexport { hasCycleInAdjacency as hasCycle } from '../../xuanji/ops/cycle-existence.ts';\n\nexport interface CycleDetectionResult {\n cycles: string[][];\n}\n\nexport const MAX_CYCLES_DEFAULT = 1000;\n\n/**\n * 枚举环(DFS + booth 规范代表元去重)。\n *\n * 去重键仍然建立在库的 `boothMinimalRotation` 之上:同一个环从不同节点起读\n * 会被规范成同一把键,因此 `a→b→a` 与 `b→a→b` 只产出一次。\n *\n * 行为契约保持不变(cycle-107 · C-1):返回值仍是闭合路径\n * `[...cycleNodes, cycleStart]`,顺序与最大环数截断语义均与旧实现一致。\n */\nexport function detectCyclesDFS(adjacency: AdjacencyMap, maxCycles: number = MAX_CYCLES_DEFAULT): string[][] {\n const visited = new Set<string>();\n const inStack = new Set<string>();\n const cycles: string[][] = [];\n const seenCycleKeys = new Set<string>();\n\n const enter = (start: string): void => {\n const path: string[] = [];\n const pathIndex = new Map<string, number>();\n const frames: Array<{ id: string; neighbors: string[]; next: number }> = [\n { id: start, neighbors: adjacency.get(start) ?? [], next: 0 },\n ];\n visited.add(start);\n inStack.add(start);\n path.push(start);\n pathIndex.set(start, 0);\n\n while (frames.length > 0) {\n if (cycles.length >= maxCycles) return;\n const frame = frames[frames.length - 1]!;\n if (frame.next >= frame.neighbors.length) {\n inStack.delete(frame.id);\n path.pop();\n pathIndex.delete(frame.id);\n frames.pop();\n continue;\n }\n const target = frame.neighbors[frame.next++]!;\n if (inStack.has(target)) {\n const cycleStart = pathIndex.get(target);\n if (cycleStart !== undefined) {\n const cycleNodes = path.slice(cycleStart);\n const cycleKey = boothMinimalRotation(cycleNodes).join('|');\n if (!seenCycleKeys.has(cycleKey)) {\n seenCycleKeys.add(cycleKey);\n cycles.push([...cycleNodes, target]);\n if (cycles.length >= maxCycles) return;\n }\n }\n } else if (!visited.has(target)) {\n visited.add(target);\n inStack.add(target);\n path.push(target);\n pathIndex.set(target, path.length - 1);\n frames.push({ id: target, neighbors: adjacency.get(target) ?? [], next: 0 });\n }\n }\n };\n\n for (const id of adjacency.keys()) {\n if (cycles.length >= maxCycles) break;\n if (!visited.has(id)) enter(id);\n }\n\n return cycles;\n}\n","import type { GraphNode, GraphEdge, RuleType, RuleDefinition, RuleViolation } from '../../types/index.ts';\nimport { NodeId, EdgeId } from '../../types/index.ts';\nimport type { AdjacencyMap } from './AdjacencyList.ts';\nimport { buildFilteredForwardAdjacency } from './AdjacencyList.ts';\nimport { detectCyclesDFS, hasCycle } from './CycleDFS.ts';\n\nexport type { RuleType, RuleDefinition, RuleViolation };\n\n/**\n * 一次 `RuleEngine.check()` 内共享的邻接表提供者。\n *\n * 同一组 edgeTypes 的多条规则只会构建一次邻接表(键 = 排序后的\n * `edgeTypes.join('|')`),避免「N 条规则各建一遍全图邻接」的重复遍历。\n */\nexport interface AdjacencyProvider {\n /**\n * 取(并在本次 check 内缓存)按边类型过滤的前向邻接表。\n * 邻接表只读使用:检测器不得就地修改它(同一对象会被多条规则共享)。\n */\n adjacencyFor(edgeTypes: readonly string[]): AdjacencyMap;\n}\n\nexport interface RuleDetector {\n detect(rule: RuleDefinition, nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[];\n /**\n * 可选:由 `RuleEngine.check` 在调用 `detect` 前注入共享上下文,调用结束后以 `null` 清空。\n *\n * 这是**可选**方法,既有第三方检测器无需实现即可继续工作;未实现时检测器自己建邻接表,\n * 行为与旧实现完全一致。实现方必须容忍 `null`(表示「本次 check 已结束」)。\n */\n setContext?(context: AdjacencyProvider | null): void;\n}\n\nconst DEFAULT_FORBIDDEN_EDGE_TYPES: readonly string[] = ['call', 'import', 'dependency'];\n\nconst PRESET_RULES: RuleDefinition[] = [\n {\n id: 'no-circular-deps',\n name: 'No Circular Dependencies',\n type: 'forbidden-edge',\n params: { edgeTypes: ['call', 'import', 'dependency'] },\n severity: 'error',\n enabled: false,\n },\n {\n id: 'max-indegree-10',\n name: 'Max Indegree ≤ 10',\n type: 'max-indegree',\n params: { threshold: 10 },\n severity: 'warning',\n enabled: false,\n },\n {\n id: 'max-outdegree-15',\n name: 'Max Outdegree ≤ 15',\n type: 'max-outdegree',\n params: { threshold: 15 },\n severity: 'warning',\n enabled: false,\n },\n {\n id: 'required-call-edge',\n name: 'Public Functions Must Have Callers',\n type: 'required-edge',\n params: { edgeType: 'call', sourceType: 'function' },\n severity: 'warning',\n enabled: false,\n },\n {\n id: 'no-cross-layer-call',\n name: 'No Cross-Layer Calls',\n type: 'cross-layer-call',\n params: {\n layers: { 'view': ['model'], 'model': ['view'] },\n edgeType: 'call',\n },\n severity: 'error',\n enabled: false,\n },\n];\n\nexport function getPresetRules(): RuleDefinition[] {\n return PRESET_RULES.map((r) => ({ ...r }));\n}\n\nexport class RuleEngine {\n private detectors: Map<RuleType, RuleDetector> = new Map();\n\n constructor() {\n this.registerDetector('forbidden-edge', new ForbiddenEdgeDetector());\n this.registerDetector('max-indegree', new MaxIndegreeDetector());\n this.registerDetector('max-outdegree', new MaxOutdegreeDetector());\n this.registerDetector('required-edge', new RequiredEdgeDetector());\n this.registerDetector('cross-layer-call', new CrossLayerCallDetector());\n }\n\n registerDetector(type: RuleType, detector: RuleDetector): void {\n this.detectors.set(type, detector);\n }\n\n /**\n * 跑一遍启用中的规则。\n *\n * 本次调用内共享一张邻接缓存(同 edgeTypes 组合只构建一次),并把提供者注入给\n * 实现了可选 `setContext` 的检测器;无论正常返回还是抛错,都会在 finally 里清空上下文,\n * 避免检测器跨调用持有上一次的图(那会让后续 check 读到旧邻接表)。\n */\n check(rules: RuleDefinition[], nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const adjacencyCache = new Map<string, AdjacencyMap>();\n const provider: AdjacencyProvider = {\n adjacencyFor: (edgeTypes: readonly string[]): AdjacencyMap => {\n const key = [...edgeTypes].sort().join('|');\n let adjacency = adjacencyCache.get(key);\n if (!adjacency) {\n adjacency = buildFilteredForwardAdjacency(edges, [...edgeTypes]);\n adjacencyCache.set(key, adjacency);\n }\n return adjacency;\n },\n };\n\n const violations: RuleViolation[] = [];\n const injected: RuleDetector[] = [];\n\n try {\n for (const rule of rules) {\n if (!rule.enabled) continue;\n const detector = this.detectors.get(rule.type);\n if (!detector) continue;\n if (detector.setContext) {\n detector.setContext(provider);\n injected.push(detector);\n }\n violations.push(...detector.detect(rule, nodes, edges));\n }\n } finally {\n for (const detector of injected) detector.setContext?.(null);\n }\n\n return violations;\n }\n}\n\nclass ForbiddenEdgeDetector implements RuleDetector {\n private context: AdjacencyProvider | null = null;\n\n setContext(context: AdjacencyProvider | null): void {\n this.context = context;\n }\n\n detect(rule: RuleDefinition, _nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const violations: RuleViolation[] = [];\n const edgeTypes = (rule.params.edgeTypes as string[] | undefined) ?? [...DEFAULT_FORBIDDEN_EDGE_TYPES];\n\n const adjacency = this.context\n ? this.context.adjacencyFor(edgeTypes)\n : buildFilteredForwardAdjacency(edges, edgeTypes);\n\n // 无环即早退:环存在性 O(V+E) 且遇到第一个 back edge 立刻返回,\n // 无环图上省掉一次「枚举所有环 + booth 去重」的全量 DFS。\n if (!hasCycle(adjacency)) return violations;\n\n const cycles = detectCyclesDFS(adjacency);\n\n for (const cycle of cycles) {\n const cycleKey = cycle.sort().join('→');\n const firstNode = cycle[0] ?? '';\n violations.push({\n ruleId: rule.id,\n nodeId: NodeId(firstNode),\n message: `Circular dependency detected involving: ${cycleKey}`,\n severity: rule.severity,\n });\n }\n\n return violations;\n }\n}\n\nclass MaxIndegreeDetector implements RuleDetector {\n detect(rule: RuleDefinition, _nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const violations: RuleViolation[] = [];\n const threshold = (rule.params.threshold as number) ?? 10;\n\n const indegree = new Map<string, number>();\n for (const edge of edges) {\n indegree.set(edge.target, (indegree.get(edge.target) ?? 0) + 1);\n }\n\n for (const [nodeId, count] of indegree) {\n if (count > threshold) {\n violations.push({\n ruleId: rule.id,\n nodeId: NodeId(nodeId),\n message: `Node ${nodeId} has indegree ${count} (max ${threshold})`,\n severity: rule.severity,\n });\n }\n }\n\n return violations;\n }\n}\n\nclass MaxOutdegreeDetector implements RuleDetector {\n detect(rule: RuleDefinition, _nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const violations: RuleViolation[] = [];\n const threshold = (rule.params.threshold as number) ?? 15;\n\n const outdegree = new Map<string, number>();\n for (const edge of edges) {\n outdegree.set(edge.source, (outdegree.get(edge.source) ?? 0) + 1);\n }\n\n for (const [nodeId, count] of outdegree) {\n if (count > threshold) {\n violations.push({\n ruleId: rule.id,\n nodeId: NodeId(nodeId),\n message: `Node ${nodeId} has outdegree ${count} (max ${threshold})`,\n severity: rule.severity,\n });\n }\n }\n\n return violations;\n }\n}\n\nclass RequiredEdgeDetector implements RuleDetector {\n detect(rule: RuleDefinition, nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const violations: RuleViolation[] = [];\n const requiredType = (rule.params.edgeType as string) ?? 'call';\n const sourceType = (rule.params.sourceType as string) ?? undefined;\n const targetType = (rule.params.targetType as string) ?? undefined;\n\n const nodeMap = new Map<string, GraphNode>();\n for (const n of nodes) nodeMap.set(n.id, n);\n\n const hasEdge = new Set<string>();\n for (const edge of edges) {\n if (edge.type !== requiredType) continue;\n if (sourceType) {\n const src = nodeMap.get(edge.source);\n if (!src || src.type !== sourceType) continue;\n }\n if (targetType) {\n const tgt = nodeMap.get(edge.target);\n if (!tgt || tgt.type !== targetType) continue;\n }\n hasEdge.add(edge.source);\n }\n\n for (const n of nodes) {\n if (sourceType && n.type !== sourceType) continue;\n if (!hasEdge.has(n.id)) {\n violations.push({\n ruleId: rule.id,\n nodeId: NodeId(n.id),\n message: `Node ${n.label} is missing required ${requiredType} edge`,\n severity: rule.severity,\n });\n }\n }\n\n return violations;\n }\n}\n\nclass CrossLayerCallDetector implements RuleDetector {\n detect(rule: RuleDefinition, nodes: GraphNode[], edges: GraphEdge[]): RuleViolation[] {\n const violations: RuleViolation[] = [];\n const layerMap = (rule.params.layers as Record<string, string[]>) ?? {};\n const callType = (rule.params.edgeType as string) ?? 'call';\n\n if (Object.keys(layerMap).length === 0) return violations;\n\n const nodeLayer = new Map<string, string>();\n for (const n of nodes) {\n for (const [layerName, patterns] of Object.entries(layerMap)) {\n if (patterns.some((p) => {\n const seg = n.filePath.split(/[\\\\/]/);\n return seg.indexOf(p) >= 0 || n.label === p;\n })) {\n nodeLayer.set(n.id, layerName);\n break;\n }\n }\n }\n\n const forbiddenPairs = new Set<string>();\n for (const [srcLayer, tgtLayers] of Object.entries(layerMap)) {\n for (const tgtLayer of tgtLayers) {\n forbiddenPairs.add(`${srcLayer}->${tgtLayer}`);\n }\n }\n\n for (const edge of edges) {\n if (edge.type !== callType) continue;\n const srcLayer = nodeLayer.get(edge.source);\n const tgtLayer = nodeLayer.get(edge.target);\n if (!srcLayer || !tgtLayer) continue;\n if (forbiddenPairs.has(`${srcLayer}->${tgtLayer}`)) {\n violations.push({\n ruleId: rule.id,\n edgeId: EdgeId(edge.id),\n message: `Cross-layer call from ${srcLayer} to ${tgtLayer}`,\n severity: rule.severity,\n });\n }\n }\n\n return violations;\n }\n}\n","/**\r\n * 图空间 G = (V, E, Σ, τ, A)。\r\n *\r\n * 璇玑图原型:一方 29×29 的字格锦缎。所有「读法」(路径规则)共享同一个图空间,\r\n * 图空间只负责存储与邻接遍历,不解释语义(spec §4.1)。\r\n *\r\n * 关键契约:\r\n * - 边键为 `source->target->type`(多重边按类型区分,同型同端幂等);\r\n * - outAdj / inAdj 都按边类型分桶,路径规则的边谓词 Φ 取对应子数组即 O(1) 裁剪;\r\n * - version 单调递增,供 PathEntry 版本一致性与不变量 I5 使用;\r\n * - changeLog **有界环形**(`MAX_CHANGE_LOG`),供增量引擎消费。\r\n *\r\n * ## 为什么 changeLog 必须有上界(cycle-111 / N-1)\r\n *\r\n * 旧注释写的是「changeLog append-only」,实现也确实只 push 不 pop。在 cycle-107~108 的\r\n * 生命周期下这是**无害**的:`XuanjiAdapter` 每次工具调用都 `new` 一次,日志随对象一起被 GC。\r\n * cycle-109 把 `XuanjiAdapterPool` 接进 `src/tools.ts` 之后,同一个 `GraphSpace` 改为\r\n * **按 repoId 常驻**,而唯一消费入口 `drainChangeLog()` 在生产侧 0 调用点——日志只进不出,\r\n * 随进程运行时长线性增长。同一段代码,换一个生命周期就从无害变成泄漏。\r\n *\r\n * 修法选择「加上界」而不是「让适配层 drain 后丢弃」:\r\n * - drain 并丢弃会把**内存缺陷换成数据丢失缺陷**——将来的真正消费者(增量重放 / 审计)\r\n * 会看到一份「看起来完整、其实开头被吃掉」的日志,而且没有任何信号;\r\n * - 加上界则保留 `drainChangeLog()` 的语义(返回自上次 drain 以来的事件),只是当未消费\r\n * 事件超过上界时丢弃**最旧**的,并用 {@link GraphSpace.droppedChangeEvents} 把丢弃量\r\n * 显式暴露出来——截断可见,与仓库「降级必须可观测」的纪律一致。\r\n *\r\n * 上界的作用域是**单个实例**:库不假设调用方会 drain,也不假设实例是短命的。\r\n *\r\n * 零宿主依赖:库内只用 string 标识,不感知 cytoscape / graphology / branded type。\r\n *\r\n * @module xuanji/core/graph-space\r\n */\r\n\r\n/** 节点标识(库内为 string,不依赖宿主的 branded type)。 */\r\nexport type NodeId = string;\r\n/** 边标识(库内仅用于追踪,不参与路径语义)。 */\r\nexport type EdgeId = string;\r\n/** 边类型(对应 Σ)。 */\r\nexport type EdgeType = string;\r\n\r\n/** 节点属性:库只存不解释。 */\r\nexport interface NodeAttrs {\r\n readonly label: string;\r\n /** 角色(起点谓词 S 常用)。 */\r\n readonly role?: string;\r\n /** 标签集合(节点约束 Ψ 常用)。 */\r\n readonly tags?: readonly string[];\r\n /** 其它宿主属性,库原样保留。 */\r\n readonly [key: string]: unknown;\r\n}\r\n\r\n/** 一条有向边。 */\r\nexport interface XJEdge {\r\n readonly id: EdgeId;\r\n readonly source: NodeId;\r\n readonly target: NodeId;\r\n readonly type: EdgeType;\r\n readonly weight?: number;\r\n}\r\n\r\n/**\r\n * 变更日志的**实例级**上限(未消费事件条数)。\r\n *\r\n * 标定依据:日志只服务增量重放与审计,没有「保留全部历史」的调用方;而池化后实例常驻,\r\n * 不设界就是无界增长。10 000 条足以覆盖「一次批量装载 + 若干次 delta」的重放窗口\r\n * (宿主一次 `graph_data` 装载的边数在千级,见 `PATH_FINDER.MAX_NODES_FOR_BFS` 的量级),\r\n * 又能在病态长跑下把内存钉在常数。\r\n *\r\n * 注意这是**常量而不是可选项**:让调用方自行配置上界等于允许传 `Infinity`,\r\n * 而那正是本项要修掉的缺陷形态。\r\n */\r\nexport const MAX_CHANGE_LOG = 10_000;\r\n\r\n/** 变更事件(append-only,增量引擎的输入)。 */\r\nexport interface ChangeEvent {\r\n readonly kind: 'add-node' | 'remove-node' | 'add-edge' | 'remove-edge';\r\n readonly edge?: XJEdge;\r\n readonly node?: { readonly id: NodeId; readonly attrs: NodeAttrs };\r\n readonly version: number;\r\n}\r\n\r\n/** 边键:多重边按类型区分(spec §4.1 契约)。 */\r\nexport function edgeKey(source: NodeId, target: NodeId, type: EdgeType): string {\r\n return `${source}->${target}->${type}`;\r\n}\r\n\r\n/**\r\n * 图空间。构造后通过 addNode/addEdge 填充,或由宿主适配层一次性装载。\r\n *\r\n * 复杂度:构建 O(V+E),按类型取邻接 O(|Σ_filter| + deg),边查 O(1)。\r\n */\r\nexport class GraphSpace {\r\n private readonly nodes = new Map<NodeId, NodeAttrs>();\r\n /** outAdj[node][edgeType] = 目标节点列表(同型同端去重)。 */\r\n private readonly outAdj = new Map<NodeId, Map<EdgeType, NodeId[]>>();\r\n /** inAdj[node][edgeType] = 来源节点列表(前缀枚举必需,spec §4.1 契约)。 */\r\n private readonly inAdj = new Map<NodeId, Map<EdgeType, NodeId[]>>();\r\n private readonly edgeMap = new Map<string, XJEdge>();\r\n /**\r\n * 未消费的变更事件。物理长度最多 `MAX_CHANGE_LOG`;`logHead` 是逻辑起点的偏移。\r\n *\r\n * 用「覆盖式环形」而不是 `push` + `splice(0, over)`:满之后每次写入都要搬动整个数组,\r\n * 10 000 条时单次开销已可测(本文件的首版实现就是这样,一次批量装载实测 350 ms)。\r\n * 环形下 record 是 O(1),只有 drain 需要 O(n)——而 drain 本来就要复制整份结果。\r\n */\r\n private readonly log: ChangeEvent[] = [];\r\n /** 逻辑起点在 `log` 中的物理偏移;只在写满之后才非零。 */\r\n private logHead = 0;\r\n private _droppedChangeEvents = 0;\r\n private _version = 0;\r\n\r\n get version(): number {\r\n return this._version;\r\n }\r\n\r\n /**\r\n * 因超出 {@link MAX_CHANGE_LOG} 而被丢弃的最旧事件数(自构造/clear 起累计)。\r\n *\r\n * 存在理由:截断本身是正确性上的让步(重放窗口变小),**静默截断**会让将来的消费者\r\n * 把「被吃掉的日志」当成「没发生过的事」。计数必须可读:`XuanjiAdapter.stats()`\r\n * 把它透出到宿主面,测试也直接断言它。\r\n * `drainChangeLog()` 不清零它——清零会掩盖「这条适配层从未被消费过」的事实。\r\n */\r\n get droppedChangeEvents(): number {\r\n return this._droppedChangeEvents;\r\n }\r\n\r\n /** 当前未消费的事件条数(有界,≤ {@link MAX_CHANGE_LOG})。 */\r\n get pendingChangeEvents(): number {\r\n return this.log.length;\r\n }\r\n\r\n /** 追加一条事件并维持上界:写满后覆盖最旧的一条。 */\r\n private record(event: ChangeEvent): void {\r\n if (this.log.length < MAX_CHANGE_LOG) {\r\n this.log.push(event);\r\n return;\r\n }\r\n // 已满:head 指向最旧的一条,就地覆盖并把它挪到下一位。\r\n this.log[this.logHead] = event;\r\n this.logHead = (this.logHead + 1) % MAX_CHANGE_LOG;\r\n this._droppedChangeEvents += 1;\r\n }\r\n\r\n get nodeCount(): number {\r\n return this.nodes.size;\r\n }\r\n\r\n get edgeCount(): number {\r\n return this.edgeMap.size;\r\n }\r\n\r\n addNode(id: NodeId, attrs: NodeAttrs): void {\r\n const isNew = !this.nodes.has(id);\r\n this.nodes.set(id, attrs);\r\n if (isNew) {\r\n // 版本号只为「结构变化」递增:属性覆盖不改变可达性\r\n this._version++;\r\n this.record({ kind: 'add-node', node: { id, attrs }, version: this._version });\r\n }\r\n }\r\n\r\n removeNode(id: NodeId): void {\r\n if (!this.nodes.has(id)) return;\r\n // 先删除相邻边(否则邻接表指向不存在的节点,不变量 I5 会报不一致)\r\n for (const type of this.edgeTypesOf('out', id)) {\r\n // Array.from 取快照:removeEdge 会 splice 同一数组,直接迭代会跳过元素\r\n for (const target of Array.from(this.outAdj.get(id)?.get(type) ?? [])) {\r\n const edge = this.edgeMap.get(edgeKey(id, target, type));\r\n if (edge) this.removeEdge(edge);\r\n }\r\n }\r\n for (const type of this.edgeTypesOf('in', id)) {\r\n for (const source of Array.from(this.inAdj.get(id)?.get(type) ?? [])) {\r\n const edge = this.edgeMap.get(edgeKey(source, id, type));\r\n if (edge) this.removeEdge(edge);\r\n }\r\n }\r\n this.nodes.delete(id);\r\n this._version++;\r\n this.record({ kind: 'remove-node', node: { id, attrs: { label: id } }, version: this._version });\r\n }\r\n\r\n /** 幂等:同 (source,target,type) 只保留第一条。 */\r\n addEdge(edge: XJEdge): boolean {\r\n const key = edgeKey(edge.source, edge.target, edge.type);\r\n if (this.edgeMap.has(key)) return false;\r\n this.edgeMap.set(key, edge);\r\n this.pushAdj(this.outAdj, edge.source, edge.type, edge.target);\r\n this.pushAdj(this.inAdj, edge.target, edge.type, edge.source);\r\n this._version++;\r\n this.record({ kind: 'add-edge', edge, version: this._version });\r\n return true;\r\n }\r\n\r\n removeEdge(edge: XJEdge): boolean {\r\n const key = edgeKey(edge.source, edge.target, edge.type);\r\n if (!this.edgeMap.has(key)) return false;\r\n this.edgeMap.delete(key);\r\n this.popAdj(this.outAdj, edge.source, edge.type, edge.target);\r\n this.popAdj(this.inAdj, edge.target, edge.type, edge.source);\r\n this._version++;\r\n this.record({ kind: 'remove-edge', edge, version: this._version });\r\n return true;\r\n }\r\n\r\n hasNode(id: NodeId): boolean {\r\n return this.nodes.has(id);\r\n }\r\n\r\n hasEdge(source: NodeId, target: NodeId, type: EdgeType): boolean {\r\n return this.edgeMap.has(edgeKey(source, target, type));\r\n }\r\n\r\n getEdge(source: NodeId, target: NodeId, type: EdgeType): XJEdge | undefined {\r\n return this.edgeMap.get(edgeKey(source, target, type));\r\n }\r\n\r\n /** 是否存在 u→v 的任一 type ∈ types 的边(规则判据用,O(|types|))。 */\r\n hasEdgeOfType(source: NodeId, target: NodeId, types: ReadonlySet<EdgeType>): boolean {\r\n for (const type of types) {\r\n if (this.edgeMap.has(edgeKey(source, target, type))) return true;\r\n }\r\n return false;\r\n }\r\n\r\n getNodeAttrs(id: NodeId): NodeAttrs | undefined {\r\n return this.nodes.get(id);\r\n }\r\n\r\n /** 所有节点 id(插入序)。 */\r\n nodeIds(): IterableIterator<NodeId> {\r\n return this.nodes.keys();\r\n }\r\n\r\n /** 所有边(插入序)。 */\r\n edges(): IterableIterator<XJEdge> {\r\n return this.edgeMap.values();\r\n }\r\n\r\n /** 图中出现过的全部边类型 Σ。 */\r\n edgeTypes(): ReadonlySet<EdgeType> {\r\n const types = new Set<EdgeType>();\r\n for (const edge of this.edgeMap.values()) types.add(edge.type);\r\n return types;\r\n }\r\n\r\n /**\r\n * 按类型过滤的出边目标(去重、插入序)。\r\n * types 为空集时返回空数组——空规则不吞噬全图(spec §八)。\r\n */\r\n outNeighbors(node: NodeId, types: ReadonlySet<EdgeType>): NodeId[] {\r\n return this.filtered(this.outAdj, node, types);\r\n }\r\n\r\n /** 按类型过滤的入边来源(去重、插入序)。 */\r\n inNeighbors(node: NodeId, types: ReadonlySet<EdgeType>): NodeId[] {\r\n return this.filtered(this.inAdj, node, types);\r\n }\r\n\r\n /** 该节点某方向上的边类型集合(removeNode 清理用)。 */\r\n private edgeTypesOf(dir: 'in' | 'out', id: NodeId): EdgeType[] {\r\n const adj = dir === 'out' ? this.outAdj : this.inAdj;\r\n const byType = adj.get(id);\r\n return byType ? [...byType.keys()] : [];\r\n }\r\n\r\n /**\r\n * 消费变更日志(增量引擎接管后清空)。\r\n *\r\n * 语义保持不变:返回**自上次 drain 以来仍保留**的事件,顺序即写入序,`version` 严格递增。\r\n * 唯一的差异是日志有上界——若期间发生溢出,返回数组会比「事件真实条数」短,\r\n * 差额由 {@link droppedChangeEvents} 给出(截断可见,不静默)。\r\n * 被丢弃的**只会是最旧的**事件,所以返回序列本身仍是连续且单调的。\r\n */\r\n drainChangeLog(): ChangeEvent[] {\r\n // 环形的逻辑序 = [head..末尾] + [0..head):复制时一次性拉直,调用方拿到的仍是普通数组。\r\n const drained =\r\n this.logHead === 0\r\n ? [...this.log]\r\n : [...this.log.slice(this.logHead), ...this.log.slice(0, this.logHead)];\r\n this.log.length = 0;\r\n this.logHead = 0;\r\n return drained;\r\n }\r\n\r\n clear(): void {\r\n this.nodes.clear();\r\n this.outAdj.clear();\r\n this.inAdj.clear();\r\n this.edgeMap.clear();\r\n this.log.length = 0;\r\n this.logHead = 0;\r\n // 丢弃计数随图本身归零:clear 之后这个实例等于新建,旧实例的截断历史不再属于它。\r\n this._droppedChangeEvents = 0;\r\n this._version = 0;\r\n }\r\n\r\n private filtered(\r\n adj: Map<NodeId, Map<EdgeType, NodeId[]>>,\r\n node: NodeId,\r\n types: ReadonlySet<EdgeType>,\r\n ): NodeId[] {\r\n const byType = adj.get(node);\r\n if (!byType || types.size === 0) return [];\r\n const seen = new Set<NodeId>();\r\n const result: NodeId[] = [];\r\n for (const type of types) {\r\n const list = byType.get(type);\r\n if (!list) continue;\r\n for (const target of list) {\r\n if (seen.has(target)) continue;\r\n seen.add(target);\r\n result.push(target);\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n private pushAdj(\r\n adj: Map<NodeId, Map<EdgeType, NodeId[]>>,\r\n node: NodeId,\r\n type: EdgeType,\r\n other: NodeId,\r\n ): void {\r\n let byType = adj.get(node);\r\n if (!byType) {\r\n byType = new Map();\r\n adj.set(node, byType);\r\n }\r\n let list = byType.get(type);\r\n if (!list) {\r\n list = [];\r\n byType.set(type, list);\r\n }\r\n if (!list.includes(other)) list.push(other);\r\n }\r\n\r\n private popAdj(\r\n adj: Map<NodeId, Map<EdgeType, NodeId[]>>,\r\n node: NodeId,\r\n type: EdgeType,\r\n other: NodeId,\r\n ): void {\r\n const byType = adj.get(node);\r\n if (!byType) return;\r\n const list = byType.get(type);\r\n if (!list) return;\r\n const idx = list.indexOf(other);\r\n if (idx >= 0) list.splice(idx, 1);\r\n if (list.length === 0) byType.delete(type);\r\n if (byType.size === 0) adj.delete(node);\r\n }\r\n}\r\n","/**\n * 路径规则 R = (S, Φ, Λ, Ψ, Ω, Δ) 与规则组合代数。\n *\n * 璇玑图原型:十种读法共享同一方锦缎——「内容与规则分离」。\n * 规则是声明式的:起点谓词 / 边类型 / 长度区间 / 节点约束 / 终点 / 闭合 /\n * 输出映射 / 规范化策略,引擎只按规则枚举,不为每种读法写一遍遍历。\n *\n * 扩展(相对 spec §2.2):\n * - `end` 终点谓词:读法可以在指定的字处收束(如「从 A 读到 B」);\n * - `closed` 闭合标记:路径必须回到起点(环枚举);\n * - `pathConstraint` 整路径谓词:组合代数(并/交/拼接/差)用它精确表达\n * 「合法于 R1 或/且/非 R2」,而逐节点谓词保持为超集,保证枚举完备。\n *\n * 零宿主依赖。\n *\n * @module xuanji/core/path-rule\n */\n\nimport type { EdgeType, GraphSpace, NodeAttrs, NodeId } from './graph-space.ts';\n\n/** 起点谓词 S: V → {0,1}。 */\nexport type StartPredicate = (node: NodeId, attrs: NodeAttrs) => boolean;\n/** 节点约束 Ψ: V → {0,1}(路径上每个节点都必须满足)。 */\nexport type NodePredicate = (node: NodeId, attrs: NodeAttrs) => boolean;\n/** 终点谓词:路径最后一个节点必须满足。 */\nexport type EndPredicate = (node: NodeId, attrs: NodeAttrs, start: NodeId) => boolean;\n/** 整路径谓词(组合代数用;返回 false 的路径不物化)。 */\nexport type PathPredicate = (nodes: readonly NodeId[], graph: GraphSpace) => boolean;\n/** 输出映射 Ω: Path → 任意值。 */\nexport type OutputMapper = (nodes: readonly NodeId[]) => unknown;\n/** 规范化策略 Δ。 */\nexport type NormalizeStrategy = 'none' | 'reverse' | 'canonical';\n\n/** 一条路径规则。 */\nexport interface PathRule {\n readonly name: string;\n /** 单调递增版本号(热插拔/索引回收用)。 */\n readonly version: number;\n readonly start: StartPredicate;\n /** Φ:允许的边类型集合 Σ_R。 */\n readonly edgeTypes: ReadonlySet<EdgeType>;\n /** Λ:长度区间(边数),0 ≤ lMin ≤ lMax。 */\n readonly lMin: number;\n readonly lMax: number;\n /** Ψ:路径上每个节点必须满足。 */\n readonly nodeConstraint?: NodePredicate;\n readonly end?: EndPredicate;\n /** 判定语义:合法路径必须回到起点(环)。 */\n readonly closed?: boolean;\n /**\n * 枚举剪枝提示:允许 DFS 走到「回到起点」的那一步。\n *\n * 与 `closed` 分离的原因:**组合规则的剪枝与判定必须解耦**。\n * 并集 `R1(闭合) ∪ R2(开放)` 的合法集合包含开放路径,判定时不能要求闭合(`closed=false`),\n * 但枚举时必须允许闭合路径收束,否则 R1 的环永远走不到终判(会被 simple 剪枝当成重复节点)。\n * 缺省等于 `closed`。\n */\n readonly pruneClosed?: boolean;\n /** 简单路径(不重复节点;闭合路径允许末节点等于首节点)。 */\n readonly simple?: boolean;\n /** 整路径谓词(组合代数产物,通常不手写)。 */\n readonly pathConstraint?: PathPredicate;\n /** 输出映射;惰性求值并缓存于 PathEntry。 */\n readonly output?: OutputMapper;\n readonly normalize: NormalizeStrategy;\n}\n\n/** 规则静态校验结果(spec §3.4)。 */\nexport interface RuleValidation {\n readonly errors: readonly string[];\n readonly warnings: readonly string[];\n}\n\n/**\n * 注册前静态校验(spec §3.4):\n * 1. Σ_R ⊆ Σ;2. 0 ≤ lMin ≤ lMax;3. simple 且 lMax > |V| 告警;\n * 4. output/normalize 合法;5. 闭合规则要求 lMin ≥ 1;\n * 6. 无界长度必须配 simple(否则路径无限,spec §八)。\n */\nexport function validateRule(\n rule: PathRule,\n knownEdgeTypes: ReadonlySet<EdgeType>,\n nodeCount?: number,\n): RuleValidation {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!rule.name || rule.name.trim().length === 0) errors.push('rule.name must be non-empty');\n if (!Number.isInteger(rule.version) || rule.version < 1) {\n errors.push('rule.version must be a positive integer');\n }\n if (typeof rule.start !== 'function') errors.push('rule.start must be a predicate');\n for (const type of rule.edgeTypes) {\n if (!knownEdgeTypes.has(type)) errors.push(`edge type \"${type}\" is not present in the graph space`);\n }\n if (rule.edgeTypes.size === 0) warnings.push('rule.edgeTypes is empty: the rule can never match an edge');\n if (!Number.isFinite(rule.lMin) || rule.lMin < 0) errors.push('rule.lMin must be a finite number ≥ 0');\n if (!(rule.lMax >= rule.lMin)) errors.push('rule.lMax must be ≥ rule.lMin');\n if (rule.lMax === Number.POSITIVE_INFINITY && rule.simple !== true) {\n errors.push('unbounded lMax requires simple: true (otherwise the path set is infinite)');\n }\n if (rule.closed === true && rule.lMin < 1) {\n errors.push('closed rules require lMin ≥ 1 (a zero-edge closed path is a single node, not a cycle)');\n }\n if (rule.simple === true && Number.isFinite(rule.lMax) && nodeCount !== undefined && rule.lMax > nodeCount) {\n warnings.push(`lMax (${rule.lMax}) exceeds |V| (${nodeCount}): simple paths cannot be that long`);\n }\n if (rule.normalize !== 'none' && rule.normalize !== 'reverse' && rule.normalize !== 'canonical') {\n errors.push(`unknown normalize strategy \"${String(rule.normalize)}\"`);\n }\n return { errors, warnings };\n}\n\n/** 组合代数失败(不可表达的组合)。 */\nexport class RuleCompositionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'RuleCompositionError';\n }\n}\n\n/**\n * 规则的有效长度上界(边数),三处调用点(`enumerate` / `onEdgeAdded` / `rebalance`)\n * 共用这一个实现(XJ-10:此前三处各写一份 `Number.isFinite(lMax) ? lMax : nodeCount`)。\n *\n * 契约:\n * - 有限 `lMax`:直接用它(取整并夹到 ≥ 0);\n * - 无界 `lMax` + `simple: true`:简单路径最长 |V| - 1 条边,用 `nodeCount` 兜底终止;\n * - 无界 `lMax` + 非 simple:**路径集合无限**,这是调用方违约(`validateRule` §6 已报错),\n * 这里抛 {@link RuleCompositionError} 而不是静默用 |V| 近似。\n *\n * 为什么从「静默近似」改为「显式拒绝」:静默兜底会得到「看起来完整、其实缺一半」的结果\n * (枚举在 |V| 处被截断而调用方以为拿到了全部),比一个明确的错误危险得多(XJ-10)。\n *\n * @param nodeCount 图节点数 |V|\n */\nexport function effectiveMaxEdges(\n rule: Pick<PathRule, 'lMax' | 'simple'>,\n nodeCount: number,\n): number {\n if (Number.isFinite(rule.lMax)) return Math.max(0, Math.floor(rule.lMax));\n if (rule.simple !== true) {\n throw new RuleCompositionError(\n 'unbounded lMax requires simple: true (the path set is infinite)',\n );\n }\n return Math.max(0, Math.floor(nodeCount));\n}\n\nfunction combinePredicates(\n a: NodePredicate | undefined,\n b: NodePredicate | undefined,\n op: 'or' | 'and',\n): NodePredicate | undefined {\n if (!a) return op === 'and' ? b : undefined;\n if (!b) return op === 'and' ? a : undefined;\n return op === 'or' ? (n, attrs) => a(n, attrs) || b(n, attrs) : (n, attrs) => a(n, attrs) && b(n, attrs);\n}\n\n/**\n * 并 R1 ∪ R2:谓词析取、长度取并。\n * 逐节点谓词取超集(OR),并用 pathConstraint 精确过滤——\n * 只有「超集剪枝 + 精确终判」才能同时保证完备与正确(spec §2.4 封闭性)。\n */\nexport function unionRules(r1: PathRule, r2: PathRule): PathRule {\n const bothEnd = r1.end && r2.end;\n return {\n name: `${r1.name}∪${r2.name}`,\n version: Math.max(r1.version, r2.version),\n start: (n, a) => r1.start(n, a) || r2.start(n, a),\n edgeTypes: new Set([...r1.edgeTypes, ...r2.edgeTypes]),\n lMin: Math.min(r1.lMin, r2.lMin),\n lMax: Math.max(r1.lMax, r2.lMax),\n nodeConstraint: combinePredicates(r1.nodeConstraint, r2.nodeConstraint, 'or'),\n ...(bothEnd ? { end: (n: NodeId, a: NodeAttrs, s: NodeId) => r1.end!(n, a, s) || r2.end!(n, a, s) } : {}),\n // 判定:并集不要求闭合(R2 的开放路径合法);剪枝:只要有一侧闭合就必须允许「回到起点」\n // 收束,否则 R1 的环永远走不到 isLegalPath。精确成员资格由 pathConstraint 决定。\n closed: false,\n pruneClosed: r1.closed === true || r2.closed === true,\n simple: r1.simple === true && r2.simple === true,\n pathConstraint: (nodes, graph) => isLegalPath(r1, nodes, graph) || isLegalPath(r2, nodes, graph),\n ...(r1.output ? { output: r1.output } : r2.output ? { output: r2.output } : {}),\n normalize: r1.normalize,\n };\n}\n\n/** 交 R1 ∩ R2:谓词合取、长度取交。 */\nexport function intersectRules(r1: PathRule, r2: PathRule): PathRule {\n const lMin = Math.max(r1.lMin, r2.lMin);\n const lMax = Math.min(r1.lMax, r2.lMax);\n if (lMin > lMax) throw new RuleCompositionError('intersectRules: the length intervals are disjoint');\n const end =\n r1.end && r2.end\n ? (n: NodeId, a: NodeAttrs, s: NodeId) => r1.end!(n, a, s) && r2.end!(n, a, s)\n : (r1.end ?? r2.end);\n return {\n name: `${r1.name}∩${r2.name}`,\n version: Math.max(r1.version, r2.version),\n start: (n, a) => r1.start(n, a) && r2.start(n, a),\n edgeTypes: new Set([...r1.edgeTypes].filter((t) => r2.edgeTypes.has(t))),\n lMin,\n lMax,\n nodeConstraint: combinePredicates(r1.nodeConstraint, r2.nodeConstraint, 'and'),\n ...(end ? { end } : {}),\n closed: r1.closed === true || r2.closed === true,\n simple: r1.simple === true || r2.simple === true,\n pathConstraint: (nodes, graph) => isLegalPath(r1, nodes, graph) && isLegalPath(r2, nodes, graph),\n ...(r1.output ? { output: r1.output } : r2.output ? { output: r2.output } : {}),\n normalize: r1.normalize,\n };\n}\n\n/**\n * 拼接 R1 · R2:p1 终点 = p2 起点,长度相加。\n * 整路径合法性 = ∃ 切分点 k,使前段合法于 R1、后段合法于 R2(O(L²),路径短)。\n *\n * 剪枝谓词刻意取**超集**:\n * - nodeConstraint 不取交(也不取并)——两段各自约束不同节点集合,任何单一逐节点\n * 谓词都会误剪「只在前段合法」或「只在后段合法」的节点。置空 + pathConstraint 精确终判;\n * - simple=false——两段各自简单不代表拼起来简单(边界节点可在两段内各出现一次),\n * 取 true 会让 DFS 剪掉这类合法拼接。简单性按段判定,已在 JSDoc 与 spec §2.4 注明。\n */\nexport function concatRules(r1: PathRule, r2: PathRule): PathRule {\n return {\n name: `${r1.name}·${r2.name}`,\n version: Math.max(r1.version, r2.version),\n start: r1.start,\n edgeTypes: new Set([...r1.edgeTypes, ...r2.edgeTypes]),\n lMin: r1.lMin + r2.lMin,\n lMax: r1.lMax + r2.lMax,\n ...(r2.end ? { end: r2.end } : {}),\n closed: false,\n simple: false,\n pathConstraint: (nodes, graph) => {\n // k 取 1..nodes.length:k=1 覆盖「R1 段是单个节点(0 边)」,\n // k=nodes.length 覆盖「R2 段是单个节点(0 边)」——lMin=0 的段必须可达。\n for (let k = 1; k <= nodes.length; k++) {\n const head = nodes.slice(0, k);\n const tail = nodes.slice(k - 1);\n if (isLegalPath(r1, head, graph) && isLegalPath(r2, tail, graph)) return true;\n }\n return false;\n },\n ...(r1.output ? { output: r1.output } : r2.output ? { output: r2.output } : {}),\n normalize: r1.normalize,\n };\n}\n\n/** 迭代 R*(有界重复 n 次):R·R·…·R。 */\nexport function repeatRule(rule: PathRule, times: number): PathRule {\n if (!Number.isInteger(times) || times < 1) {\n throw new RuleCompositionError('repeatRule: times must be a positive integer');\n }\n let acc = rule;\n for (let i = 1; i < times; i++) acc = concatRules(acc, rule);\n return { ...acc, name: `${rule.name}^{*${times}}` };\n}\n\n/** 差 R1 \\ R2:合法于 R1 且不合法于 R2(剪枝沿用 R1 的超集)。 */\nexport function differenceRules(r1: PathRule, r2: PathRule): PathRule {\n return {\n ...r1,\n name: `${r1.name}\\${r2.name}`,\n pathConstraint: (nodes, graph) => isLegalPath(r1, nodes, graph) && !isLegalPath(r2, nodes, graph),\n };\n}\n\n/** 路径是否重复访问节点(闭合路径允许唯一的「末=首」重复)。 */\nexport function hasDuplicateNodes(nodes: readonly NodeId[], allowClosedReturn: boolean): boolean {\n const seen = new Set<NodeId>();\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]!;\n if (i === nodes.length - 1 && allowClosedReturn && i > 0 && node === nodes[0]) continue;\n if (seen.has(node)) return true;\n seen.add(node);\n }\n return false;\n}\n\n/**\n * 整路径合法性判定(C1 可靠性):S、Φ、Λ、Ψ、Δ 之外的可判定约束全在此处。\n * 枚举器只做剪枝,最终是否物化以本函数为准——「剪枝可以是超集,判据必须精确」。\n */\nexport function isLegalPath(\n rule: PathRule,\n nodes: readonly NodeId[],\n graph: GraphSpace,\n): boolean {\n if (nodes.length === 0) return false;\n const edges = nodes.length - 1;\n if (edges < rule.lMin || edges > rule.lMax) return false;\n if (rule.closed === true) {\n if (edges < 1 || nodes[0] !== nodes[nodes.length - 1]) return false;\n }\n // 闭合返回的放行条件与剪枝一致:组合规则可能 closed=false 但 pruneClosed=true\n // (并集含闭合与开放两侧),此时逐节点简单性只能是超集,精确性由 pathConstraint 负责。\n if (rule.simple === true && hasDuplicateNodes(nodes, (rule.pruneClosed ?? rule.closed) === true)) return false;\n\n const startAttrs = graph.getNodeAttrs(nodes[0]!);\n if (!startAttrs || !rule.start(nodes[0]!, startAttrs)) return false;\n\n for (let i = 0; i < nodes.length; i++) {\n const id = nodes[i]!;\n const attrs = graph.getNodeAttrs(id);\n if (!attrs) return false;\n if (rule.nodeConstraint && !rule.nodeConstraint(id, attrs)) return false;\n }\n\n if (rule.end) {\n const last = nodes[nodes.length - 1]!;\n const lastAttrs = graph.getNodeAttrs(last);\n if (!lastAttrs || !rule.end(last, lastAttrs, nodes[0]!)) return false;\n }\n\n for (let i = 0; i + 1 < nodes.length; i++) {\n if (!graph.hasEdgeOfType(nodes[i]!, nodes[i + 1]!, rule.edgeTypes)) return false;\n }\n\n if (rule.pathConstraint && !rule.pathConstraint(nodes, graph)) return false;\n return true;\n}\n","/**\n * 路径索引:起点桶 / 终点桶 / 边对倒排 / hash 去重 / 惰性失效计数。\n *\n * 璇玑图原型:一方锦缎上已经物化的「读法产物」按起点、终点、边数分桶,\n * 查询按边数升序读回,**不再**假设「增量只需触碰与新边相邻的桶」。\n *\n * 关键契约(cycle-107 任务 A + cycle-110 XJ-06 / XJ-09):\n * - 桶键是**边数**(nodes.length - 1)。分桶**只服务查询**(`queryByStart` /\n * `queryByEnd` 的边数升序返回、`getStartBuckets` 供 `query()` 取分母)。\n * **分桶不服务增量**:增量引擎已改为「旧图反向 DFS 前缀 × 新图正向 DFS 后缀」\n * (`incremental.ts` 模块头、设计方案 §12.2),它不读桶、不做桶配对;\n * 规范 §4.2「onEdgeAdded 需要按 len(pre)+1+len(suf) 配对」的表述已作废。\n * Λ 长度剪枝也不落在桶上——它在 `isLegalPath`(`path-rule.ts`)里逐条精确判定,\n * 桶只在查询侧决定**返回顺序**。这条区分是有意为之:桶是查询优化,不是正确性依据。\n * - hash 去重:同 hash 的条目只入桶一次,add 返回 false(不重复入桶、不改计数);\n * - edgeToPaths 键是 `${u}->${v}`(**不含 type**):删除任一类型的一条边都要保守失效;\n * - stale 是惰性失效标记:置位后 queryByStart / queryByEnd / sample 不再返回它,\n * liveCount = total - stale 是 O(1) 的;\n * - 零长度条目(单节点、边数 0,lMin = 0 的规则)同样入桶,但没有边对登记。\n *\n * @module xuanji/index/path-index\n */\n\nimport type { NodeId } from '../core/graph-space.ts';\nimport type { PathEntry } from '../core/path-entry.ts';\n\n/** 索引计数器:total 为入桶条目总数,stale 为其中被标记失效的条数。 */\nexport interface IndexCounters {\n total: number;\n stale: number;\n}\n\n/** 边对键:与 GraphSpace 的 `source->target->type` 不同,这里刻意**不含类型**。 */\nexport function edgePairKey(source: NodeId, target: NodeId): string {\n return `${source}->${target}`;\n}\n\ntype LengthBuckets = Map<number, PathEntry[]>;\n\n/** 取(必要时创建)某节点某长度的桶。 */\nfunction bucketFor(\n index: Map<NodeId, LengthBuckets>,\n node: NodeId,\n edgeCount: number,\n): PathEntry[] {\n let byLength = index.get(node);\n if (!byLength) {\n byLength = new Map();\n index.set(node, byLength);\n }\n let bucket = byLength.get(edgeCount);\n if (!bucket) {\n bucket = [];\n byLength.set(edgeCount, bucket);\n }\n return bucket;\n}\n\n/** 按边数升序收集未失效条目(桶内保持插入序,结果确定)。 */\nfunction collectLive(byLength: LengthBuckets | undefined): PathEntry[] {\n if (!byLength) return [];\n const lengths = [...byLength.keys()].sort((a, b) => a - b);\n const result: PathEntry[] = [];\n for (const length of lengths) {\n const bucket = byLength.get(length);\n if (!bucket) continue;\n for (const entry of bucket) {\n if (!entry.stale) result.push(entry);\n }\n }\n return result;\n}\n\n/**\n * 路径索引。\n *\n * 三条倒排共享同一批 PathEntry 对象(引用相等),所以置 stale 只需改一处。\n */\nexport class PathIndex {\n /** 起点 → 边数 → 条目。 */\n readonly startIndex = new Map<NodeId, Map<number, PathEntry[]>>();\n /** 终点 → 边数 → 条目。 */\n readonly endIndex = new Map<NodeId, Map<number, PathEntry[]>>();\n /** Δ-规范 hash → 条目(去重与 O(1) 存在性判定)。 */\n readonly hashIndex = new Map<string, PathEntry>();\n /** `${u}->${v}` → 经过该边对的条目 hash 集合。 */\n readonly edgeToPaths = new Map<string, Set<string>>();\n readonly counters: IndexCounters = { total: 0, stale: 0 };\n\n /**\n * 入桶。同 hash 已存在时返回 false 且**不重复入桶、不递增计数**。\n *\n * 空节点序列没有可索引的起点/终点,直接拒绝(返回 false)。\n */\n add(entry: PathEntry): boolean {\n if (entry.nodes.length === 0) return false;\n if (this.hashIndex.has(entry.hash)) return false;\n\n this.hashIndex.set(entry.hash, entry);\n const edgeCount = entry.nodes.length - 1;\n bucketFor(this.startIndex, entry.nodes[0]!, edgeCount).push(entry);\n bucketFor(this.endIndex, entry.nodes[entry.nodes.length - 1]!, edgeCount).push(entry);\n\n for (let i = 0; i + 1 < entry.nodes.length; i++) {\n const key = edgePairKey(entry.nodes[i]!, entry.nodes[i + 1]!);\n let hashes = this.edgeToPaths.get(key);\n if (!hashes) {\n hashes = new Set();\n this.edgeToPaths.set(key, hashes);\n }\n hashes.add(entry.hash);\n }\n\n this.counters.total++;\n if (entry.stale) this.counters.stale++;\n return true;\n }\n\n /**\n * 保守失效:把经过边对 `edgePair` 的全部条目置 stale。\n * 返回**本次新置**的条数(幂等:重复调用返回 0)。\n */\n markStale(edgePair: string): number {\n const hashes = this.edgeToPaths.get(edgePair);\n if (!hashes) return 0;\n let count = 0;\n for (const hash of hashes) {\n const entry = this.hashIndex.get(hash);\n if (!entry || entry.stale) continue;\n entry.stale = true;\n this.counters.stale++;\n count++;\n }\n return count;\n }\n\n /**\n * 保守失效:起点**或**终点落在 nodes 里的条目全部置 stale。\n * 返回本次新置的条数(同一条目在起点桶与终点桶各出现一次时只计一次)。\n */\n markStaleForNodes(nodes: Iterable<NodeId>): number {\n let count = 0;\n for (const node of nodes) {\n for (const index of [this.startIndex, this.endIndex]) {\n const byLength = index.get(node);\n if (!byLength) continue;\n for (const bucket of byLength.values()) {\n for (const entry of bucket) {\n if (entry.stale) continue;\n entry.stale = true;\n this.counters.stale++;\n count++;\n }\n }\n }\n }\n return count;\n }\n\n /** 从某节点出发的未失效条目(边数升序)。 */\n queryByStart(start: NodeId): PathEntry[] {\n return collectLive(this.startIndex.get(start));\n }\n\n /** 终止于某节点的未失效条目(边数升序)。 */\n queryByEnd(end: NodeId): PathEntry[] {\n return collectLive(this.endIndex.get(end));\n }\n\n /** 起点桶(可能为空 Map;调用方不要原地修改)。 */\n getStartBuckets(node: NodeId): Map<number, PathEntry[]> {\n return this.startIndex.get(node) ?? new Map();\n }\n\n /** 终点桶(可能为空 Map;调用方不要原地修改)。 */\n getEndBuckets(node: NodeId): Map<number, PathEntry[]> {\n return this.endIndex.get(node) ?? new Map();\n }\n\n /**\n * 抽取至多 n 条未失效条目做抽样体检(I5 用)。\n *\n * 契约(XJ-06,修的是 I5 的抽样偏差):**均匀铺满 + 必含尾部**。\n *\n * 旧实现按 `hashIndex` 插入序取前 n 条,条目数超过 n 之后新条目永不入样——\n * 而 I5 要抓的恰恰是「新写入的坏条目」(version > graph.version、相邻边已删),\n * 抽样偏差方向与风险方向完全相反。现在的取法:\n * - 全部 live 条目按插入序排开(插入序 = 写入序,确定);\n * - 保留末尾 `max(1, floor(n/4))` 条(最新写入的条目必入样);\n * - 剩余额度在前段 `live.length - tailCount` 条里按等步长均匀取,首条必入样;\n * - 前段与尾段下标区间不相交,因此结果无重复且严格递增(顺序确定);\n * - `n <= 0` 返回 `[]`;`live.length <= n` 返回全部 live 的副本。\n *\n * 返回的是新数组:调用方修改它不影响索引(I5 只读)。\n */\n sample(n: number): PathEntry[] {\n const limit = Number.isFinite(n) ? Math.floor(n) : Number.POSITIVE_INFINITY;\n if (limit <= 0) return [];\n const live: PathEntry[] = [];\n for (const entry of this.hashIndex.values()) {\n if (!entry.stale) live.push(entry);\n }\n if (live.length <= limit) return live;\n if (!Number.isFinite(limit)) return live;\n\n // 尾部配额:至少 1 条(「必含尾部」是硬要求,n < 4 时配额就是 1,不是 0)。\n const tailCount = Math.max(1, Math.floor(limit / 4));\n const headQuota = limit - tailCount;\n const headRegion = live.length - tailCount;\n const result: PathEntry[] = [];\n\n if (headQuota > 0 && headRegion > 0) {\n // 步长按「前段长度 / 前段额度」取整,至少 1,保证下标严格递增且不越界。\n const step = Math.max(1, Math.floor(headRegion / headQuota));\n for (let i = 0; i < headQuota; i++) {\n const index = Math.min(i * step, headRegion - 1);\n const entry = live[index];\n if (entry && result[result.length - 1] !== entry) result.push(entry);\n }\n }\n for (let i = live.length - tailCount; i < live.length; i++) {\n const entry = live[i];\n if (entry) result.push(entry);\n }\n return result.slice(0, limit);\n }\n\n /** 未失效条目数(O(1))。 */\n liveCount(): number {\n return Math.max(0, this.counters.total - this.counters.stale);\n }\n\n clear(): void {\n this.startIndex.clear();\n this.endIndex.clear();\n this.hashIndex.clear();\n this.edgeToPaths.clear();\n this.counters.total = 0;\n this.counters.stale = 0;\n }\n}\n\n/**\n * 契约扩展(非 §任务A 必需成员):把一条**仍然合法**的 stale 条目恢复为 live。\n *\n * 存在理由:`markStale` 的边对键不含 type,删除「a->b (call)」会把仍由\n * 「a->b (data)」支撑的条目一并置 stale;增量/重组重新枚举到同一 hash 时,\n * 只要条目本身仍满足 isLegalPath,就应当恢复而不是永久丢失。\n * 合法性由调用方判定(本函数不碰图)。\n *\n * 返回是否发生了恢复。\n */\nexport function reviveEntry(index: PathIndex, entry: PathEntry): boolean {\n if (!entry.stale) return false;\n if (index.hashIndex.get(entry.hash) !== entry) return false;\n entry.stale = false;\n if (index.counters.stale > 0) index.counters.stale--;\n return true;\n}\n","/**\n * 滚动哈希:把「节点序列」映射为可 O(1) 拼接的指纹。\n *\n * 璇玑图原型:增量插入一条边时,前缀哈希 + 新边 + 后缀哈希直接组合,\n * 不需要重新遍历整条路径(spec §5.2)。\n *\n * 实现为多项式滚动哈希的**幺半群**形式:\n * parts([a, b]) = concat(parts([a]), parts([b]))\n * concat(A, B).hash = A.hash * BASE^B.len + B.hash (mod 2^64)\n *\n * 只用于**等价判定/去重**,不用于安全用途。\n *\n * @module xuanji/ops/rolling-hash\n */\n\nconst BASE = 131n;\nconst MASK = (1n << 64n) - 1n;\n/** 节点分隔符,避免 'ab'+'c' 与 'a'+'bc' 撞哈希。 */\nconst SEP = 1n;\n\n/** 一段序列的可拼接哈希片段。 */\nexport interface HashPart {\n /** 多项式哈希值(64-bit 自然溢出)。 */\n readonly hash: bigint;\n /** 参与哈希的字符数(含节点分隔符)。 */\n readonly len: number;\n}\n\n/** B^len 的缓存(惰性扩展)。 */\nconst powCache: bigint[] = [1n];\n\nfunction powBase(exp: number): bigint {\n if (exp < 0) throw new RangeError('rolling-hash: negative exponent');\n while (powCache.length <= exp) {\n powCache.push((powCache[powCache.length - 1]! * BASE) & MASK);\n }\n return powCache[exp]!;\n}\n\n/** 单个节点 id 的哈希片段(字符 + 分隔符)。 */\nexport function hashNode(id: string): HashPart {\n let h = 0n;\n for (let i = 0; i < id.length; i++) {\n h = (h * BASE + BigInt(id.charCodeAt(i))) & MASK;\n }\n h = (h * BASE + SEP) & MASK;\n return { hash: h, len: id.length + 1 };\n}\n\n/** 空序列片段(哈希 0、长度 0),是 concat 的单位元。 */\nexport const EMPTY_HASH: HashPart = Object.freeze({ hash: 0n, len: 0 });\n\n/** O(1) 拼接两个片段。 */\nexport function concatHash(a: HashPart, b: HashPart): HashPart {\n return {\n hash: (a.hash * powBase(b.len) + b.hash) & MASK,\n len: a.len + b.len,\n };\n}\n\n/** 计算整个节点序列的哈希片段,O(L·|id|)。 */\nexport function sequenceHashParts(nodes: readonly string[]): HashPart {\n let acc = EMPTY_HASH;\n for (const id of nodes) acc = concatHash(acc, hashNode(id));\n return acc;\n}\n\n/** 64-bit 哈希的十六进制字符串形式(去重键、PathEntry.hash 用)。 */\nexport function hashToHex(part: HashPart): string {\n return part.hash.toString(16).padStart(16, '0');\n}\n\n/** 面向 PathEntry 的门面。 */\nexport class RollingHash {\n /** 整条序列的哈希指纹(十六进制,16 字符定长)。 */\n static hashSequence(nodes: readonly string[]): string {\n return hashToHex(sequenceHashParts(nodes));\n }\n\n /** 可拼接片段(增量组合用)。 */\n static parts(nodes: readonly string[]): HashPart {\n return sequenceHashParts(nodes);\n }\n\n /** O(1) 组合:prefix + suffix。 */\n static combine(prefix: HashPart, suffix: HashPart): HashPart {\n return concatHash(prefix, suffix);\n }\n\n static empty(): HashPart {\n return EMPTY_HASH;\n }\n}\n","/**\n * 路径条目 PathEntry:物化路径 + Δ-规范哈希 + 版本 + 惰性输出。\n *\n * 契约(spec §4.3):\n * - nodes 存完整节点序列(前缀/后缀拼接需要);\n * - hash 必须基于 Δ-规范形式计算(循环等价才能去重);\n * - labels 惰性构建:长尾路径不付出字符串成本;\n * - version 记录生成时的图版本,供不变量 I5 检查。\n *\n * @module xuanji/core/path-entry\n */\n\nimport type { GraphSpace, NodeId } from './graph-space.ts';\nimport type { PathRule } from './path-rule.ts';\nimport { boothMinimalRotation } from '../ops/booth.ts';\nimport { RollingHash } from '../ops/rolling-hash.ts';\n\n/** 一条被物化的路径。 */\nexport interface PathEntry {\n /** 完整节点序列(原序,不是规范序)。 */\n readonly nodes: NodeId[];\n /** Δ-规范形式的指纹。 */\n readonly hash: string;\n /** 生成时的图版本。 */\n readonly version: number;\n /** 惰性失效标记(删除边后置位,查询永不返回 stale 条目)。 */\n stale: boolean;\n /** 惰性输出(Ω 的结果,首次查询时构建)。 */\n labels?: unknown;\n}\n\n/** 按元素字典序比较两个节点序列(-'<' | 0 | '>')。 */\nexport function compareNodeSequences(a: readonly NodeId[], b: readonly NodeId[]): number {\n const len = Math.min(a.length, b.length);\n for (let i = 0; i < len; i++) {\n const x = a[i]!;\n const y = b[i]!;\n if (x < y) return -1;\n if (x > y) return 1;\n }\n return a.length - b.length;\n}\n\n/**\n * 按规则的 Δ 策略求规范形式。\n * - none:原序;\n * - reverse:正/反序中字典序较小者;\n * - canonical:Booth 最小循环旋转。\n *\n * 闭合路径的表示是 `[v0, v1, …, vk]` 且 `vk === v0`;直接对含重复首节点的序列做\n * 旋转会得到 `[v1, …, vk, v0]` 这类**非法路径表示**(末节点不再等于首节点)。\n * 因此闭合路径只对「开核」`nodes.slice(0, -1)` 做旋转——哈希取的是环的等价类,\n * 而 `PathEntry.nodes` 仍保存调用方传入的完整路径。\n */\nexport function canonicalNodes(\n rule: Pick<PathRule, 'normalize' | 'closed'>,\n nodes: readonly NodeId[],\n): NodeId[] {\n switch (rule.normalize) {\n case 'reverse': {\n const reversed = [...nodes].reverse();\n return compareNodeSequences(reversed, nodes) < 0 ? reversed : [...nodes];\n }\n case 'canonical': {\n if (rule.closed === true && nodes.length > 1 && nodes[0] === nodes[nodes.length - 1]) {\n return boothMinimalRotation(nodes.slice(0, -1));\n }\n return boothMinimalRotation(nodes);\n }\n case 'none':\n default:\n return [...nodes];\n }\n}\n\n/** 创建路径条目:规范哈希在库内一次算好,调用方不重复计算。 */\nexport function makeEntry(\n rule: Pick<PathRule, 'normalize' | 'closed'>,\n nodes: readonly NodeId[],\n graphVersion: number,\n): PathEntry {\n const canonical = canonicalNodes(rule, nodes);\n return {\n nodes: [...nodes],\n hash: RollingHash.hashSequence(canonical),\n version: graphVersion,\n stale: false,\n };\n}\n\n/** 惰性输出映射 Ω:首次调用才求值并缓存到条目上。 */\nexport function entryLabels(entry: PathEntry, rule: PathRule): unknown {\n if (entry.labels !== undefined) return entry.labels;\n const produced = rule.output ? rule.output(entry.nodes) : entry.nodes;\n entry.labels = produced;\n return produced;\n}\n\n/** 条目是否可作为查询结果(未失效,且边仍全部存在——I5 的查询侧防线)。 */\nexport function isLiveEntry(entry: PathEntry, graph: GraphSpace, rule: PathRule): boolean {\n if (entry.stale) return false;\n if (entry.version > graph.version) return false;\n for (let i = 0; i + 1 < entry.nodes.length; i++) {\n if (!graph.hasEdgeOfType(entry.nodes[i]!, entry.nodes[i + 1]!, rule.edgeTypes)) return false;\n }\n return true;\n}\n","/**\n * 增量引擎:新增一条边后,只物化「包含这条新边」的新合法路径(C3 完备性)。\n *\n * 设计说明(与契约文档的差异,已在 cycle-107 报告中说明):\n * 文档原稿的「从 endIndex[u] / startIndex[v] 取前缀/后缀再拼接」只在\n * 前缀/后缀**已被物化**时成立;而索引只存 len ≥ lMin 且整条合法的条目,\n * 于是两类合法新路径永远找不到:\n * 1) lMin ≥ 2:前缀/后缀各自短于 lMin 时不入桶(例:lMin=2,图 a→b、c→d,\n * 新增 b→c,新路径 a→b→c 的前缀 [a,b] 与后缀 [c,d] 都只有 1 条边);\n * 2) closed=true:环的前缀从 s 到 u 不闭合,因此不是规则的合法路径,不在桶里\n * (例:闭合规则,图 a→b→c,新增 c→a,三角形 a→b→c→a 找不到)。\n *\n * 因此这里把「前缀/后缀」取成**可作前缀/后缀的合法走法**(索引条目的超集):\n * - 前缀:在旧图 G \\ {e} 上从 u 反向 DFS(含零长度虚拟前缀 [u],必须);\n * - 后缀:在新图 G' 上从 v 正向 DFS(因此也覆盖「新边被使用多次」的走法);\n * - 拼接公式仍是契约里的 `pre.nodes.concat(suf.nodes)`:pre 已以 u 结尾、\n * suf 已以 v 开头,接起来正好跨过 u→v,不额外插入 [u,v]。\n *\n * 终判一律 `isLegalPath`(精确),DFS 只做超集剪枝(长度 / 节点约束 / simple 去重)。\n *\n * 复杂度与降级(spec §5.4 / §7.3「路径数爆炸 → 回退」):\n * 每条前缀 × 每条后缀都会被尝试展开,lMax 大时可能指数爆炸,因此设有\n * `INCREMENTAL_WORK_BUDGET` 展开预算(前缀展开 + 后缀展开都计数)。一旦超预算,\n * 说明本次增量已经比全量枚举更贵:`index.clear()` 把整索引降级为空并返回 [],\n * 查询层自然回退到全量枚举(fallback 契约),不抛异常、不留半成品。\n * 降级可通过 `wasLastIncrementalDegraded()` 观测(每次 onEdgeAdded 开头重置)。\n *\n * @module xuanji/index/incremental\n */\n\nimport type { EdgeType, GraphSpace, NodeId, XJEdge } from '../core/graph-space.ts';\nimport type { PathRule } from '../core/path-rule.ts';\nimport { effectiveMaxEdges, isLegalPath } from '../core/path-rule.ts';\nimport type { PathEntry } from '../core/path-entry.ts';\nimport { makeEntry } from '../core/path-entry.ts';\nimport { edgePairKey, reviveEntry } from './path-index.ts';\nimport type { PathIndex } from './path-index.ts';\n\n/**\n * 增量展开预算(前缀展开 + 后缀展开次数)。超出即触发整索引降级。\n *\n * 标定(XJ-11:阈值必须与「图上界」成对校验,不能是一个孤立魔数):\n * - 计数单位是**展开次数**,不是节点数。线性链上每个前缀/后缀各展开一次,因此\n * 「一次无分支的增量」至少需要 `|V|` 次展开 ⇒ 预算必须 ≥ 宿主允许送入的最大的图,\n * 否则合法的大图一做增量就必然降级(降级后每次查询全量重枚举,增量引擎等于关闭)。\n * 宿主侧的上界是 `PATH_FINDER.MAX_NODES_FOR_BFS`(50 000,见 `src/shared/constants.ts`),\n * 本常量取同一个 50 000 是**契约**而不是巧合:`tests/unit/xuanjiBudgetContract.test.ts`\n * 用成对不等式把它钉住(旧注释只写着「两个 50000 是巧合」)。\n * - 显示层上界 `CLUSTER.MAX_VISIBLE_NODES`(1 500)远小于它,因此「用户真正看得见的图」\n * 即使每节点平均 33 次分支展开也不会轻易触发降级。\n * - 预算不可小于 1:`resolveBudget` 把非法值夹到 ≥ 0,`workBudget: 0` 是显式的「必然降级」\n * 测试入口(首个展开即超预算)。\n */\nexport const INCREMENTAL_WORK_BUDGET = 50_000;\n\n/** onEdgeAdded 的可选参数(适配层不传,默认 INCREMENTAL_WORK_BUDGET)。 */\nexport interface IncrementalOptions {\n /** 覆盖展开预算;测试用它把小预算注入到正常路径上。 */\n readonly workBudget?: number;\n}\n\n/** 降级哨兵:只在模块内部抛出/捕获,不泄漏给调用方。 */\nconst WORK_BUDGET_EXCEEDED: unique symbol = Symbol('xuanji.incremental.work-budget-exceeded');\n\ninterface WorkMeter {\n used: number;\n budget: number;\n}\n\n/** 记一次展开;超预算立即抛哨兵(由 onEdgeAdded 统一降级)。 */\nfunction spend(meter: WorkMeter): void {\n meter.used += 1;\n if (meter.used > meter.budget) throw WORK_BUDGET_EXCEEDED;\n}\n\nfunction resolveBudget(options?: IncrementalOptions): number {\n const requested = options?.workBudget;\n if (requested === undefined || !Number.isFinite(requested)) return INCREMENTAL_WORK_BUDGET;\n return Math.max(0, Math.floor(requested));\n}\n\n/**\n * 降级状态(XJ-11):**以索引实例为键**,不是模块级单例。\n *\n * 旧实现是 `let lastDegraded`:两个 adapter(两个仓库)或两条规则并发增量时,\n * A 的降级会被 B 的 `lastDegraded = false` 清掉——降级信号不可观测,\n * 而「不可观测的降级」等于没有降级(查询层不会回退,用户拿到半成品)。\n *\n * 现在状态挂在**被传入的 PathIndex** 上:\n * - 调 `wasLastIncrementalDegraded(index)` 得到该索引最近一次的判定(生产用法);\n * - 不传参时仍返回「最近一次 onEdgeAdded 是否降级」的全局视图,仅作旧调用方兼容;\n * 新代码不应依赖它(跨实例会被互相覆盖)。\n */\nconst degradedIndexes = new WeakSet<PathIndex>();\nlet lastDegradedGlobal = false;\n\n/**\n * 最近一次 `onEdgeAdded` 是否因超出展开预算而降级。\n *\n * @param index 给出时返回**该索引**最近一次调用的判定(XJ-11 实例态,推荐);\n * 省略时退化为全局视图(旧行为,跨实例不可靠,仅为兼容保留)\n */\nexport function wasLastIncrementalDegraded(index?: PathIndex): boolean {\n if (index !== undefined) return degradedIndexes.has(index);\n return lastDegradedGlobal;\n}\n\n/**\n * 有效长度上界(边数):统一由 {@link effectiveMaxEdges} 给出(XJ-10)。\n *\n * 无界 `lMax` 只在 simple 规则下合法,此时用 |V| 兜底;无界 + 非 simple 是非法规则,\n * `effectiveMaxEdges` 会抛 `RuleCompositionError`,不再静默近似。\n * (此前本文件自带一份相同实现,`rebalance.ts` 与 `query-engine.ts` 再各带一份。)\n */\n\n/**\n * u→v 是否还有**别的类型**的边。\n * 反向遍历时新边(u→v, newEdge.type)必须被排除,否则前缀会包含新边本身;\n * 但同端不同型的既有边是合法的旧边,不能误排除(多重边场景)。\n */\nfunction hasOtherTypeEdge(\n graph: GraphSpace,\n types: ReadonlySet<EdgeType>,\n excluded: EdgeType,\n source: NodeId,\n target: NodeId,\n): boolean {\n for (const type of types) {\n if (type === excluded) continue;\n if (graph.hasEdge(source, target, type)) return true;\n }\n return false;\n}\n\n/**\n * 在旧图 G \\ {e} 上反向收集所有可作前缀的走法(以 head = u 结尾)。\n *\n * 总是包含零长度虚拟前缀 [head]——索引只存 len ≥ lMin 的条目,缺了它就永远发现不了\n * 「新边直接闭合一个以 u 为起点的路径」的新环。\n */\nfunction collectBackwardPrefixes(\n graph: GraphSpace,\n rule: PathRule,\n newEdge: XJEdge,\n head: NodeId,\n maxPrefixEdges: number,\n meter: WorkMeter,\n): NodeId[][] {\n const prefixes: NodeId[][] = [[head]];\n const path: NodeId[] = [head];\n const onPath = new Set<NodeId>([head]);\n\n const walkBack = (current: NodeId, edges: number): void => {\n if (edges >= maxPrefixEdges) return;\n for (const source of graph.inNeighbors(current, rule.edgeTypes)) {\n spend(meter);\n // 新边不算旧图的一部分(同端不同型的旧边则允许)\n if (\n source === newEdge.source &&\n current === newEdge.target &&\n !hasOtherTypeEdge(graph, rule.edgeTypes, newEdge.type, source, current)\n ) {\n continue;\n }\n // simple 规则下前缀必须无重复节点(安全剪枝:重复前缀不可能是简单路径的前缀)\n if (rule.simple === true && onPath.has(source)) continue;\n const attrs = graph.getNodeAttrs(source);\n if (!attrs) continue;\n if (rule.nodeConstraint && !rule.nodeConstraint(source, attrs)) continue;\n\n path.unshift(source);\n onPath.add(source);\n prefixes.push([...path]);\n walkBack(source, edges + 1);\n onPath.delete(source);\n path.shift();\n }\n };\n\n walkBack(head, 0);\n return prefixes;\n}\n\n/**\n * 新增边 e = (u,v) 之后,物化所有「包含 e」的新合法路径。\n *\n * @param options.workBudget DFS 展开预算(默认 INCREMENTAL_WORK_BUDGET);超预算时整索引降级为空并返回 []\n * @returns 本次新入桶的条目(不含同 hash 已存在者;降级时为空数组)\n */\nexport function onEdgeAdded(\n edge: XJEdge,\n rule: PathRule,\n graph: GraphSpace,\n index: PathIndex,\n options?: IncrementalOptions,\n): PathEntry[] {\n // 每次调用开头重置**两个维度**:本索引的标记(实例态)与全局视图(兼容)。\n degradedIndexes.delete(index);\n lastDegradedGlobal = false;\n\n // Φ:类型不在规则里 → 与新边无关\n if (!rule.edgeTypes.has(edge.type)) return [];\n const u = edge.source;\n const v = edge.target;\n // 端点必须已在图空间中(否则路径无法终判)\n if (!graph.getNodeAttrs(u) || !graph.getNodeAttrs(v)) return [];\n\n // XJ-10:唯一实现,非法规则(无界 + 非 simple)在此抛错而非静默近似\n const maxEdges = effectiveMaxEdges(rule, graph.nodeCount);\n if (maxEdges < 1) return [];\n if (maxEdges < rule.lMin) return [];\n\n const added: PathEntry[] = [];\n const meter: WorkMeter = { used: 0, budget: resolveBudget(options) };\n\n const materialize = (nodes: readonly NodeId[]): void => {\n const entry = makeEntry(rule, nodes, graph.version);\n const existing = index.hashIndex.get(entry.hash);\n if (existing) {\n // 同 hash 已存在:不重复入桶、不返回;但若它只是被保守地误标 stale,且节点序列仍合法,则恢复\n if (existing.stale && isLegalPath(rule, existing.nodes, graph)) reviveEntry(index, existing);\n return;\n }\n if (index.add(entry)) added.push(entry);\n };\n\n /** 从 pre + [v] 出发,在 G' 上向后续走(新边可以再次被使用,覆盖多次穿越的走法)。 */\n const walkForward = (nodes: NodeId[], edges: number): void => {\n if (edges > maxEdges) return;\n if (edges >= rule.lMin && isLegalPath(rule, nodes, graph)) materialize(nodes);\n if (edges >= maxEdges) return;\n\n const last = nodes[nodes.length - 1]!;\n for (const next of graph.outNeighbors(last, rule.edgeTypes)) {\n spend(meter);\n const attrs = graph.getNodeAttrs(next);\n if (!attrs) continue;\n if (rule.nodeConstraint && !rule.nodeConstraint(next, attrs)) continue;\n if (rule.simple === true && nodes.includes(next)) {\n // 简单路径里唯一合法的重复是「闭合回到起点」且必须是末节点\n if (rule.closed === true && next === nodes[0] && edges + 1 >= rule.lMin) {\n const closedNodes = [...nodes, next];\n if (isLegalPath(rule, closedNodes, graph)) materialize(closedNodes);\n }\n continue;\n }\n walkForward([...nodes, next], edges + 1);\n }\n };\n\n try {\n const maxPrefixEdges = Math.max(0, maxEdges - 1);\n for (const prefix of collectBackwardPrefixes(graph, rule, edge, u, maxPrefixEdges, meter)) {\n const seed = [...prefix, v];\n walkForward(seed, seed.length - 1);\n }\n return added;\n } catch (error) {\n if (error !== WORK_BUDGET_EXCEEDED) throw error;\n // spec §5.4 / §7.3:本次增量已比全量还贵 → 整索引降级为空,查询层回退全量枚举。\n // 不抛异常、不留半成品(cleared 后 counters 归零,hashIndex 为空)。\n index.clear();\n degradedIndexes.add(index);\n lastDegradedGlobal = true;\n return [];\n }\n}\n\n/**\n * 删除边之后的增量处理:保守失效经过 (u,v) 的全部条目。\n *\n * 只置 stale(不重新枚举),返回**本次新置**的条数;类型不在规则里则不触碰索引。\n * 幂等:重复调用返回 0。\n *\n * 这是 XJ-04 之前的**旧入口**,返回 number 的语义被既有调用方(含 `XuanjiAdapter`)\n * 依赖,因此原样保留;需要「是否该重建」的判定请用 {@link onEdgeRemovedDetailed}。\n */\nexport function onEdgeRemoved(edge: XJEdge, rule: PathRule, index: PathIndex): number {\n return onEdgeRemovedDetailed(edge, rule, index).newlyStale;\n}\n\n/** 删除单条边之后的判定结果(XJ-04)。 */\nexport interface EdgeRemovalResult {\n /** 本次新置 stale 的条目数(幂等:重复调用为 0)。 */\n readonly newlyStale: number;\n /** 索引的**全量** stale 比例:counters.stale / counters.total。 */\n readonly staleRatio: number;\n /** stale 比例是否已超过阈值,即索引需要全量重建。 */\n readonly needsRebuild: boolean;\n}\n\n/** stale 比例超过该值即视为索引需要全量重建(与 query-engine 的回退阈值 0.3 一致)。 */\nexport const DEFAULT_THETA_STALE = 0.3;\n\n/** {@link onEdgeRemovedDetailed} 的可选参数。 */\nexport interface EdgeRemovalOptions {\n /** 覆盖 stale 阈值(缺省 {@link DEFAULT_THETA_STALE});用于测试与按读法调参。 */\n readonly thetaStale?: number;\n}\n\nfunction resolveTheta(options?: EdgeRemovalOptions): number {\n const requested = options?.thetaStale;\n if (requested === undefined || !Number.isFinite(requested)) return DEFAULT_THETA_STALE;\n return Math.max(0, requested);\n}\n\n/**\n * 删除边并**如实报告**索引的失效程度(XJ-04:阈值必须到达动作)。\n *\n * 关键分母:`staleRatio` 的分子分母都取自 `index.counters`(**全量**计数,含已 stale 的条目),\n * 不能用 `queryByStart`/`sample` 过滤后的 live 数当分母——那样一个「全部条目都已失效」的索引\n * 会被算成 0% 失效,阈值永不触发(同样的坑见 `query-engine.ts` 的 `query()`)。\n *\n * 阈值语义是**严格大于**:比例恰等于 theta 时不重建(与 `query-engine.ts` 的 `> 0.3` 一致)。\n * `total === 0`(空索引)时比例定义为 0、`needsRebuild` 为 false,不谎报。\n *\n * 边类型不在规则里时 `newlyStale` 必为 0(这条边与该读法无关),但比例与判定仍如实返回——\n * 调用方问的是「这个索引还能不能用」,答案不取决于刚删的那条边属不属于这条规则。\n */\nexport function onEdgeRemovedDetailed(\n edge: XJEdge,\n rule: PathRule,\n index: PathIndex,\n options?: EdgeRemovalOptions,\n): EdgeRemovalResult {\n const newlyStale = rule.edgeTypes.has(edge.type)\n ? index.markStale(edgePairKey(edge.source, edge.target))\n : 0;\n const total = index.counters.total;\n const staleRatio = total === 0 ? 0 : index.counters.stale / total;\n return { newlyStale, staleRatio, needsRebuild: staleRatio > resolveTheta(options) };\n}\n","/**\n * 不变量检查器 I1–I5:索引与图空间的一致性体检。\n *\n * 璇玑图原型:读法可以很多,但「锦缎」与「读法索引」之间的对应关系必须始终成立。\n * 检查器不修复任何东西,只如实报告——修复策略属于增量引擎/invariants 的调用方。\n *\n * 五项不变量(cycle-107 契约):\n * - I1:rule.edgeTypes 中每类边至少出现在一条 live 物化路径的相邻边上;\n * - I2:图中每种角色(attrs.role,缺省 tags[0])至少出现在一条 live 路径中;\n * - I3:按边类型聚合的 live 路径平均长度,max-min ≤ thetaDepth;\n * - I4:counters.stale / counters.total ≤ thetaStale(O(1),total=0 视为比率 0);\n * - I5:抽样条目的 version ≤ graph.version,且非 stale 条目的每条相邻边仍存在。\n *\n * 零宿主依赖。\n *\n * @module xuanji/ops/invariants\n */\n\nimport type { EdgeType, GraphSpace, NodeAttrs } from '../core/graph-space.ts';\nimport type { PathRule } from '../core/path-rule.ts';\nimport type { PathEntry } from '../core/path-entry.ts';\nimport { isLiveEntry } from '../core/path-entry.ts';\nimport type { PathIndex } from '../index/path-index.ts';\n\n/** 阈值:stale 比例上限与各边类型平均路径长度极差上限。 */\nexport interface InvariantThresholds {\n /** I4:stale/total 的允许上限(缺省 0.3,与查询回退阈值一致)。 */\n readonly thetaStale?: number;\n /** I3:不同边类型平均路径长度的允许极差(缺省 2)。 */\n readonly thetaDepth?: number;\n}\n\n/** 单项不变量结果。 */\nexport interface InvariantResult {\n readonly id: 'I1' | 'I2' | 'I3' | 'I4' | 'I5';\n readonly ok: boolean;\n readonly detail: string;\n /** 违规条目数(ok 恒为 0;未达标时为具体违规计数)。 */\n readonly violations: number;\n}\n\n/** I2 的角色提取:attrs.role 优先,缺省取第一个 tag。 */\nfunction roleOf(attrs: NodeAttrs | undefined): string | undefined {\n if (!attrs) return undefined;\n if (typeof attrs.role === 'string' && attrs.role.length > 0) return attrs.role;\n const first = attrs.tags?.[0];\n if (typeof first === 'string' && first.length > 0) return first;\n return undefined;\n}\n\n/** 收集全部 live 物化路径(按规范 hash 去重,起点/终点两路桶都看,避免漏索引)。 */\nfunction collectLiveEntries(index: PathIndex, graph: GraphSpace, rule: PathRule): PathEntry[] {\n const seen = new Set<string>();\n const result: PathEntry[] = [];\n const visit = (entry: PathEntry): void => {\n if (seen.has(entry.hash)) return;\n if (!isLiveEntry(entry, graph, rule)) return;\n seen.add(entry.hash);\n result.push(entry);\n };\n for (const buckets of index.startIndex.values()) {\n for (const entries of buckets.values()) for (const entry of entries) visit(entry);\n }\n for (const buckets of index.endIndex.values()) {\n for (const entries of buckets.values()) for (const entry of entries) visit(entry);\n }\n return result;\n}\n\n/** I1–I5 检查器。阈值在构造时固定,检查过程无副作用(只读索引)。 */\nexport class InvariantChecker {\n private readonly thetaStale: number;\n private readonly thetaDepth: number;\n\n constructor(thresholds: InvariantThresholds = {}) {\n this.thetaStale = thresholds.thetaStale ?? 0.3;\n this.thetaDepth = thresholds.thetaDepth ?? 2;\n }\n\n /**\n * I1:每类被规则允许的边,至少出现在一条 live 物化路径的相邻边上。\n * 空 edgeTypes 视为「无可覆盖项」(validateRule 会对此告警);不谎报失败。\n */\n checkI1(index: PathIndex, rule: PathRule, graph: GraphSpace): InvariantResult {\n const covered = new Set<EdgeType>();\n for (const entry of collectLiveEntries(index, graph, rule)) {\n for (let i = 0; i + 1 < entry.nodes.length; i++) {\n const u = entry.nodes[i]!;\n const v = entry.nodes[i + 1]!;\n for (const type of rule.edgeTypes) {\n if (graph.hasEdge(u, v, type)) covered.add(type);\n }\n }\n }\n const missing: EdgeType[] = [];\n for (const type of rule.edgeTypes) if (!covered.has(type)) missing.push(type);\n return {\n id: 'I1',\n ok: missing.length === 0,\n detail:\n missing.length === 0\n ? 'every rule edge type appears on at least one live materialized path'\n : 'edge types without any live materialized path: ' + missing.join(', '),\n violations: missing.length,\n };\n }\n\n /**\n * I2:图中每种角色至少出现在一条 live 路径中。\n *\n * 诚实说明:nodeConstraint 是**不可反推的任意谓词**——无法从它推导出「哪些角色必须覆盖」。\n * 因此 I2 只统计「图中实际存在的角色」(图像素)在 live 路径里的覆盖率,\n * 缺失角色数即 violations;它不声称 nodeConstraint 满足。\n */\n checkI2(index: PathIndex, rule: PathRule, graph: GraphSpace): InvariantResult {\n const graphRoles = new Set<string>();\n for (const id of graph.nodeIds()) {\n const role = roleOf(graph.getNodeAttrs(id));\n if (role !== undefined) graphRoles.add(role);\n }\n const covered = new Set<string>();\n for (const entry of collectLiveEntries(index, graph, rule)) {\n for (const id of entry.nodes) {\n const role = roleOf(graph.getNodeAttrs(id));\n if (role !== undefined && graphRoles.has(role)) covered.add(role);\n }\n }\n const missing: string[] = [];\n for (const role of graphRoles) if (!covered.has(role)) missing.push(role);\n return {\n id: 'I2',\n ok: missing.length === 0,\n detail:\n graphRoles.size === 0\n ? 'no node in the graph declares a role; I2 is vacuously satisfied'\n : 'role coverage ' +\n (graphRoles.size - missing.length) +\n '/' +\n graphRoles.size +\n (missing.length > 0 ? '; uncovered: ' + missing.join(', ') : ''),\n violations: missing.length,\n };\n }\n\n /**\n * I3:按边类型聚合 live 路径的平均长度,极差不得超过 thetaDepth。\n * 只有出现在至少一条 live 路径上的边类型参与聚合;少于两类时显然达标。\n */\n checkI3(index: PathIndex, rule: PathRule, graph: GraphSpace): InvariantResult {\n const sums = new Map<EdgeType, { sum: number; count: number }>();\n for (const entry of collectLiveEntries(index, graph, rule)) {\n const length = entry.nodes.length - 1;\n const typesHere = new Set<EdgeType>();\n for (let i = 0; i + 1 < entry.nodes.length; i++) {\n for (const type of rule.edgeTypes) {\n if (graph.hasEdge(entry.nodes[i]!, entry.nodes[i + 1]!, type)) typesHere.add(type);\n }\n }\n for (const type of typesHere) {\n const aggregate = sums.get(type) ?? { sum: 0, count: 0 };\n aggregate.sum += length;\n aggregate.count += 1;\n sums.set(type, aggregate);\n }\n }\n\n if (sums.size < 2) {\n return {\n id: 'I3',\n ok: true,\n detail: 'fewer than two edge types have live paths; depth spread is 0',\n violations: 0,\n };\n }\n\n const averages: Array<{ type: EdgeType; avg: number }> = [];\n for (const [type, aggregate] of sums) {\n averages.push({ type, avg: aggregate.sum / aggregate.count });\n }\n let min = averages[0]!.avg;\n let max = averages[0]!.avg;\n for (const item of averages) {\n if (item.avg < min) min = item.avg;\n if (item.avg > max) max = item.avg;\n }\n const spread = max - min;\n const exceeding = averages.filter((item) => item.avg > min + this.thetaDepth);\n const ok = spread <= this.thetaDepth;\n return {\n id: 'I3',\n ok,\n detail:\n 'average path length per edge type over [' +\n averages.map((item) => item.type + '=' + item.avg.toFixed(2)).join(', ') +\n '] spread=' +\n spread.toFixed(2) +\n ' thetaDepth=' +\n this.thetaDepth,\n violations: ok ? 0 : exceeding.length,\n };\n }\n\n /** I4:stale 比例上限。O(1),不遍历任何条目。 */\n checkI4(index: PathIndex): InvariantResult {\n const total = index.counters.total;\n const stale = index.counters.stale;\n const ratio = total === 0 ? 0 : stale / total;\n const ok = ratio <= this.thetaStale;\n return {\n id: 'I4',\n ok,\n detail:\n 'stale/total = ' +\n stale +\n '/' +\n total +\n ' = ' +\n ratio.toFixed(3) +\n ' (thetaStale=' +\n this.thetaStale +\n ')',\n violations: ok ? 0 : stale,\n };\n }\n\n /**\n * I5:抽样一致性。version ≤ graph.version;非 stale 条目的每条相邻边必须仍存在。\n * 边检查使用 rule.edgeTypes(空集合会让 hasEdgeOfType 永远为 false——那是检查器的错,不是图的错)。\n */\n checkI5(index: PathIndex, rule: PathRule, graph: GraphSpace): InvariantResult {\n const sample = index.sample(100);\n let violations = 0;\n const offenders: string[] = [];\n for (const entry of sample) {\n if (entry.version > graph.version) {\n violations++;\n offenders.push('version ' + entry.version + ' > graph ' + graph.version);\n continue;\n }\n if (entry.stale) continue;\n for (let i = 0; i + 1 < entry.nodes.length; i++) {\n if (!graph.hasEdgeOfType(entry.nodes[i]!, entry.nodes[i + 1]!, rule.edgeTypes)) {\n violations++;\n offenders.push('missing edge ' + entry.nodes[i]! + '->' + entry.nodes[i + 1]!);\n break;\n }\n }\n }\n return {\n id: 'I5',\n ok: violations === 0,\n detail:\n 'sampled ' +\n sample.length +\n ' entries; ' +\n (violations === 0 ? 'all consistent' : 'violations: ' + offenders.slice(0, 5).join('; ')),\n violations,\n };\n }\n\n /** 按 I1→I5 顺序全部检查。 */\n checkAll(index: PathIndex, rule: PathRule, graph: GraphSpace): InvariantResult[] {\n return [\n this.checkI1(index, rule, graph),\n this.checkI2(index, rule, graph),\n this.checkI3(index, rule, graph),\n this.checkI4(index),\n this.checkI5(index, rule, graph),\n ];\n }\n}","/**\n * Top-K 选择(稳定、非变异)。\n *\n * 璇玑图原型:从成千上万条读法里挑出「最险」或「最工」的十句,\n * 排序必须稳定——同分时保持枚举顺序,结果才可复现(确定性优先于微优化)。\n *\n * 纯函数、零依赖。\n *\n * @module xuanji/query/topk\n */\n\n/**\n * 取分数最高的 k 项。\n *\n * 契约:\n * - `k <= 0` 返回 `[]`(不抛错,退化输入不制造异常);\n * - `k >= items.length` 返回**副本**(调用方可以安全修改,不污染输入);\n * - 稳定排序:分数相同时保持原数组顺序(用原下标做次级键,不依赖引擎的 sort 稳定性);\n * - 不修改输入数组;\n * - 缺省 score 把元素本身当作数值(元素不是 number 时视为 0,NON_NUMERIC 语义见下)。\n *\n * @param items 候选集合(只读,不会被排序或修改)\n * @param k 取前 k 项\n * @param score 评分函数,**分数越大越靠前**;缺省用元素自身的数值\n */\nexport function topK<T>(items: readonly T[], k: number, score?: (item: T) => number): T[] {\n if (!(k > 0) || items.length === 0) return [];\n\n const scoreOf = score ?? ((item: T) => (typeof item === 'number' && Number.isFinite(item) ? item : 0));\n\n // cycle-119 修复:`k >= items.length` 曾在此**提前返回插入序**——但「取前 k 项」的语义\n // 包含顺序(分数越大越靠前),k 覆盖全量时顺序照样影响消费方:top-k-weight 把\n // `maxPaths = cursor + pageSize + 1` 传进来,k==候选数 时 ranked[0] 会是 DFS 首条而不是\n // 最高权重条(实测:菱形图 1+1 vs 5+5,topk 结果 = dfs 结果,透传参数完全失效)。\n // 修复 = 删短路,让 k==len 也走统一排序路径(仍返回新数组,不改输入;\n // 「副本不是同一引用」的既有契约不变,只是副本的**顺序**现在由 score 决定)。\n\n // 装饰-排序-还原:显式携带原下标,稳定性不依赖 Array.prototype.sort 的实现细节。\n const decorated: Array<{ item: T; score: number; index: number }> = items.map((item, index) => ({\n item,\n score: scoreOf(item),\n index,\n }));\n\n decorated.sort((a, b) => {\n if (a.score > b.score) return -1;\n if (a.score < b.score) return 1;\n return a.index - b.index;\n });\n\n const result: T[] = [];\n // 上界取 min(k, decorated.length):k > 候选数时全部返回(修复前 k>len 走短路分支,\n // 删短路后若仍循环到 k 会读 undefined——`?.item` 掩盖成 push undefined 的更糟形态)。\n const take = Math.min(k, decorated.length);\n for (let i = 0; i < take; i++) result.push(decorated[i]!.item);\n return result;\n}\n","/**\r\n * 查询引擎:枚举(物化)、读取(带失效回退)、存在性(walk / cycle)。\r\n *\r\n * 璇玑图原型:同一方锦缎有多种读法。枚举器按规则把「字路」物化进路径索引,\r\n * 读取器负责回答「从这个字起读,有哪些合法读法」,并保证**永不返回失效条目**——\r\n * 宁可回退到现场重算,也不把被删边之后的旧读法交给调用方。\r\n *\r\n * 完备性与精确性分工(C1):\r\n * - 剪枝(rule.start / nodeConstraint / simple / 长度)允许是**超集**;\r\n * - 是否物化一律由 {@link isLegalPath} 精确终判;\r\n * - 因此逐节点谓词放宽不会产生错误条目,只会多走几步。\r\n *\r\n * 零宿主依赖。\r\n *\r\n * @module xuanji/query/query-engine\r\n */\r\n\r\nimport type { GraphSpace, NodeId } from '../core/graph-space.ts';\r\nimport type { PathRule } from '../core/path-rule.ts';\r\nimport { RuleCompositionError, effectiveMaxEdges, isLegalPath } from '../core/path-rule.ts';\r\nimport type { PathEntry } from '../core/path-entry.ts';\r\nimport { entryLabels, isLiveEntry, makeEntry } from '../core/path-entry.ts';\r\nimport type { PathIndex } from '../index/path-index.ts';\r\nimport { hasDirectedCycle } from '../ops/cycle-existence.ts';\r\nimport { topK } from './topk.ts';\r\n\r\nexport { hasDirectedCycle };\r\nexport { hasCycleInAdjacency } from '../ops/cycle-existence.ts';\r\n\r\n/**\r\n * `'top-k-weight'` 的候选集预算(候选路径条数上限)。\r\n *\r\n * `'dfs-order'` 可以在达到 `maxPaths` 时立刻停;`'top-k-weight'` **不能**——不看全候选\r\n * 就无法说「这几条是权重最高的」。因此它必须有一个显式上界,否则「保留高权重」会变成\r\n * 一次无界内存放大。超界抛错而不是静默退回 DFS 序:静默降级会让调用方以为拿到了\r\n * 高权重结果(与 XJ-10「非法规则不得静默近似」同一立场)。\r\n *\r\n * ## 标定依据(cycle-112 继承项 B,实测于 cycle-111 验收)\r\n *\r\n * 完全二分层图(每层宽度 w、层数 d ⇒ 候选数 ≈ w^d)下的实测:\r\n *\r\n * | 形状 | 候选数 | 结果 | 耗时 |\r\n * |---|---|---|---|\r\n * | 6 层 × 8 宽 | 32,768 | OK | ~343 ms |\r\n * | 6 层 × 10 宽 | **100,000** | 恰好用尽(不抛错) | ~1268 ms |\r\n * | 7 层 × 8 宽 | 262,144 | **抛错**(超界) | — |\r\n *\r\n * ⇒ 100_000 的余量只有 **3.1 倍**(262,144 / 100,000),处于「6 层稠密图安全、7 层必炸」\r\n * 的边界。它标定的是**内存与时间**的工程上限(候选数组每条是 `[...nodes]` 浅拷贝),\r\n * 不是数学不变量。**成对不等式**:候选数 ≈ `w^d`,调用方控制它的手柄是 `rule.lMax`\r\n * (层数上限)与 `rule.edgeTypes`(宽度来源)。需要支持更稠密的图时,请显式传\r\n * `candidateBudget` 并接受对应成本,**不要调大这个默认值**——默认值保护的是没读这段\r\n * 注释的人。\r\n */\r\nconst TOP_K_CANDIDATE_BUDGET = 100_000;\r\n\r\nfunction resolveCandidateBudget(value: number | undefined): number {\r\n if (typeof value !== 'number' || !Number.isFinite(value)) return TOP_K_CANDIDATE_BUDGET;\r\n return Math.max(1, Math.floor(value));\r\n}\r\n\r\n/**\r\n * 路径权重:各步边的 `XJEdge.weight ?? 0` 之和。\r\n *\r\n * `weight` 此前全库零使用点(XJ-07 / #32)。未标注权重的边贡献 0,于是「所有权重都是 0」\r\n * 时 top-k 的分数全相同、`topK` 的稳定性保证它退化为 DFS 序——**确定且可复现**。\r\n *\r\n * ## 为什么每步取「可用边里的最大权重」而不是「第一条命中的边」(cycle-111 修正)\r\n *\r\n * 图空间允许多重边(同端不同型是两条边,见 `edgeKey` 的 `source->target->type`)。\r\n * 原实现 `for (const type of rule.edgeTypes) { … break; }` 取**迭代序里第一条存在的边**,\r\n * 而 `PathRule.edgeTypes` 是 `Set`——于是同一个图、同一条规则,只要改变 `Set` 的声明顺序,\r\n * top-k 的结果就会翻转。实测(a→b 同时有 `call(1)` 与 `import(100)`,另有 a→d→c 权重 2+2):\r\n * 默认顺序选出 `a>d>c`;把 Set 改成 `['import','call']` 后选出 `a>b>c`。\r\n * 这与本函数承诺的「确定且可复现」直接冲突,也与「高权重路径该被保留」的语义冲突。\r\n *\r\n * 取 **max** 而不是 sum 的理由:一步只走一条边,把两条边的权重相加会凭空放大分数;\r\n * 而 `isLegalPath` 判定的是「存在任一允许类型的边」,所以这一步在语义上确实可以\r\n * 走权重最高的那一条。max 是「不夸大、不依赖顺序」的唯一选择。\r\n */\r\nfunction pathWeight(graph: GraphSpace, rule: PathRule, nodes: readonly NodeId[]): number {\r\n let total = 0;\r\n for (let i = 0; i + 1 < nodes.length; i++) {\r\n let best = 0;\r\n let found = false;\r\n for (const type of rule.edgeTypes) {\r\n const edge = graph.getEdge(nodes[i]!, nodes[i + 1]!, type);\r\n if (!edge) continue;\r\n const weight = edge.weight ?? 0;\r\n if (!found || weight > best) {\r\n best = weight;\r\n found = true;\r\n }\r\n }\r\n total += best;\r\n }\r\n return total;\r\n}\r\n\r\n/** 枚举选项。 */\r\nexport interface EnumerateOptions {\r\n /**\r\n * 最多物化多少条新路径。\r\n * - `truncation: 'dfs-order'`(缺省):达到即停止,截断按 **DFS 序**(确定、可复现);\r\n * - `truncation: 'top-k-weight'`:先枚举全部候选再按权重降序取前 k。\r\n * 已是 `index.hashIndex` 中存在的路径不计入配额(跳过且不返回)。\r\n */\r\n readonly maxPaths?: number;\r\n /**\r\n * 截断策略(XJ-07)。\r\n *\r\n * 规范 §7.3 的原文是「路径数爆炸 → Top-K 截断,保留高权重路径」,而实现一直按 DFS 序\r\n * 截断——高权重路径可能正好是被 DFS 序丢掉的那条。这里把两种语义都显式化:\r\n * **`maxPaths` 只表示条数上限,本身不承诺「保留最重要的那些」**;要保权重必须显式选\r\n * `'top-k-weight'`。缺省为 `'dfs-order'`(与历史行为一致)。\r\n */\r\n readonly truncation?: 'dfs-order' | 'top-k-weight';\r\n /** `'top-k-weight'` 的候选集预算;缺省 {@link TOP_K_CANDIDATE_BUDGET}。 */\r\n readonly candidateBudget?: number;\r\n}\r\n\r\n/**\r\n * 从每个 start 出发 DFS,把合法路径物化进 `index`,返回**本次新增**的条目。\r\n *\r\n * 契约:\r\n * - 节点必须带 attrs(`getNodeAttrs` 为空则跳过);`rule.start` 不满足的 start 跳过;\r\n * - 边遍历用 `graph.outNeighbors(node, rule.edgeTypes)`(空边类型集合不吞噬全图);\r\n * - 剪枝:simple 用本地 visited;nodeConstraint 不满足的节点不进入(超集剪枝);\r\n * - 终判:仅当 `depth >= rule.lMin` 时调用 `isLegalPath`,通过才物化;\r\n * hash 已存在则**跳过且不返回**(旋转等价路径天然去重);\r\n * - 截断(XJ-07):缺省 `'dfs-order'` 达 `maxPaths` 即停;`'top-k-weight'` 先收全部候选\r\n * 再按 `weight` 降序取前 `maxPaths`(同分稳定保持 DFS 序)。未知策略抛\r\n * {@link RuleCompositionError},不静默当作 DFS 序;\r\n * - 长度上界由 {@link effectiveMaxEdges} 统一给出(XJ-10):有限 `lMax` 用它;\r\n * 无界 + simple 用 |V| 兜底;**无界 + 非 simple 是非法规则,直接抛错**而不是\r\n * 静默用 |V| 近似出一份「看起来完整」的结果。\r\n *\r\n * 复杂度:O(visited 路径数 × 路径长度),最坏指数级——这正是需要索引与增量引擎的原因。\r\n */\r\nexport function enumerate(\r\n graph: GraphSpace,\r\n rule: PathRule,\r\n starts: Iterable<NodeId>,\r\n index: PathIndex,\r\n options: EnumerateOptions = {},\r\n): PathEntry[] {\r\n const truncation = options.truncation ?? 'dfs-order';\r\n if (truncation !== 'dfs-order' && truncation !== 'top-k-weight') {\r\n throw new RuleCompositionError(`unknown truncation strategy \"${String(truncation)}\"`);\r\n }\r\n const maxPaths = options.maxPaths ?? Number.POSITIVE_INFINITY;\r\n const results: PathEntry[] = [];\r\n if (!(maxPaths > 0) || graph.nodeCount === 0) return results;\r\n\r\n const simple = rule.simple === true;\r\n // 剪枝用 pruneClosed(组合规则可能与判定用 closed 不同),判定仍由 isLegalPath 负责。\r\n const closedForPruning = (rule.pruneClosed ?? rule.closed) === true;\r\n // 长度上界统一由库内唯一实现给出(XJ-10)。非法规则(无界 + 非 simple)在此抛错。\r\n const depthCap = effectiveMaxEdges(rule, graph.nodeCount);\r\n if (!(depthCap >= rule.lMin)) return results;\r\n\r\n const topKMode = truncation === 'top-k-weight';\r\n const candidateBudget = topKMode ? resolveCandidateBudget(options.candidateBudget) : Number.POSITIVE_INFINITY;\r\n const candidates: NodeId[][] = [];\r\n\r\n const closed = closedForPruning;\r\n const path: NodeId[] = [];\r\n const visited = new Set<NodeId>();\r\n\r\n /** DFS 序:立刻物化并计数;top-k:只收候选(顺序由 topK 决定)。 */\r\n const accept = (nodes: readonly NodeId[]): void => {\r\n if (topKMode) {\r\n candidates.push([...nodes]);\r\n if (candidates.length > candidateBudget) {\r\n throw new RuleCompositionError(\r\n `top-k-weight: candidate set exceeds budget (${candidateBudget}); narrow the rule or raise candidateBudget`,\r\n );\r\n }\r\n return;\r\n }\r\n const entry = makeEntry(rule, nodes, graph.version);\r\n if (index.hashIndex.has(entry.hash)) return;\r\n index.add(entry);\r\n results.push(entry);\r\n };\r\n\r\n /** 是否已达条数上限(只有 DFS 序模式会提前停)。 */\r\n const full = (): boolean => !topKMode && results.length >= maxPaths;\r\n\r\n const expand = (node: NodeId, depth: number, canExpand: boolean): void => {\r\n if (full()) return;\r\n\r\n if (depth >= rule.lMin && isLegalPath(rule, path, graph)) {\r\n accept(path);\r\n if (full()) return;\r\n }\r\n\r\n if (!canExpand || depth >= depthCap) return;\r\n\r\n for (const next of graph.outNeighbors(node, rule.edgeTypes)) {\r\n if (full()) return;\r\n\r\n // 闭合路径的「末=首」是唯一允许的重复节点;simple 下它只能作为终点,不能继续展开。\r\n const closesCycle = closed && next === path[0];\r\n const alreadyVisited = visited.has(next);\r\n if (simple && alreadyVisited && !closesCycle) continue;\r\n\r\n const attrs = graph.getNodeAttrs(next);\r\n if (!attrs) continue;\r\n if (rule.nodeConstraint && !rule.nodeConstraint(next, attrs)) continue;\r\n\r\n const pushedToVisited = simple && !alreadyVisited;\r\n if (pushedToVisited) visited.add(next);\r\n path.push(next);\r\n expand(next, depth + 1, !(simple && closesCycle));\r\n path.pop();\r\n if (pushedToVisited) visited.delete(next);\r\n }\r\n };\r\n\r\n for (const start of starts) {\r\n if (full()) break;\r\n const attrs = graph.getNodeAttrs(start);\r\n if (!attrs) continue;\r\n if (!rule.start(start, attrs)) continue;\r\n if (rule.nodeConstraint && !rule.nodeConstraint(start, attrs)) continue;\r\n\r\n path.length = 0;\r\n visited.clear();\r\n path.push(start);\r\n if (simple) visited.add(start);\r\n expand(start, 0, true);\r\n }\r\n\r\n if (!topKMode) return results;\r\n\r\n // top-k:候选已全部收齐(含旋转等价重复),按权重降序取前 maxPaths。\r\n // 同 hash 的第二条会被下面继续跳过,因此返回条数可能少于 maxPaths——如实返回,不补齐。\r\n const ranked = topK(candidates, maxPaths, (nodes) => pathWeight(graph, rule, nodes));\r\n for (const nodes of ranked) {\r\n const entry = makeEntry(rule, nodes, graph.version);\r\n if (index.hashIndex.has(entry.hash)) continue;\r\n index.add(entry);\r\n results.push(entry);\r\n }\r\n return results;\r\n}\r\n\r\n/**\r\n * 读取某个起点的物化路径。\r\n *\r\n * 契约:`index.queryByStart` 已过滤 stale;这里再用 {@link isLiveEntry} 逐条确认\r\n * (版本与相邻边存在性),因此**永不返回 stale 条目**。\r\n * 存活率过低((原条目数 - live) / 原条目数 > 0.3)说明索引对该起点已大面积失效,\r\n * 交给 `fallback` 现场重算;没有 fallback 就返回 `null`(\"不知道\"而不是\"没有\")。\r\n *\r\n * @param fallback 惰性重算(通常 `() => enumerate(graph, rule, [start], index)`)\r\n * @returns 存活条目;空或失效比例 > 0.3 时为 `fallback() ?? null`\r\n */\r\nexport function query(\r\n rule: PathRule,\r\n start: NodeId,\r\n index: PathIndex,\r\n graph: GraphSpace,\r\n fallback?: () => PathEntry[],\r\n): PathEntry[] | null {\r\n // 分母必须包含**已标记 stale** 的条目(getStartBuckets 不做过滤),否则\r\n // 「100 条里 40 条被删边置 stale」的索引会被算成 0% 失效,永远不回退(§5.4 的契约就失效了)。\r\n const buckets = index.getStartBuckets(start);\r\n const all: PathEntry[] = [];\r\n for (const entries of buckets.values()) all.push(...entries);\r\n const live = all.filter((entry) => isLiveEntry(entry, graph, rule));\r\n if (live.length === 0) return fallback ? fallback() : null;\r\n const staleRatio = (all.length - live.length) / all.length;\r\n if (staleRatio > 0.3) return fallback ? fallback() : null;\r\n return live;\r\n}\r\n\r\n/**\r\n * 查询返回面的**一页**:条目 + Ω 惰性输出 + 下一页游标(XJ-03)。\r\n *\r\n * 为什么需要它:规范 §5.4 要求「return live 的 **labels**」、§10-9 要求「labels 惰性构建」,\r\n * 但 `PathEntry.labels` 在此之前只有 `entryLabels` 内部读写,宿主拿到的永远是\r\n * `string[][]`——Ω 在类型里存在,在返回面上不存在。这个函数把两者接到一起:\r\n * - 只对**当前页**求 Ω(`entryLabels` 首次调用才求值并缓存到条目上),长尾路径不付成本;\r\n * - `cursor`/`pageSize` 表达分页:调用方可以从上次的 `nextCursor` 继续;\r\n * - 不传 `cursor`/`pageSize` 时返回全部条目(与「不分页」的旧行为同序同值)。\r\n *\r\n * @param entries 已按规则排好序的物化条目(通常来自 `enumerate` 的返回值)\r\n */\r\nexport function readPathPage(\r\n rule: PathRule,\r\n entries: readonly PathEntry[],\r\n options: { readonly cursor?: number; readonly pageSize?: number } = {},\r\n): { entries: PathEntry[]; labels: unknown[]; nextCursor: number | null } {\r\n const cursor = Math.max(0, Math.floor(options.cursor ?? 0));\r\n const pageSize =\r\n options.pageSize === undefined ? Number.POSITIVE_INFINITY : Math.max(0, Math.floor(options.pageSize));\r\n const page = Number.isFinite(pageSize)\r\n ? entries.slice(cursor, cursor + pageSize)\r\n : entries.slice(cursor);\r\n const end = cursor + page.length;\r\n return {\r\n entries: page,\r\n // Ω 只对当前页求值:惰性的意义就是「没被返回的路径不付输出成本」。\r\n labels: page.map((entry) => entryLabels(entry, rule)),\r\n nextCursor: end < entries.length ? end : null,\r\n };\r\n}\r\n\r\n/**\r\n * 存在性判定:(node, depth) 乘积状态 BFS,O(V · lMax)。\r\n *\r\n * 语义边界(务必如实理解):本函数回答「存在一条**walk**」,忽略 simple 的重复节点约束,\r\n * 也不检查整路径谓词 `pathConstraint`——因此:\r\n * - 返回 `false` ⇒ 一定不存在合法路径(可靠的否定下界);\r\n * - 返回 `true` ⇒ 可能存在合法 walk,但**未必**存在合法简单路径。\r\n *\r\n * 逐节点谓词(rule.start / nodeConstraint)与终点谓词(rule.end)参与剪枝与接纳,\r\n * 因为违反它们的节点不可能出现在任何合法路径上(见 {@link isLegalPath})。\r\n *\r\n * @param target 若给出,只接受终点为该节点的 walk\r\n */\r\nexport function existsWalk(\r\n graph: GraphSpace,\r\n rule: PathRule,\r\n start: NodeId,\r\n target?: NodeId,\r\n): boolean {\r\n const startAttrs = graph.getNodeAttrs(start);\r\n if (!startAttrs) return false;\r\n if (!rule.start(start, startAttrs)) return false;\r\n if (rule.nodeConstraint && !rule.nodeConstraint(start, startAttrs)) return false;\r\n\r\n // walk 语义:状态是 (node, depth),深度上界就是 lMax。这里**不能**直接用\r\n // `effectiveMaxEdges`(XJ-10 的唯一实现)——那个函数把「无界 + 非 simple」判为非法规则并抛错,\r\n // 而 walk 存在性判定恰恰要在这种(调用方违约的)规则上仍给出可靠的**否定**下界:\r\n // 无界时用 |V| 终止比抛错更有用,且不夸大结果(见上面「语义边界」)。\r\n const depthCap = Number.isFinite(rule.lMax) ? rule.lMax : graph.nodeCount;\r\n if (!(depthCap >= rule.lMin)) return false;\r\n\r\n const closed = rule.closed === true;\r\n const accepts = (node: NodeId, depth: number): boolean => {\r\n if (depth < rule.lMin) return false;\r\n if (closed && !(depth >= 1 && node === start)) return false;\r\n if (target !== undefined && node !== target) return false;\r\n if (rule.end) {\r\n const attrs = graph.getNodeAttrs(node);\r\n if (!attrs || !rule.end(node, attrs, start)) return false;\r\n }\r\n return true;\r\n };\r\n\r\n if (accepts(start, 0)) return true;\r\n\r\n const seen = new Set<string>();\r\n const stateKey = (node: NodeId, depth: number): string => `${node}\\u0000${depth}`;\r\n const queue: Array<{ node: NodeId; depth: number }> = [{ node: start, depth: 0 }];\r\n seen.add(stateKey(start, 0));\r\n let head = 0;\r\n\r\n while (head < queue.length) {\r\n const current = queue[head++]!;\r\n if (current.depth >= depthCap) continue;\r\n for (const next of graph.outNeighbors(current.node, rule.edgeTypes)) {\r\n const depth = current.depth + 1;\r\n const key = stateKey(next, depth);\r\n if (seen.has(key)) continue;\r\n const attrs = graph.getNodeAttrs(next);\r\n if (!attrs) continue;\r\n if (rule.nodeConstraint && !rule.nodeConstraint(next, attrs)) continue;\r\n seen.add(key);\r\n if (accepts(next, depth)) return true;\r\n queue.push({ node: next, depth });\r\n }\r\n }\r\n return false;\r\n}","/**\n * 路径模式 ρ:边类型序列正则,编译为现有 `PathRule`。\n *\n * 璇玑图原型:「同一方锦缎,十种读法」。此前每种读法都得手写一个 `PathRule`——\n * `edgeTypes` 是一个**扁平 Set**,只能表达「每步可以走这些类型之一」,无法表达\n * 「第一步 call、第二步 import、第三步 dependency」这种**顺序约束**。\n * 研究方案 §4 把查询形式化为 `P = (v_s, ρ, v_t)`,ρ 是边类型序列正则——\n * 本模块就是把那个形式化落地的唯一入口。\n *\n * ## 设计裁决:编译到 PathRule,不新造枚举引擎\n *\n * 三条理由(每条都对应本仓已固化的事实,不是偏好):\n *\n * 1. **剪枝与判据分离是本库的核心不变量**(`isLegalPath` 头注:「剪枝可以是超集,\n * 判据必须精确」)。ρ 天然适合这个分工:`edgeTypes` 取 ρ 中**出现过的所有类型**\n * 的并集(超集,安全方向——多走几步而已),精确的序列匹配放进 `pathConstraint`。\n * 复用一个已证明可靠的引擎,比新写一个 DFS 少一整套完备性风险。\n *\n * 2. **免费继承 `enumerate` 的全部工程能力**:分页游标(`nextCursor`)、Booth 规范化\n * 去重(同一条路径从不同起点读只算一条)、增量索引物化、`maxPaths` 截断、\n * `top-k-weight` 排序。任何新引擎都要把这些重新做一遍并且重新证明。\n *\n * 3. **NFA 乘积索引被明确否决过**(cycle-111 / XJ-08 删除 `core/nfa.ts`:它只有\n * `states = lMax - lMin + 1` 一个减法,而存在性需求已由 `existsWalk` 覆盖)。\n * 重新引入一个「状态表 + 乘积」的索引层会重复那次否决。本模块的 NFA 是\n * **一次性的编译产物**(每规则一份、长度 = 正则规模),用于路径终判,\n * 不做跨规则乘积、不进哈希索引。\n *\n * ## 匹配语义(多重边:**并列标签**,不是「取第一条」)\n *\n * ρ 描述的是**边序列**(类型序列)。路径 `nodes` 有 `nodes.length - 1` 条边。\n *\n * 图空间允许多重边(`edgeKey` 是 `source->target->type`,同端不同型是两条边,\n * 见 `graph-space.ts` 的 `addEdge` 幂等键)。因此一条「节点对」在类型层面上是\n * **一组并列标签**:`u→v` 同时有 `call` 与 `import` 时,这条边的读法有两种。\n * 匹配语义必须是「**存在一种标签指派**使序列匹配 ρ」——这正好是 NFA 子集模拟\n * 的自然语义:每一步对**该节点对上所有存在的候选类型**取转移的并集。\n *\n * 为什么不能「取第一条命中的类型」(此处曾是一个真实缺陷,与 cycle-111 的\n * `pathWeight` 同族):\n *\n * 取第一条会让 `Set` 的迭代序决定结果。实测反例:`u→v` 同时有 `call` 与\n * `import`,ρ = `import`。若候选集迭代序是 `['call','import']`,先命中 `call`,\n * ρ 判为不匹配——而图上明明存在一条 `import` 读法。更糟的是这个「不匹配」\n * 会随 `Set` 构造顺序翻转,同一条 ρ 在同一张图上有两种答案。取并集既正确又免疫顺序。\n *\n * 复杂度:编译 O(|ρ|);终判 O(L·|Q|·|T|),L 为路径边数、|Q| 为 NFA 状态数、\n * |T| 为该节点对上存在的候选类型数(通常 1,多重边时 2-3)。\n * 路径短(通常 ≤ 10)且 |Q| 与正则规模同阶,故终判成本可忽略。\n *\n * 零宿主依赖(库边界守卫会扫这个文件)。\n *\n * @module xuanji/core/path-pattern\n */\n\nimport type { GraphSpace, NodeId } from './graph-space.ts';\nimport type { EdgeType } from './graph-space.ts';\nimport type { PathRule, NormalizeStrategy } from './path-rule.ts';\n\n// ── 语法树 ──────────────────────────────────────────────────────────────\n\n/** ρ 的语法树节点(编译前的中间表示)。 */\nexport type PatternNode =\n /** 单个边类型。 */\n | { readonly kind: 'type'; readonly type: EdgeType }\n /** 拼接(a b 或 a.b):先 a 后 b。 */\n | { readonly kind: 'concat'; readonly parts: readonly PatternNode[] }\n /** 并(a|b):任一匹配。 */\n | { readonly kind: 'union'; readonly options: readonly PatternNode[] }\n /** 量词:重复 min..max 次(max 为 `Infinity` 表示无上界)。 */\n | { readonly kind: 'repeat'; readonly inner: PatternNode; readonly min: number; readonly max: number }\n /** 空串 ε:匹配零条边。 */\n | { readonly kind: 'empty' };\n\n// ── 解析错误 ────────────────────────────────────────────────────────────\n\n/**\n * ρ 解析/编译错误。\n *\n * 响亮失败:非法模式一律抛错,**不静默降级为「匹配一切」**。这条立场与\n * `effectiveMaxEdges`(无界 + 非 simple 直接抛错,不静默用 |V| 近似)同源——\n * 静默近似会让调用方以为自己拿到了精确结果。\n */\nexport class PathPatternError extends Error {\n readonly position: number;\n constructor(message: string, position = -1) {\n super(position >= 0 ? `${message} (at offset ${position})` : message);\n this.name = 'PathPatternError';\n this.position = position;\n }\n}\n\n// ── 词法 ────────────────────────────────────────────────────────────────\n\ntype TokenKind = 'type' | 'pipe' | 'dot' | 'lparen' | 'rparen' | 'star' | 'plus' | 'question' | 'lbrace' | 'rbrace' | 'comma' | 'number';\n\ninterface Token {\n readonly kind: TokenKind;\n readonly text: string;\n readonly pos: number;\n}\n\n/** 边类型名的合法字符集:字母、数字、下划线、连字符。 */\nconst TYPE_CHAR = /[A-Za-z0-9_-]/;\n\nfunction tokenize(pattern: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i]!;\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') { i++; continue; }\n if (ch === '|') { tokens.push({ kind: 'pipe', text: ch, pos: i++ }); continue; }\n if (ch === '.') { tokens.push({ kind: 'dot', text: ch, pos: i++ }); continue; }\n if (ch === '(') { tokens.push({ kind: 'lparen', text: ch, pos: i++ }); continue; }\n if (ch === ')') { tokens.push({ kind: 'rparen', text: ch, pos: i++ }); continue; }\n if (ch === '*') { tokens.push({ kind: 'star', text: ch, pos: i++ }); continue; }\n if (ch === '+') { tokens.push({ kind: 'plus', text: ch, pos: i++ }); continue; }\n if (ch === '?') { tokens.push({ kind: 'question', text: ch, pos: i++ }); continue; }\n if (ch === '{') { tokens.push({ kind: 'lbrace', text: ch, pos: i++ }); continue; }\n if (ch === '}') { tokens.push({ kind: 'rbrace', text: ch, pos: i++ }); continue; }\n if (ch === ',') { tokens.push({ kind: 'comma', text: ch, pos: i++ }); continue; }\n if (/[0-9]/.test(ch)) {\n let j = i;\n while (j < pattern.length && /[0-9]/.test(pattern[j]!)) j++;\n tokens.push({ kind: 'number', text: pattern.slice(i, j), pos: i });\n i = j;\n continue;\n }\n if (TYPE_CHAR.test(ch)) {\n let j = i;\n while (j < pattern.length && TYPE_CHAR.test(pattern[j]!)) j++;\n tokens.push({ kind: 'type', text: pattern.slice(i, j), pos: i });\n i = j;\n continue;\n }\n throw new PathPatternError(`unexpected character \"${ch}\" in path pattern`, i);\n }\n return tokens;\n}\n\n// ── 语法分析(递归下降) ────────────────────────────────────────────────\n\n/**\n * 优先级(低 → 高):`|` < 拼接 < 量词 < 原子。\n * 拼接是最松的隐式运算符:`a b` == `a.b` == 先 a 后 b。\n */\nclass PatternParser {\n private index = 0;\n private readonly tokens: readonly Token[];\n private readonly source: string;\n\n constructor(tokens: readonly Token[], source: string) {\n this.tokens = tokens;\n this.source = source;\n }\n\n parse(): PatternNode {\n if (this.tokens.length === 0) return { kind: 'empty' };\n const node = this.parseUnion();\n if (this.index < this.tokens.length) {\n const tok = this.tokens[this.index]!;\n throw new PathPatternError(`unexpected \"${tok.text}\" in path pattern \"${this.source}\"`, tok.pos);\n }\n return node;\n }\n\n private peek(): Token | undefined {\n return this.tokens[this.index];\n }\n\n private eat(kind: TokenKind): Token | undefined {\n const tok = this.peek();\n if (!tok || tok.kind !== kind) return undefined;\n this.index++;\n return tok;\n }\n\n /** union := concat ('|' concat)* */\n private parseUnion(): PatternNode {\n const options: PatternNode[] = [this.parseConcat()];\n while (this.eat('pipe')) options.push(this.parseConcat());\n return options.length === 1 ? options[0]! : { kind: 'union', options };\n }\n\n /** concat := quantified (('.'? quantified))* */\n private parseConcat(): PatternNode {\n const parts: PatternNode[] = [this.parseQuantified()];\n for (;;) {\n const tok = this.peek();\n if (!tok) break;\n if (tok.kind === 'pipe' || tok.kind === 'rparen') break;\n this.eat('dot'); // `.` 是可选的显式拼接符\n parts.push(this.parseQuantified());\n }\n return parts.length === 1 ? parts[0]! : { kind: 'concat', parts };\n }\n\n /** quantified := atom ('*' | '+' | '?' | '{n[,m]}')? */\n private parseQuantified(): PatternNode {\n const atom = this.parseAtom();\n const tok = this.peek();\n if (!tok) return atom;\n if (tok.kind === 'star') { this.index++; return { kind: 'repeat', inner: atom, min: 0, max: Number.POSITIVE_INFINITY }; }\n if (tok.kind === 'plus') { this.index++; return { kind: 'repeat', inner: atom, min: 1, max: Number.POSITIVE_INFINITY }; }\n if (tok.kind === 'question') { this.index++; return { kind: 'repeat', inner: atom, min: 0, max: 1 }; }\n if (tok.kind === 'lbrace') {\n this.index++;\n const minTok = this.eat('number');\n if (!minTok) throw new PathPatternError('expected a number after \"{\"', tok.pos);\n const min = Number(minTok.text);\n let max = min;\n if (this.eat('comma')) {\n const maxTok = this.eat('number');\n max = maxTok ? Number(maxTok.text) : Number.POSITIVE_INFINITY;\n }\n if (!this.eat('rbrace')) throw new PathPatternError('expected \"}\" to close the repetition', tok.pos);\n if (!Number.isInteger(min) || min < 0) throw new PathPatternError(`invalid repetition lower bound ${min}`, tok.pos);\n if (max !== Number.POSITIVE_INFINITY && (!Number.isInteger(max) || max < min)) {\n throw new PathPatternError(`invalid repetition upper bound ${max} (min is ${min})`, tok.pos);\n }\n return { kind: 'repeat', inner: atom, min, max };\n }\n return atom;\n }\n\n /** atom := type | '(' union ')' */\n private parseAtom(): PatternNode {\n const tok = this.peek();\n if (!tok) throw new PathPatternError('unexpected end of path pattern', this.source.length);\n if (tok.kind === 'type') { this.index++; return { kind: 'type', type: tok.text }; }\n if (tok.kind === 'lparen') {\n this.index++;\n const inner = this.parseUnion();\n if (!this.eat('rparen')) throw new PathPatternError('expected \")\" to close the group', tok.pos);\n return inner;\n }\n throw new PathPatternError(`unexpected \"${tok.text}\" in path pattern`, tok.pos);\n }\n}\n\n/** 解析 ρ 字符串为语法树。空串返回 ε(匹配零条边的空路径)。 */\nexport function parsePathPattern(pattern: string): PatternNode {\n if (typeof pattern !== 'string') throw new PathPatternError('path pattern must be a string');\n return new PatternParser(tokenize(pattern), pattern).parse();\n}\n\n// ── 编译:语法树 → NFA ──────────────────────────────────────────────────\n\n/**\n * NFA 状态:一组「接受某条边之后能到达的状态」的转移表。\n *\n * 用**转移边标注的类型集合**而非字符集——一次转移可以同时接受多个边类型\n * (多类型分支合并),减少状态数。\n */\ninterface NfaState {\n /** 该状态是否为接受态。 */\n accept: boolean;\n /** 出边:{ 可接受的边类型集合, 目标状态 }。 */\n readonly transitions: Array<{ types: ReadonlySet<EdgeType>; to: number }>;\n /** ε 转移(Thompson 构造的胶水)。 */\n readonly epsilon: number[];\n}\n\nclass NfaBuilder {\n readonly states: NfaState[] = [];\n\n newState(accept = false): number {\n this.states.push({ accept, transitions: [], epsilon: [] });\n return this.states.length - 1;\n }\n\n addTransition(from: number, types: ReadonlySet<EdgeType>, to: number): void {\n this.states[from]!.transitions.push({ types, to });\n }\n\n addEpsilon(from: number, to: number): void {\n this.states[from]!.epsilon.push(to);\n }\n}\n\n/**\n * Thompson 构造:返回 { start, accept }(都不是子片段自己的状态)。\n *\n * 说明:`repeat` 的 `max` 在编译期被展开为有限份拷贝(`max` 有限时),\n * 无上界时用「循环回到自身」表达(Kleene 星)。因此**编译产物大小与\n * `{n,m}` 的 m 线性相关**——`call{1,50}` 会产生 50 份拷贝,这是显式代价。\n */\nfunction compileNode(node: PatternNode, b: NfaBuilder): { start: number; accept: number } {\n switch (node.kind) {\n case 'empty': {\n const s = b.newState();\n const a = b.newState(true);\n b.addEpsilon(s, a);\n return { start: s, accept: a };\n }\n case 'type': {\n const s = b.newState();\n const a = b.newState(true);\n b.addTransition(s, new Set([node.type]), a);\n return { start: s, accept: a };\n }\n case 'concat': {\n if (node.parts.length === 0) return compileNode({ kind: 'empty' }, b);\n let acc = compileNode(node.parts[0]!, b);\n const start = acc.start;\n for (let i = 1; i < node.parts.length; i++) {\n const next = compileNode(node.parts[i]!, b);\n // 拼接点:前一子片段的 accept 不再是接受态\n b.states[acc.accept]!.accept = false;\n b.addEpsilon(acc.accept, next.start);\n acc = { start, accept: next.accept };\n }\n return acc;\n }\n case 'union': {\n const s = b.newState();\n const a = b.newState(true);\n for (const option of node.options) {\n const sub = compileNode(option, b);\n b.addEpsilon(s, sub.start);\n b.states[sub.accept]!.accept = false;\n b.addEpsilon(sub.accept, a);\n }\n return { start: s, accept: a };\n }\n case 'repeat':\n return compileRepeat(node, b);\n }\n}\n\nfunction compileRepeat(\n node: Extract<PatternNode, { kind: 'repeat' }>,\n b: NfaBuilder,\n): { start: number; accept: number } {\n const s = b.newState();\n // 先放 min 份必需拷贝\n let cursor = s;\n for (let i = 0; i < node.min; i++) {\n const sub = compileNode(node.inner, b);\n b.addEpsilon(cursor, sub.start);\n b.states[sub.accept]!.accept = false;\n // 中间态的 accept 已被清掉;用其 accept 作为下一段的入口\n cursor = sub.accept;\n }\n\n if (node.max === Number.POSITIVE_INFINITY) {\n // Kleene 星:一个可跳过的循环体\n const a = b.newState(true);\n const body = compileNode(node.inner, b);\n b.addEpsilon(cursor, a); // 零次(或 min 次之后停止)\n b.addEpsilon(cursor, body.start); // 再来一次\n b.states[body.accept]!.accept = false;\n b.addEpsilon(body.accept, cursor); // 回到循环入口\n return { start: s, accept: a };\n }\n\n // 有限上界:额外 (max - min) 份可选拷贝\n const a = b.newState(true);\n b.addEpsilon(cursor, a); // 取 min 次就停\n let tail = cursor;\n const extra = node.max - node.min;\n for (let i = 0; i < extra; i++) {\n const sub = compileNode(node.inner, b);\n b.addEpsilon(tail, sub.start);\n b.states[sub.accept]!.accept = false;\n b.addEpsilon(sub.accept, a);\n tail = sub.accept;\n }\n return { start: s, accept: a };\n}\n\n/** ε 闭包(幂等:visited 集合保证每个状态只处理一次)。 */\nfunction epsilonClosure(states: ReadonlySet<number>, nfa: readonly NfaState[]): Set<number> {\n const out = new Set<number>(states);\n const stack = [...states];\n while (stack.length > 0) {\n const s = stack.pop()!;\n for (const next of nfa[s]!.epsilon) {\n if (!out.has(next)) { out.add(next); stack.push(next); }\n }\n }\n return out;\n}\n\n/** 编译后的路径模式(可复用的终判器)。 */\nexport interface CompiledPathPattern {\n /** 原始 ρ 文本(诊断用)。 */\n readonly source: string;\n /** ρ 中出现的所有边类型(超集剪枝用)。 */\n readonly edgeTypes: ReadonlySet<EdgeType>;\n /** NFA 状态数(预算/诊断用)。 */\n readonly stateCount: number;\n /** 路径的最小边数(ρ 的下界,`lMin` 至少取它)。 */\n readonly minEdges: number;\n /** 路径的最大边数;无上界为 `Infinity`。 */\n readonly maxEdges: number;\n /**\n * 判断「给定的边类型序列」是否匹配 ρ(每步恰好一个标签)。\n *\n * 这是终判入口的**退化形态**:多重边上请用 {@link matchesLabelSequence}\n * (每步一组并列标签)。提供它是因为绝大多数单测与诊断场景只关心\n * 「类型序列字面匹配」,不需要建图。\n */\n matchesEdgeSequence(types: readonly EdgeType[]): boolean;\n /**\n * 判断「每步一组并列标签」的序列是否匹配 ρ。\n *\n * 多重边语义:存在一种「每步选一个标签」的指派使序列匹配。\n * 实现为 NFA 子集模拟,每步对**该步全部标签**取转移并集。\n */\n matchesLabelSequence(labelsPerStep: readonly (readonly EdgeType[])[]): boolean;\n}\n\n/** 语法树 → 各类型出现集合 + 长度区间(纯静态分析)。 */\nfunction analyze(node: PatternNode, types: Set<EdgeType>): { min: number; max: number } {\n switch (node.kind) {\n case 'empty': return { min: 0, max: 0 };\n case 'type': types.add(node.type); return { min: 1, max: 1 };\n case 'union': {\n let min = Number.POSITIVE_INFINITY;\n let max = 0;\n for (const option of node.options) {\n const r = analyze(option, types);\n min = Math.min(min, r.min);\n max = Math.max(max, r.max);\n }\n return { min: min === Number.POSITIVE_INFINITY ? 0 : min, max };\n }\n case 'concat': {\n let min = 0;\n let max = 0;\n for (const part of node.parts) {\n const r = analyze(part, types);\n min += r.min;\n max = max === Number.POSITIVE_INFINITY || r.max === Number.POSITIVE_INFINITY\n ? Number.POSITIVE_INFINITY\n : max + r.max;\n }\n return { min, max };\n }\n case 'repeat': {\n const r = analyze(node.inner, types);\n const max = r.max === Number.POSITIVE_INFINITY\n ? Number.POSITIVE_INFINITY\n : r.max * node.max;\n return { min: r.min * node.min, max: node.max === Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY : max };\n }\n }\n}\n\n/**\n * 编译 ρ 为可复用终判器。\n *\n * @throws {PathPatternError} 语法错误 / 空类型名 / 重复上界小于下界\n */\nexport function compilePathPattern(pattern: string): CompiledPathPattern {\n const ast = parsePathPattern(pattern);\n const edgeTypes = new Set<EdgeType>();\n const bounds = analyze(ast, edgeTypes);\n if (edgeTypes.size === 0) {\n // ε 模式(空路径)是合法的,但没有边类型可剪枝——调用方会得到空子集,\n // 因此这里**明确允许**,由 compilePathRule 决定怎么处理(见其注释)。\n }\n\n const builder = new NfaBuilder();\n const fragment = compileNode(ast, builder);\n const nfa = builder.states;\n const startClosure = epsilonClosure(new Set([fragment.start]), nfa);\n\n return {\n source: pattern,\n edgeTypes,\n stateCount: nfa.length,\n minEdges: bounds.min,\n maxEdges: bounds.max,\n matchesEdgeSequence(types: readonly EdgeType[]): boolean {\n return runNfa(types.map((t) => [t]));\n },\n matchesLabelSequence(labelsPerStep: readonly (readonly EdgeType[])[]): boolean {\n if (labelsPerStep.some((labels) => labels.length === 0)) return false;\n return runNfa(labelsPerStep);\n },\n };\n\n /**\n * NFA 子集模拟。每步对**该步全部并列标签**取转移并集——\n * 这正是「存在一种标签指派使序列匹配」的实现(多重边语义)。\n */\n function runNfa(steps: readonly (readonly EdgeType[])[]): boolean {\n let current = startClosure;\n for (const labels of steps) {\n const next = new Set<number>();\n for (const state of current) {\n for (const transition of nfa[state]!.transitions) {\n for (const label of labels) {\n if (transition.types.has(label)) { next.add(transition.to); break; }\n }\n }\n }\n if (next.size === 0) return false;\n current = epsilonClosure(next, nfa);\n }\n for (const state of current) if (nfa[state]!.accept) return true;\n return false;\n }\n}\n\n// ── 与 PathRule 的桥接 ─────────────────────────────────────────────────\n\n/**\n * 从图解析一对节点之间**所有存在的候选边类型**(多重边的并列标签)。\n *\n * 返回空数组表示「这对节点之间没有任何候选边」——调用方应判为不匹配。\n *\n * **刻意返回数组而不是单个类型。** 曾经这里是 `Edgetype | null`,按\n * `candidates` 的迭代序取第一条命中的类型,于是 `Set` 的构造顺序决定了\n * 匹配结果(多条边并存时 ρ 有两种答案)。取全部并列标签把「存在一种读法」\n * 这个语义交给 NFA 子集模拟处理,结果与迭代序无关。详见模块头注。\n *\n * 为什么不放在 `CompiledPathPattern` 里:那会让终判器依赖 `GraphSpace`,\n * 而终判器只需要类型序列。分层清楚,也让 `matchesEdgeSequence` 可以用\n * 纯字符串数组单测(无需建图)。\n */\nexport function edgeTypesBetween(\n graph: GraphSpace,\n from: NodeId,\n to: NodeId,\n candidates: ReadonlySet<EdgeType>,\n): EdgeType[] {\n const present: EdgeType[] = [];\n for (const type of candidates) {\n if (graph.getEdge(from, to, type)) present.push(type);\n }\n return present;\n}\n\n/** `compilePathRule` 的选项。 */\nexport interface PathPatternRuleOptions {\n /** 规则名(诊断/索引键用)。 */\n readonly name?: string;\n /** 起点谓词;缺省「任意节点」。 */\n readonly start?: PathRule['start'];\n /** 终点谓词。 */\n readonly end?: PathRule['end'];\n /** 路径上每个节点必须满足(超集剪枝)。 */\n readonly nodeConstraint?: PathRule['nodeConstraint'];\n /**\n * 显式跳数区间。缺省取 ρ 自身的长度区间。\n *\n * 为什么允许覆盖:ρ 的 `{n,m}` 已表达长度,但调用方常用「任意 ρ、最多 5 跳」\n * 这种正交约束。取**交集**(与 ρ 的区间求交)而不是替换——替换会让\n * `call{2,4}` + `maxHops: 10` 静默接受 10 跳路径。\n */\n readonly minHops?: number;\n readonly maxHops?: number;\n /** 是否要求简单路径;缺省 `true`(与 findPathsBetween 一致)。 */\n readonly simple?: boolean;\n /** 是否闭合(环枚举)。 */\n readonly closed?: boolean;\n /** 规范化策略;缺省 `'none'`(与 findPathsBetween 一致)。 */\n readonly normalize?: NormalizeStrategy;\n}\n\n/**\n * 把 ρ 编译成一条 `PathRule`——**这是 ρ 进入枚举引擎的唯一通道**。\n *\n * 三件事的分工(缺一不可):\n * - `edgeTypes` = ρ 中出现过的**所有**类型的并集(超集剪枝:不会误剪合法路径,\n * 只可能多走几步)。ε 模式(无类型)时为**空集**,`enumerate` 会因此返回空\n * (`outNeighbors(node, 空集)` = 空)——这是诚实行为:没有边类型的 ρ 无法在\n * 非空路径上匹配,只有 `lMin = 0` 的单节点路径合法。\n * - `pathConstraint` = 精确的 NFA 终判(序列匹配)。\n * - `lMin` / `lMax` = ρ 长度区间与显式跳数区间的**交集**;ρ 无上界时用\n * `minHops`/`maxHops`(或 `|V|`)兜底,绝不留下无界 + 非 simple 的非法组合。\n */\nexport function compilePathRule(pattern: string, options: PathPatternRuleOptions = {}): PathRule {\n const compiled = compilePathPattern(pattern);\n\n const patternMax = compiled.maxEdges;\n const optMax = options.maxHops ?? Number.POSITIVE_INFINITY;\n const lMaxRaw = Math.min(patternMax, optMax);\n const lMinRaw = Math.max(compiled.minEdges, options.minHops ?? 0);\n\n const simple = options.simple ?? true;\n // 无界 + 非 simple 是非法规则(路径集合无限,spec §八):此时把 simple 强制为 true。\n // 静默把 lMax 收到 |V| 而保留 simple:false 会枚举「长度 ≤ |V| 的任意路径」,\n // 与调用方「简单路径」的预期不符——所以这里改 simple 而不是改 lMax。\n const needsBounding = lMaxRaw === Number.POSITIVE_INFINITY;\n const effectiveSimple = needsBounding ? true : simple;\n // 无上界时留给 effectiveMaxEdges 用 |V| 兜底(它要求 simple === true,上面已保证)。\n const lMax = needsBounding ? Number.POSITIVE_INFINITY : lMaxRaw;\n\n const rule: PathRule = {\n name: options.name ?? `pattern:${pattern}`,\n version: 1,\n start: options.start ?? (() => true),\n edgeTypes: compiled.edgeTypes,\n lMin: lMinRaw,\n lMax,\n simple: effectiveSimple,\n ...(options.nodeConstraint ? { nodeConstraint: options.nodeConstraint } : {}),\n ...(options.end ? { end: options.end } : {}),\n ...(options.closed === true ? { closed: true } : {}),\n normalize: options.normalize ?? 'none',\n // 精确终判:把 nodes 解成「每步并列标签」的序列,交给 NFA 子集模拟。\n // 迭代序无关性由 matchesLabelSequence 保证(见模块头注的实测反例)。\n pathConstraint: (nodes: readonly NodeId[], graph: GraphSpace) => {\n const labelsPerStep: EdgeType[][] = [];\n for (let i = 0; i + 1 < nodes.length; i++) {\n const labels = edgeTypesBetween(graph, nodes[i]!, nodes[i + 1]!, compiled.edgeTypes);\n if (labels.length === 0) return false;\n labelsPerStep.push(labels);\n }\n return compiled.matchesLabelSequence(labelsPerStep);\n },\n };\n return rule;\n}\n\n/**\n * 解析错误诊断:把 ρ 的错误位置映射回人类可读的指示(供工具层返回给模型)。\n *\n * 为什么单独给一个函数:LLM 拿到 `PathPatternError` 的裸 message 只能看到\n * 「unexpected `` at offset 12」,无法定位。这里给出带 `^` 标记的双行片段。\n */\nexport function describePatternError(pattern: string, error: unknown): string {\n if (!(error instanceof PathPatternError)) {\n return error instanceof Error ? error.message : String(error);\n }\n if (error.position < 0) return error.message;\n const caret = `${' '.repeat(Math.max(0, error.position))}^`;\n return `${error.message}\\n ${pattern}\\n ${caret}`;\n}\n","/**\r\n * XuanjiAdapter — 璇玑图算法库的宿主适配层。\r\n *\r\n * 定位(对应 `docs/research/璇玑图算法库设计与集成方案.md` §七):\r\n * 把项目的 `GraphData` / `GraphDelta` 翻译成库的 `GraphSpace` / 增量事件,\r\n * 把宿主需求翻译成 `PathRule`,然后委托库执行。**这是唯一与宿主耦合的模块**。\r\n *\r\n * 强制边界:`src/xuanji/**` 只依赖自身(见 tests/unit/xuanji/libraryBoundary.test.ts),\r\n * 所有 `GraphNode` / branded id / GraphDelta 的转换都发生在这里。\r\n *\r\n * cycle-109(XJ-02 / XJ-04 / XJ-05)在这一层补上三件事:\r\n * 1. {@link XuanjiAdapterPool}:按 repoId 缓存 adapter(LRU),让「同一个图空间」能被\r\n * 反复查询而不是每次重建;{@link XuanjiAdapter.syncGraph} 给出 `'full' | 'delta' | 'none'`\r\n * 的判定(判定结果作为返回值暴露,便于测试与观测)。\r\n * 判定原则:**不确定就全量重建**——缓存陈旧会直接返回过期路径,宁可慢不可错。\r\n * 2. `applyDelta` 在 stale 比例超阈值时清空该规则的索引并把它写进 `degradedRules`,\r\n * 「需全量重枚举」从一句注释变成一个可观测的返回值。\r\n * 3. 静态校验落地在一条**公共**的 {@link XuanjiAdapter.validateRule} 上:\r\n * `registerRule` 用它做注册闸门(errors 非空 ⇒ 不注册,返回 `{ index, validation }`),\r\n * 两条内联读法规则用它做**每次枚举都执行**的闸门(`findPathsBetween` / `cycles` →\r\n * {@link XuanjiAdapter.assertRule})。生产环境(`src/` 内)的必然校验调用是后者;\r\n * `registerRule` 目前没有生产调用点,保留给宿主程序化注册/测试,不当成「唯一入口」。\r\n *\r\n * @module shared/analysis/XuanjiAdapter\r\n */\r\n\r\nimport type { GraphData, GraphDelta, GraphEdge, GraphNode } from '../../types/index.ts';\r\nimport { GraphSpace, type NodeAttrs, type XJEdge } from '../../xuanji/core/graph-space.ts';\r\nimport type { PathRule, RuleValidation } from '../../xuanji/core/path-rule.ts';\r\nimport { RuleCompositionError, validateRule as validateRuleSpec } from '../../xuanji/core/path-rule.ts';\r\nimport { PathIndex } from '../../xuanji/index/path-index.ts';\r\nimport {\r\n DEFAULT_THETA_STALE,\r\n onEdgeAdded,\r\n onEdgeRemovedDetailed,\r\n wasLastIncrementalDegraded,\r\n} from '../../xuanji/index/incremental.ts';\r\nimport {\r\n InvariantChecker,\r\n type InvariantResult,\r\n type InvariantThresholds,\r\n} from '../../xuanji/ops/invariants.ts';\r\nimport { enumerate, readPathPage } from '../../xuanji/query/query-engine.ts';\r\nimport {\r\n compilePathPattern,\r\n compilePathRule,\r\n describePatternError,\r\n parsePathPattern,\r\n type CompiledPathPattern,\r\n} from '../../xuanji/core/path-pattern.ts';\r\n\r\n/** 允许的边类型(项目 `GraphEdge.type` 的取值域)。 */\r\nexport type AdapterEdgeType = GraphEdge['type'];\r\n\r\n/** 默认参与路径读法的边类型:结构包含边 `contains` 不构成调用/依赖路径。 */\r\nexport const DEFAULT_PATH_EDGE_TYPES: readonly AdapterEdgeType[] = [\r\n 'call',\r\n 'import',\r\n 'extend',\r\n 'implement',\r\n 'dependency',\r\n];\r\n\r\n/**\r\n * Σ:`GraphEdge.type` 的**全部取值域**(cycle-135)。\r\n *\r\n * 用途是把 ρ 里「类型名拼错」与「类型名合法但这张图上没有这种边」分开。\r\n * 两者都导致零结果,但**诊断价值完全不同**:前者是用户输入错误(应提示改用\r\n * 正确的类型名),后者是合法的零结果(图上确实没有这种关系)。若混在一个\r\n * 集合里返回,`calls+` 与「图上没有 call 边」在返回面同形,Agent/用户都会误判。\r\n *\r\n * 与 {@link DEFAULT_PATH_EDGE_TYPES} 的关系:后者是「路径读法参与遍历的子集」,\r\n * 前者是「类型名的合法全集」。前者用于拼错诊断,后者用于默认遍历范围——\r\n * 两者不可互相替代(`contains` 在 Σ 里合法,但不参与默认路径读法)。\r\n */\r\nexport const KNOWN_ADAPTER_EDGE_TYPES: ReadonlySet<AdapterEdgeType> = new Set<AdapterEdgeType>([\r\n 'call',\r\n 'import',\r\n 'extend',\r\n 'implement',\r\n 'dependency',\r\n 'contains',\r\n 'association',\r\n 'aggregation',\r\n 'composition',\r\n]);\r\n\r\n/** `findPathsBetween` 的读法参数。 */\r\nexport interface PathEnumerationOptions {\r\n /** 参与遍历的边类型;缺省用 DEFAULT_PATH_EDGE_TYPES。 */\r\n readonly edgeTypes?: readonly AdapterEdgeType[];\r\n /** 最少跳数(边数),默认 1。 */\r\n readonly minHops?: number;\r\n /** 最多跳数(边数),默认 5。 */\r\n readonly maxHops?: number;\r\n /** 最多返回多少条路径,默认 20。 */\r\n readonly limit?: number;\r\n /** 分页游标(已返回条数;缺省 0)。XJ-03:翻页用上一页的 `nextCursor`。 */\r\n readonly cursor?: number;\r\n /** 每页条数(缺省等于 `limit`)。 */\r\n readonly pageSize?: number;\r\n /**\r\n * 截断策略(cycle-112 继承项 A:`top-k-weight` 此前只有单元测试可达——\r\n * `grep -rn truncation src/ | grep -v xuanji` = 0,生产两处 `enumerate` 都只传\r\n * `{maxPaths}`)。透传给库的 `EnumerateOptions.truncation`:\r\n * - `'dfs-order'`(缺省):达到条数上限即停,按 DFS 序(确定、可复现);\r\n * - `'top-k-weight'`:先枚举全部候选再按**边权重之和**降序取前 k——\r\n * 注意它必须**看完所有候选**才知道哪 k 条最高(候选集超 `TOP_K_CANDIDATE_BUDGET`\r\n * 时库会抛 `RuleCompositionError`),大图上显著更慢,调用方应缩小 `lMax` 或边类型\r\n * 集合后再用。\r\n */\r\n readonly truncation?: 'dfs-order' | 'top-k-weight';\r\n}\r\n\r\n/** 路径枚举结果。 */\r\nexport interface PathEnumerationResult {\r\n /** 每条路径是节点 id 序列(含首尾)。 */\r\n readonly paths: string[][];\r\n /** 是否因 limit 截断。 */\r\n readonly truncated: boolean;\r\n /** 实际返回条数。 */\r\n readonly total: number;\r\n /**\r\n * Ω 惰性输出:本页每条路径经过规则 `output` 映射后的值(XJ-03)。\r\n * 只对**当前页**求值;旧调用方可以完全忽略这个字段(可选)。\r\n */\r\n readonly labels?: readonly unknown[];\r\n /** 下一页游标;`null` 表示没有更多(XJ-03)。不传分页参数时也会给出。 */\r\n readonly nextCursor?: number | null;\r\n}\r\n\r\n/** 环枚举参数。 */\r\nexport interface CycleEnumerationOptions {\r\n readonly edgeTypes?: readonly AdapterEdgeType[];\r\n /** 环的最大长度(边数),默认 6。 */\r\n readonly maxLength?: number;\r\n /** 最多返回多少条环,默认 100。 */\r\n readonly limit?: number;\r\n /** 分页游标(已返回条数;缺省 0)。 */\r\n readonly cursor?: number;\r\n /** 每页条数(缺省等于 `limit`)。 */\r\n readonly pageSize?: number;\r\n}\r\n\r\n/**\r\n * 路径模式 ρ 的枚举参数(cycle-134)。\r\n *\r\n * 与 {@link PathEnumerationOptions} 的关键区别:**没有 `edgeTypes`**。\r\n * 边类型集合由 ρ 文本自身决定(`compilePathPattern` 分析得出),\r\n * 让调用方另传一份只会产生「两份真源」——它们能不一致,且不一致时的语义\r\n * 无法定义(是求交还是替换?)。要限制类型请直接改 ρ。\r\n */\r\nexport interface PatternEnumerationOptions {\r\n /** 起点节点 id;限定后只从该点出发(**缩小起点**,不是过滤结果)。 */\r\n readonly startNode?: string;\r\n /** 终点节点 id;作为 ρ 之外的附加终点谓词。 */\r\n readonly endNode?: string;\r\n /** 显式起点集合(`startNode` 的批量版;两者同时给时 `startNode` 优先)。 */\r\n readonly starts?: Iterable<string>;\r\n /** 最少跳数;与 ρ 自身的长度下界取**交集**。 */\r\n readonly minHops?: number;\r\n /** 最多跳数;与 ρ 自身的长度上界取**交集**。缺省 8。 */\r\n readonly maxHops?: number;\r\n /** 最多返回多少条路径(分页语义同 `findPathsBetween`)。 */\r\n readonly limit?: number;\r\n readonly cursor?: number;\r\n readonly pageSize?: number;\r\n /** 是否要求简单路径;缺省 `true`。 */\r\n readonly simple?: boolean;\r\n /** 是否闭合(环形态的 ρ)。 */\r\n readonly closed?: boolean;\r\n /** 截断策略;语义同 {@link PathEnumerationOptions.truncation}。 */\r\n readonly truncation?: 'dfs-order' | 'top-k-weight';\r\n}\r\n\r\n/** 路径模式枚举结果:在 {@link PathEnumerationResult} 之上补 ρ 的元信息。 */\r\nexport interface PatternEnumerationResult extends PathEnumerationResult {\r\n /** 回显原始 ρ(调用方可断言「我拿到的确实是这条模式的结果」)。 */\r\n readonly pattern: string;\r\n /** ρ 中出现的边类型(已排序;诊断用——说明「这条模式会走哪些关系」)。 */\r\n readonly patternEdgeTypes: readonly string[];\r\n /** 编译后的 NFA 状态数(预算/性能诊断用)。 */\r\n readonly stateCount: number;\r\n /**\r\n * ρ 里出现、但**不在 Σ(`GraphEdge.type` 取值域)**里的类型名(cycle-135)。\r\n *\r\n * 为什么要单独一个字段:`calls+`(拼错)与「图上没有 call 边」都产生\r\n * `paths: []`,但前者是**用户输入错误**、后者是**合法的零结果**。把两者混在\r\n * 返回面里,调用方(模型或用户)无法区分「我写错了」与「图上没有」,必然误判。\r\n *\r\n * 取值域口径:`KNOWN_ADAPTER_EDGE_TYPES`(9 值全集)。注意它与\r\n * `patternEdgeTypes` 的差别——后者是 ρ 里**出现过的所有**标识符(含拼错的),\r\n * 前者只保留拼错的。`unknownEdgeTypes` 非空时结果必为空,且**不是语法错误**。\r\n */\r\n readonly unknownEdgeTypes: readonly string[];\r\n}\r\n\r\n/**\r\n * 把 ρ 语法错误渲染成人类/模型可读的诊断(带 `^` 位置指示)。\r\n *\r\n * 为什么在适配层而不是直接在工具层用 `describePatternError`:工具层拿到的\r\n * 是适配器暴露的 `pathsByPattern`,它可能抛 `PathPatternError`。把「如何解释\r\n * 这个错误」放在适配层,工具层就只需要 `catch` + 调用本函数,不必再 import\r\n * 库内类型(保持工具层只依赖适配层公共面)。\r\n */\r\nexport function formatPatternError(pattern: string, error: unknown): string {\r\n return describePatternError(pattern, error);\r\n}\r\n\r\n/**\r\n * ρ 的**语法预检**:只解析不编译。\r\n *\r\n * 为什么单独暴露一个「只解析」的入口,而不是让调用方直接调 `compilePathPattern`:\r\n * 编译要建 NFA(状态数与 `{n,m}` 的 m 线性相关,`call{1,50}` 会造 50 份拷贝)。\r\n * 界面上的「模式输入框」需要在用户**每次按键**后判断合法性,那里必须付不起\r\n * 编译成本——只解析是 O(|ρ|) 且无分配放大。\r\n *\r\n * @throws {PathPatternError} 语法错误\r\n */\r\nexport function checkPatternSyntax(pattern: string): { ok: true } | { ok: false; error: unknown } {\r\n try {\r\n parsePathPattern(pattern);\r\n return { ok: true };\r\n } catch (error) {\r\n return { ok: false, error };\r\n }\r\n}\r\n\r\n/** 编译 ρ 并返回其静态分析(不枚举)。工具层用它先做「模式是否可用」的预检。 */\r\nexport function inspectPathPattern(pattern: string): CompiledPathPattern {\r\n return compilePathPattern(pattern);\r\n}\r\n\r\n/**\r\n * `syncGraph` 能接受的图数据:`GraphData` 的子集 + 可选的 metadata。\r\n * 写成结构子集而不是 `GraphData`,是为了让只关心 nodes/edges 的调用方(测试、增量重放)\r\n * 不必伪造 `repoId` / `nodeCount` 等字段。\r\n */\r\nexport type SyncGraphInput = Pick<GraphData, 'nodes' | 'edges'> & {\r\n readonly metadata?: { readonly timestamp?: number };\r\n};\r\n\r\n/**\r\n * `syncGraph` 的判定结果:\r\n * - `'none'`:适配层已持有同一个图(结构逐节点/逐边一致),既不重载也不动索引;\r\n * - `'delta'`:新图是当前图的**纯增补**(旧节点/边全在,且属性未变),只应用增量;\r\n * - `'full'`:其余一切情况(首次装载、删除、属性变化、无法确认)——全量重建。\r\n *\r\n * 三种结果的正确性一律优先于性能:只有能**证明**增量等价时才走 `'delta'`/`'none'`。\r\n */\r\nexport type SyncGraphOutcome = 'full' | 'delta' | 'none';\r\n\r\n/** `registerRule` 的返回:索引(未注册时为 null)与静态校验结果。 */\r\nexport interface RuleRegistration {\r\n /** 索引实例;`validation.errors` 非空时为 null(规则未被注册)。 */\r\n readonly index: PathIndex | null;\r\n readonly validation: RuleValidation;\r\n}\r\n\r\n/** `applyDelta` 的结果(XJ-04:`degradedRules` 是本轮被降级重建的规则名)。 */\r\nexport interface DeltaApplicationResult {\r\n readonly addedPaths: number;\r\n readonly stalePaths: number;\r\n /** stale 超阈值 / 增量超预算而**被清空**的规则名:这些索引需全量重枚举。 */\r\n readonly degradedRules: readonly string[];\r\n /** 本轮 I4 体检的逐规则结论(XJ-04 第 3 步 / D-10)。 */\r\n readonly invariants: DeltaInvariantReport;\r\n}\r\n\r\n/** {@link XuanjiAdapterPool.applyDelta} 的结果。 */\r\nexport interface PoolDeltaResult extends DeltaApplicationResult {\r\n /** delta 是否真的被应用到缓存的适配层(基线不匹配 / 尚未装载时为 false)。 */\r\n readonly applied: boolean;\r\n}\r\n\r\n/** 逐规则索引健康快照(integrity 报告的一行)。 */\r\nexport interface RuleIndexHealth {\r\n readonly name: string;\r\n /** PathIndex 的 O(1) 计数器(total 含 stale,与 I4 口径同源)。 */\r\n readonly counters: { total: number; stale: number };\r\n}\r\n\r\n/**\r\n * 图完整性报告(璇玑图白盒可观测层,cycle-132 / D-06 + D-10 剩余半边清偿)。\r\n *\r\n * ## 三条设计决定(每条都是取舍,不是缺省)\r\n *\r\n * 1. **只读水位优先,排水显式开启**。`drainChanges: true` 才调 `drainChangeLog()`——\r\n * 「报告顺手清空日志」会把内存问题换成数据丢失问题(仓库对这对代价的经典裁决,\r\n * 见 `graph-space.ts` 头注 N-1):默认只读时调用方拿到 pending/dropped 水位,\r\n * 真正的消费方(增量重放 / 审计)显式排水并自己拿走事件。\r\n * 2. **不跑 I1/I2**。它们是「覆盖型」不变量(每类边/每种角色至少出现在一条物化路径),\r\n * 而生产枚举带 `maxPaths` 截断——截断索引上跑 I1/I2 会把「少枚举了」误报成\r\n * 「索引坏了」(伪违规)。只跑无歧义的计数面(I4 已在 applyDelta 内线),\r\n * 覆盖型检查保留给测试 oracle。\r\n * 3. **休眠态必须显式说出**。生产路径(graph_path_enumerate / graph_cycles)用一次性\r\n * 内联索引,`rules` 注册面恒空 ⇒ 增量管道处于「正确但休眠」状态。报告里的\r\n * `incrementalIdle: true` 就是这个事实的白盒化——而不是让空数组冒充「一切健康」\r\n * (本仓库反复出现的「空集冒充全绿」形态)。\r\n */\r\nexport interface GraphIntegrityReport {\r\n /** 图空间是否装载过(未装载时其余字段全是零值,调用方以此区分「空图」与「没装载」)。 */\r\n readonly loaded: boolean;\r\n readonly graph: { nodes: number; edges: number; version: number };\r\n /**\r\n * 变更日志水位:`pending` = 未消费事件数(≤ MAX_CHANGE_LOG);`dropped` = 因上界被\r\n * 覆盖的最旧事件累计数(drain 不清零);`drained` = 本次调用排走的条数,\r\n * **仅在 `drainChanges: true` 时非 null**(只读调用恒 null,与「没排」区分)。\r\n */\r\n readonly changeLog: { pending: number; dropped: number; drained: number | null };\r\n /** 已注册规则的健康行(注册序)。生产休眠态下为空数组。 */\r\n readonly rules: readonly RuleIndexHealth[];\r\n /**\r\n * 增量管道是否休眠(已装载但零注册规则 ⇒ 索引全部一次性、无增量维护面)。\r\n * `loaded: false` 时恒 false——「没装载」不冒充「休眠」。\r\n */\r\n readonly incrementalIdle: boolean;\r\n}\r\n\r\n/**\r\n * 本轮 `applyDelta` 跑到的 I4 不变量结论(XJ-04 第 3 步 / D-10)。\r\n *\r\n * 为什么把结论**返回**而不是只写日志:`degradedRules` 只能回答「最后有没有被降级」,\r\n * 无法区分「规则本来就没有条目(total=0)」与「有 40% 失效但阈值仍放行」。\r\n * 两者对调用方的意义完全不同,而 `InvariantResult.detail` 里恰好带着\r\n * `stale/total = 3/10 = 0.300` 这种可直接内省的事实。\r\n */\r\nexport interface DeltaInvariantReport {\r\n /** 规则名 → I4 结论(按规则首次注册序)。 */\r\n readonly byRule: ReadonlyMap<string, InvariantResult>;\r\n /** I4 判为不达标的规则名(它们同时也是 elapsed 的 `degradedRules` 成员)。 */\r\n readonly violated: readonly string[];\r\n}\r\n\r\n/** 未应用 delta 的固定返回(不可变,避免调用方误改共享对象)。 */\r\nconst DELTA_SKIPPED: PoolDeltaResult = Object.freeze({\r\n applied: false,\r\n addedPaths: 0,\r\n stalePaths: 0,\r\n degradedRules: Object.freeze([]) as readonly string[],\r\n // 没跑过就没有结论:空 map 而不是「全 ok」,否则调用方无法区分\r\n // 「体检通过」与「根本没体检」(本项目反复出现的「空集冒充全绿」形态)。\r\n invariants: Object.freeze({\r\n byRule: new Map<string, InvariantResult>(),\r\n violated: Object.freeze([]) as readonly string[],\r\n }) as DeltaInvariantReport,\r\n});\r\n\r\n/** 空枚举结果的固定返回。 */\r\nconst EMPTY_PATH_RESULT: PathEnumerationResult = Object.freeze({\r\n paths: [] as string[][],\r\n truncated: false,\r\n total: 0,\r\n});\r\n\r\n/** 一次装载的结构戳:只用于**快路径**,真正的等价判定见 `diffAgainstSpace`。 */\r\ninterface SyncStamp {\r\n readonly timestamp: number;\r\n readonly nodeCount: number;\r\n readonly edgeCount: number;\r\n}\r\n\r\nfunction nodeAttrs(node: GraphNode): NodeAttrs {\r\n return {\r\n label: node.label,\r\n role: node.type,\r\n tags: node.tags,\r\n filePath: node.filePath,\r\n isExported: node.properties?.isExported === true || node.properties?.is_exported === 1,\r\n layer: node.layer,\r\n };\r\n}\r\n\r\nfunction xjEdge(edge: GraphEdge): XJEdge {\r\n return {\r\n id: edge.id,\r\n source: edge.source,\r\n target: edge.target,\r\n type: edge.type,\r\n ...(edge.weight === undefined ? {} : { weight: edge.weight }),\r\n };\r\n}\r\n\r\n/** 图空间里已存的属性与新节点的属性是否一致(不一致就不能走增量:属性参与规则谓词)。 */\r\nfunction attrsEqual(stored: NodeAttrs, next: NodeAttrs): boolean {\r\n if (\r\n stored.label !== next.label ||\r\n stored.role !== next.role ||\r\n stored.filePath !== next.filePath ||\r\n stored.isExported !== next.isExported ||\r\n stored.layer !== next.layer\r\n ) {\r\n return false;\r\n }\r\n const a = stored.tags ?? [];\r\n const b = next.tags ?? [];\r\n if (a.length !== b.length) return false;\r\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\r\n return true;\r\n}\r\n\r\n/** 结构戳;没有可用 timestamp 时返回 null(快路径失效,只能走结构比较)。 */\r\nfunction stampOf(data: SyncGraphInput): SyncStamp | null {\r\n const timestamp = data.metadata?.timestamp;\r\n if (typeof timestamp !== 'number' || !Number.isFinite(timestamp)) return null;\r\n return { timestamp, nodeCount: data.nodes.length, edgeCount: data.edges.length };\r\n}\r\n\r\n/**\r\n * 与当前图空间的三向结构比较结果:\r\n * - `identical`:逐节点属性 + 逐边三元组完全一致;\r\n * - `additive`:旧节点/边全都还在且属性未变,只是多出了新节点/新边;\r\n * - `incompatible`:删除了节点或边、或有关键属性变化 —— 只能全量重建。\r\n */\r\ntype SpaceDiff =\r\n | { kind: 'identical' }\r\n | { kind: 'additive'; addedNodes: GraphNode[]; addedEdges: GraphEdge[] }\r\n | { kind: 'incompatible' };\r\n\r\n/**\r\n * 结点集合与边集合的图空间视图。\r\n *\r\n * ## XJ-04 第 3 步 / D-10:`InvariantChecker` 在本层的接线点(cycle-111)\r\n *\r\n * `applyDelta` 末尾对**每条已注册规则**跑 I4(stale 比例),不达标即入 `degradedRules`\r\n * 并清空该索引。这不是「为了花掉一个死符号」的仪式:接线前必须先修一条真缺陷——\r\n *\r\n * **删节点从来没有在索引上留下任何痕迹**。旧实现只做 `space.removeNode(id)`,而\r\n * `removeNode` 内部删掉的相邻边**不经过** `dropEdge`,于是 `index.counters.stale` 纹丝不动。\r\n * 实测(3 节点 a→b→c、物化 2 条路径后删 b):`counters = {total: 2, stale: 0}`,\r\n * 而索引里两条条目都指向已不存在的 b。I4 读的正是这两个计数,所以它会对一个**全线失效**\r\n * 的索引给出 `ok: true`——分母含 stale 却看不出失效,与 `incremental.ts` 里\r\n * 「用过滤后的 live 当分母,于是 0% 失效」是同一个坑的镜像。\r\n *\r\n * 因此这里的删节点分支改成:先按真实邻接**逐边走 `dropEdge`**(与删边同一条 stale 通道),\r\n * 再删节点。I4 随后读到的是全量口径(`counters.total` 做分母,含已 stale 的条目),\r\n * 与 `onEdgeRemovedDetailed` 同源。\r\n *\r\n * 与 `onEdgeRemovedDetailed` 的关系要说清楚,否则会觉得这道检查是重复的:两者阈值同为\r\n * `0.3`,在**纯删边**的 delta 上判定等价;I4 真正多覆盖的是「删/改节点」以及\r\n * 「一次 delta 里多种变更叠加后的终态」——那时的失效不是任何单条边删除的结论。\r\n */\r\nexport class XuanjiAdapter {\r\n private space = new GraphSpace();\r\n /** 已注册规则名 → 索引(增量维护)。 */\r\n private readonly indexes = new Map<string, PathIndex>();\r\n private readonly rules = new Map<string, PathRule>();\r\n /** 是否装载过图(首次装载必须全量,不能只靠 delta 拼出来)。 */\r\n private loaded = false;\r\n /** 最近一次「适配层 == 某份图数据」的结构戳;applyDelta 后必然作废。 */\r\n private lastSyncStamp: SyncStamp | null = null;\r\n /** 最近一次 `applyDelta` 的 I4 结论(见 {@link lastInvariantReport})。 */\r\n private lastInvariants: DeltaInvariantReport | null = null;\r\n /** I4 的阈值。缺省 `thetaStale` 与增量引擎的 `DEFAULT_THETA_STALE` 取同一个常量。 */\r\n private readonly invariantThresholds: InvariantThresholds;\r\n\r\n /**\r\n * @param thresholds I4/I3 阈值;`thetaStale` 缺省复用 `DEFAULT_THETA_STALE`(0.3)。\r\n * 复用而非另写字面量:两处阈值一旦分叉,就会出现「增量引擎说该重建、不变量说还健康」\r\n * 的自相矛盾,而两者都自称是 stale 比例的判据。\r\n */\r\n constructor(thresholds: InvariantThresholds = {}) {\r\n this.invariantThresholds = {\r\n thetaStale: thresholds.thetaStale ?? DEFAULT_THETA_STALE,\r\n ...(thresholds.thetaDepth === undefined ? {} : { thetaDepth: thresholds.thetaDepth }),\r\n };\r\n }\r\n\r\n /** 图空间是否已装载过。未装载时 delta 无基线可依,池层必须拒绝应用。 */\r\n get isReady(): boolean {\r\n return this.loaded;\r\n }\r\n\r\n /**\r\n * 用完整图装载(重建图空间;已注册规则的索引全部清空)。\r\n *\r\n * 任何「无法证明与当前图等价」的数据都必须走这里(宁可慢不可错)。\r\n */\r\n loadGraph(data: SyncGraphInput): void {\r\n this.space = new GraphSpace();\r\n for (const node of data.nodes) this.space.addNode(node.id, nodeAttrs(node));\r\n for (const edge of data.edges) this.space.addEdge(xjEdge(edge));\r\n for (const index of this.indexes.values()) index.clear();\r\n this.loaded = true;\r\n this.lastSyncStamp = stampOf(data);\r\n }\r\n\r\n /**\r\n * 把适配层同步到 `data`,返回实际采取的同步方式(`'full' | 'delta' | 'none'`)。\r\n *\r\n * 判定顺序(每一步都要求「能证明等价」,否则落到 `'full'`):\r\n * 1. 从未装载 ⇒ `'full'`;\r\n * 2. 结构戳(timestamp + 规模)相同 ⇒ `'none'`(快路径);\r\n * 3. 与图空间逐节点属性、逐边三元组比较:\r\n * - 完全一致 ⇒ `'none'`(timestamp 变了但图没变,**不得**因缓存陈旧返回旧路径,\r\n * 这里因为等价所以复用是安全的);\r\n * - 纯增补 ⇒ 用一条真实 delta(新增节点/新增边)走 `applyDelta` ⇒ `'delta'`;\r\n * - 其余(删除 / 属性变化 / 悬空边)⇒ `loadGraph` ⇒ `'full'`。\r\n *\r\n * 返回值是给调用方与测试看的**判定证据**:拿不到 `'none'` 就说明每次都在重建。\r\n */\r\n syncGraph(data: SyncGraphInput): SyncGraphOutcome {\r\n if (!this.loaded) {\r\n this.loadGraph(data);\r\n return 'full';\r\n }\r\n\r\n const stamp = stampOf(data);\r\n if (stamp && this.lastSyncStamp && this.sameStamp(stamp, this.lastSyncStamp)) {\r\n return 'none';\r\n }\r\n\r\n const diff = this.diffAgainstSpace(data);\r\n if (diff.kind === 'identical') {\r\n // 结构相同 ⇒ 复用现有空间与索引是安全的;只刷新戳,避免下次再付一遍结构比较。\r\n this.lastSyncStamp = stamp;\r\n return 'none';\r\n }\r\n\r\n if (diff.kind === 'additive') {\r\n this.applyDelta({\r\n addedNodes: diff.addedNodes,\r\n removedNodes: [],\r\n modifiedNodes: [],\r\n addedEdges: diff.addedEdges,\r\n removedEdges: [],\r\n modifiedEdges: [],\r\n });\r\n // applyDelta 内部会作废 lastSyncStamp(空间已不再等于任何已知修订),这里重新钉住。\r\n this.lastSyncStamp = stamp;\r\n return 'delta';\r\n }\r\n\r\n this.loadGraph(data);\r\n return 'full';\r\n }\r\n\r\n /**\r\n * 增量应用 GraphDelta(璇玑图「改一字只重读经过该字的诗」)。\r\n *\r\n * - 删除边:库的 `onEdgeRemovedDetailed` 只把经过该边的物化路径标记 stale(O(k)),不重建索引;\r\n * - 新增边:`onEdgeAdded` 用「前缀 × 新边 × 后缀」物化新路径,长度分桶裁剪;\r\n * - 只有**已注册规则**才承担增量维护成本;\r\n * - XJ-04:某条规则的 stale 比例 > 0.3(或增量枚举超出工作预算)时,其索引被清空并记入\r\n * `degradedRules`,本次 delta 内不再向它增量写入——留一个「空索引」比留一个「看起来完整、\r\n * 实际只含新边路径」的半成品安全(查询层据此回退全量枚举)。\r\n */\r\n applyDelta(delta: GraphDelta): DeltaApplicationResult {\r\n let addedPaths = 0;\r\n let stalePaths = 0;\r\n const degraded = new Set<string>();\r\n\r\n // 增量之后图空间不再等于任何已知修订:快路径戳必须作废,否则 syncGraph 会把旧戳当最新。\r\n this.lastSyncStamp = null;\r\n\r\n const retire = (name: string, index: PathIndex): void => {\r\n index.clear();\r\n degraded.add(name);\r\n };\r\n\r\n const dropEdge = (ej: XJEdge): void => {\r\n this.space.removeEdge(ej);\r\n for (const [name, index] of this.indexes) {\r\n if (degraded.has(name)) continue;\r\n const rule = this.rules.get(name);\r\n if (!rule) continue;\r\n const outcome = onEdgeRemovedDetailed(ej, rule, index);\r\n stalePaths += outcome.newlyStale;\r\n if (outcome.needsRebuild) retire(name, index);\r\n }\r\n };\r\n\r\n const addEdge = (ej: XJEdge): void => {\r\n this.space.addEdge(ej);\r\n for (const [name, index] of this.indexes) {\r\n if (degraded.has(name)) continue;\r\n const rule = this.rules.get(name);\r\n if (!rule) continue;\r\n addedPaths += onEdgeAdded(ej, rule, this.space, index).length;\r\n // 预算降级时 onEdgeAdded 已自行 clear();这里把它记入 degradedRules 并停止后续写入。\r\n // XJ-11:读**该索引**的实例态(不是模块级全局),否则两条规则 / 两个仓库并发时\r\n // A 的降级会被 B 的正常调用清掉。\r\n if (wasLastIncrementalDegraded(index)) degraded.add(name);\r\n }\r\n };\r\n\r\n // 删节点必须**先按真实邻边逐条走 dropEdge**:GraphSpace.removeNode 内部删掉的相邻边\r\n // 不经过上面那条 stale 通道(实测 counters.stale 纹丝不动),于是索引会留下指向已删\r\n // 节点的条目而 I4 仍报健康。取删除前的邻接快照,逐条构造出等价的 XJEdge 再走 dropEdge。\r\n for (const node of delta.removedNodes) {\r\n for (const ej of this.incidentEdges(node.id)) dropEdge(ej);\r\n this.space.removeNode(node.id);\r\n }\r\n for (const node of delta.addedNodes) this.space.addNode(node.id, nodeAttrs(node));\r\n for (const node of delta.modifiedNodes) this.space.addNode(node.id, nodeAttrs(node));\r\n\r\n for (const edge of delta.removedEdges) dropEdge(xjEdge(edge));\r\n\r\n for (const edge of delta.modifiedEdges) {\r\n const ej = xjEdge(edge);\r\n dropEdge(ej);\r\n addEdge(ej);\r\n }\r\n\r\n for (const edge of delta.addedEdges) addEdge(xjEdge(edge));\r\n\r\n // XJ-04 第 3 步:所有变更落地后,用 I4 对自己的索引做一次体检。\r\n // 放在最后而不是每条边上:I4 问的是「这条规则现在还能不能用」,那是 delta 的**终态**。\r\n const invariants = this.checkStaleInvariant(degraded);\r\n\r\n return { addedPaths, stalePaths, degradedRules: [...degraded], invariants };\r\n }\r\n\r\n /** 最近一次 `applyDelta` 的 I4 结论(未跑过时为 null)。 */\r\n get lastInvariantReport(): DeltaInvariantReport | null {\r\n return this.lastInvariants;\r\n }\r\n\r\n /**\r\n * 对每条已注册规则跑 I4,把不达标者清空并记入 `degraded`。\r\n *\r\n * 分母口径:`InvariantChecker.checkI4` 内部用 `index.counters.total`——**含已 stale 的条目**,\r\n * 与 `incremental.ts` 的 `onEdgeRemovedDetailed` 完全同源。用 `liveCount()` 当分母是这一族\r\n * 缺陷的复发形态(一个全部失效的索引会被算成 0% 失效,阈值永不触发)。\r\n *\r\n * 已被其它原因降级(stale 超阈 / 预算超限)的规则不重复检查:它们的索引已是空的\r\n * (`retire` 里 `clear()` 过,total=0 ⇒ 比率按 0 处理),再跑一次只会得到一条\r\n * 无信息的 ok:true,反而稀释了 `byRule` 的可读性。\r\n */\r\n private checkStaleInvariant(degraded: Set<string>): DeltaInvariantReport {\r\n const byRule = new Map<string, InvariantResult>();\r\n const violated: string[] = [];\r\n const checker = new InvariantChecker(this.invariantThresholds);\r\n for (const [name, index] of this.indexes) {\r\n if (degraded.has(name)) continue;\r\n const result = checker.checkI4(index);\r\n byRule.set(name, result);\r\n if (!result.ok) {\r\n // 与 stale 超阈走同一个动作:留空索引好过留半成品。\r\n index.clear();\r\n degraded.add(name);\r\n violated.push(name);\r\n }\r\n }\r\n this.lastInvariants = { byRule, violated };\r\n return this.lastInvariants;\r\n }\r\n\r\n /**\r\n * 某个节点当前的**全部**相邻边快照(出边 + 入边),用于「删节点前先逐条失效」。\r\n *\r\n * 必须是快照:`dropEdge` 会改动邻接表,边遍历边删会漏掉元素(与 `GraphSpace.removeNode`\r\n * 里 `Array.from` 的理由相同)。\r\n */\r\n private incidentEdges(nodeId: string): XJEdge[] {\r\n const types = this.space.edgeTypes();\r\n const edges: XJEdge[] = [];\r\n for (const target of this.space.outNeighbors(nodeId, types)) {\r\n for (const type of types) {\r\n const edge = this.space.getEdge(nodeId, target, type);\r\n if (edge) edges.push(edge);\r\n }\r\n }\r\n for (const source of this.space.inNeighbors(nodeId, types)) {\r\n for (const type of types) {\r\n const edge = this.space.getEdge(source, nodeId, type);\r\n if (edge) edges.push(edge);\r\n }\r\n }\r\n return edges;\r\n }\r\n\r\n /**\r\n * 当前图空间是否**包含** delta 的基线(删除/修改的条目都在,新增边的端点可解析)。\r\n *\r\n * 用于池层决定「这条 delta 能不能安全应用到缓存的适配层」:delta 只对它被计算出来的\r\n * 那个修订有意义,基线不匹配时应用会把两版图混在一起,因此宁可丢弃缓存重建。\r\n */\r\n canApplyDelta(delta: GraphDelta): boolean {\r\n for (const node of delta.removedNodes) if (!this.space.hasNode(node.id)) return false;\r\n for (const node of delta.modifiedNodes) if (!this.space.hasNode(node.id)) return false;\r\n for (const edge of delta.removedEdges) {\r\n if (!this.space.hasEdge(edge.source, edge.target, edge.type)) return false;\r\n }\r\n for (const edge of delta.modifiedEdges) {\r\n if (!this.space.hasEdge(edge.source, edge.target, edge.type)) return false;\r\n }\r\n const addedNodeIds = new Set<string>(delta.addedNodes.map((node) => node.id));\r\n for (const edge of [...delta.addedEdges, ...delta.modifiedEdges]) {\r\n if (!this.space.hasNode(edge.source) && !addedNodeIds.has(edge.source)) return false;\r\n if (!this.space.hasNode(edge.target) && !addedNodeIds.has(edge.target)) return false;\r\n }\r\n return true;\r\n }\r\n\r\n /**\r\n * 用 `validateRule` 校验一条规则(spec §3.4),返回原始 errors / warnings。\r\n *\r\n * XJ-05 的**公共校验面**:两处调用者——\r\n * 1. {@link registerRule}(程序化注册闸门,errors 非空则不注册);\r\n * 2. {@link assertRule}(内联读法闸门,`findPathsBetween` / `cycles` 每次调用都走它)。\r\n *\r\n * 刻意做成 public 而不是 private:验收断言要能在运行时证明「生产枚举路径上\r\n * 确实执行了校验」,公共方法可以被 spy,私有方法只能靠猜。\r\n *\r\n * @param graph 用于取 Σ 与 |V| 的图空间(缺省为本适配层已装载的空间)\r\n */\r\n validateRule(rule: PathRule, graph?: GraphSpace): RuleValidation {\r\n const space = graph ?? this.space;\r\n return validateRuleSpec(rule, space.edgeTypes(), space.nodeCount);\r\n }\r\n\r\n /**\r\n * 注册一条路径规则(索引惰性创建,与 RuleRegistry 语义一致)。\r\n *\r\n * XJ-05:errors 非空 ⇒ 规则不进入规则表、不建索引,`index` 返回 null(warnings 原样透传)。\r\n * 已存在的同名规则不会被非法注册覆盖。\r\n *\r\n * 如实说明:**`registerRule` 目前在 `src/` 内没有生产调用点**(`graph_path_enumerate` /\r\n * `graph_cycles` 用的是内联读法规则,见 {@link assertRule}),它是保留给宿主程序化注册\r\n * 与测试的 API。生产上的必然校验关口是 `assertRule`,不要把它当成「唯一入口」。\r\n *\r\n * @param graph 用于校验的图空间(缺省为本适配层已装载的空间)\r\n */\r\n registerRule(rule: PathRule, graph?: GraphSpace): RuleRegistration {\r\n const validation = this.validateRule(rule, graph);\r\n if (validation.errors.length > 0) return { index: null, validation };\r\n\r\n this.rules.set(rule.name, rule);\r\n let index = this.indexes.get(rule.name);\r\n if (!index) {\r\n index = new PathIndex();\r\n this.indexes.set(rule.name, index);\r\n }\r\n return { index, validation };\r\n }\r\n\r\n getRule(name: string): PathRule | undefined {\r\n return this.rules.get(name);\r\n }\r\n\r\n getIndex(name: string): PathIndex | undefined {\r\n return this.indexes.get(name);\r\n }\r\n\r\n /**\r\n * 枚举「从 source 到 target」的简单路径(璇玑图的一种「读法」)。\r\n * 每次调用使用独立索引——只读查询,不承担增量维护责任。\r\n *\r\n * XJ-03:返回值经库的 {@link readPathPage} 组装,因此带 `labels`(Ω 惰性输出,\r\n * 只对当前页求值)与 `nextCursor`(分页)。不传 `cursor`/`pageSize` 时\r\n * `paths` / `truncated` / `total` 三个旧字段与改造前逐元素一致。\r\n */\r\n findPathsBetween(\r\n source: string,\r\n target: string,\r\n options: PathEnumerationOptions = {},\r\n ): PathEnumerationResult {\r\n const minHops = Math.max(1, options.minHops ?? 1);\r\n const maxHops = Math.max(minHops, options.maxHops ?? 5);\r\n const limit = Math.max(1, options.limit ?? 20);\r\n const cursor = Math.max(0, Math.floor(options.cursor ?? 0));\r\n const pageSize = Math.max(1, Math.floor(options.pageSize ?? limit));\r\n\r\n if (!this.space.hasNode(source) || !this.space.hasNode(target)) return EMPTY_PATH_RESULT;\r\n\r\n // 读法只能走图里真实存在的边类型:请求了图中没有的类型(如 'extend')本来就走不通,\r\n // 先裁剪掉,既保持结果不变,也让这条内联规则能通过 registerRule 同源的校验闸门。\r\n const edgeTypes = this.presentEdgeTypes(options.edgeTypes ?? DEFAULT_PATH_EDGE_TYPES);\r\n if (edgeTypes.size === 0) return EMPTY_PATH_RESULT;\r\n\r\n const rule: PathRule = {\r\n name: `between:${source}->${target}`,\r\n version: 1,\r\n start: (id) => id === source,\r\n end: (id) => id === target,\r\n edgeTypes,\r\n lMin: minHops,\r\n lMax: maxHops,\r\n simple: true,\r\n normalize: 'none',\r\n output: (nodes) => nodes,\r\n };\r\n this.assertRule(rule);\r\n\r\n const index = new PathIndex();\r\n // 多枚举一条用于区分「恰好 limit 条」与「被截断」;分页时窗口终点是 cursor + pageSize。\r\n // truncation 透传(cycle-112 继承项 A):`'top-k-weight'` 时库会先收齐全部候选再按\r\n // 权重取前 k —— `maxPaths` 语义不变(仍是返回条数上限),只是**选择策略**不同。\r\n const entries = enumerate(this.space, rule, [source], index, {\r\n maxPaths: cursor + pageSize + 1,\r\n ...(options.truncation ? { truncation: options.truncation } : {}),\r\n });\r\n const truncated = entries.length > cursor + pageSize;\r\n const page = readPathPage(rule, entries, { cursor, pageSize });\r\n return {\r\n paths: page.entries.map((entry) => [...entry.nodes]),\r\n truncated,\r\n total: page.entries.length,\r\n labels: page.labels,\r\n nextCursor: page.nextCursor,\r\n };\r\n }\r\n\r\n /**\r\n * 枚举图中的简单环(闭合路径 + Booth 规范化去重)。\r\n * 同一环从不同节点起读只出现一次——「同一首诗从不同字起读仍是同一首」。\r\n *\r\n * XJ-03:与 {@link findPathsBetween} 同源,返回值带 `labels` / `nextCursor`。\r\n */\r\n cycles(options: CycleEnumerationOptions = {}): PathEnumerationResult {\r\n const maxLength = Math.max(1, options.maxLength ?? 6);\r\n const limit = Math.max(1, options.limit ?? 100);\r\n const cursor = Math.max(0, Math.floor(options.cursor ?? 0));\r\n const pageSize = Math.max(1, Math.floor(options.pageSize ?? limit));\r\n\r\n const edgeTypes = this.presentEdgeTypes(options.edgeTypes ?? DEFAULT_PATH_EDGE_TYPES);\r\n if (edgeTypes.size === 0) return EMPTY_PATH_RESULT;\r\n\r\n const rule: PathRule = {\r\n name: 'cycles',\r\n version: 1,\r\n start: () => true,\r\n edgeTypes,\r\n lMin: 1,\r\n lMax: maxLength,\r\n simple: true,\r\n closed: true,\r\n normalize: 'canonical',\r\n output: (nodes) => nodes,\r\n };\r\n this.assertRule(rule);\r\n\r\n const index = new PathIndex();\r\n const starts = [...this.space.nodeIds()];\r\n const entries = enumerate(this.space, rule, starts, index, { maxPaths: cursor + pageSize + 1 });\r\n const truncated = entries.length > cursor + pageSize;\r\n const page = readPathPage(rule, entries, { cursor, pageSize });\r\n return {\r\n paths: page.entries.map((entry) => [...entry.nodes]),\r\n truncated,\r\n total: page.entries.length,\r\n labels: page.labels,\r\n nextCursor: page.nextCursor,\r\n };\r\n }\r\n\r\n /**\r\n * 按**路径模式 ρ**(边类型序列正则)枚举路径——cycle-134 新增的读法。\r\n *\r\n * 与 {@link findPathsBetween} 的分工(这是本方法存在的唯一理由):\r\n * - `findPathsBetween` 的 `edgeTypes` 是**扁平集合**:「每一步可以走这些类型之一」。\r\n * 它无法表达「第一步 call、第二步 import」这种**顺序**约束。\r\n * - 本方法的 ρ 是**序列正则**:`call+ . import? . dependency` 这类跨视图路径\r\n * 只有它能表达。研究方案 §4 把查询形式化为 `P = (v_s, ρ, v_t)`,\r\n * 这是那个形式化第一次有真实实现。\r\n *\r\n * 实现上完全复用既有引擎(`compilePathRule` → `enumerate`),因此免费获得\r\n * 分页游标、Booth 规范化去重、`maxPaths` 截断与 `top-k-weight` 排序。\r\n *\r\n * ## 错误处理:ρ 语法错误**抛错**而不是返回空结果\r\n *\r\n * 空结果与语法错误在返回面上必须可区分。若把 `PathPatternError` 吞成「零条路径」,\r\n * 模型会以为「这个模式在这个仓库里没有匹配」——而真相是「模式写错了」。\r\n * 这是本仓反复出现的「空集冒充结论」形态,故这里让错误上抛,由工具层\r\n * 用 {@link formatPatternError} 渲染成带 `^` 指示的诊断。\r\n *\r\n * @param pattern ρ 文本(见 `path-pattern.ts` 的语法说明)\r\n * @param options 起点/终点/跳数窗/分页\r\n * @throws {PathPatternError} ρ 语法错误\r\n */\r\n pathsByPattern(pattern: string, options: PatternEnumerationOptions = {}): PatternEnumerationResult {\r\n const compiled = compilePathPattern(pattern);\r\n const starts = this.resolvePatternStarts(options);\r\n\r\n /**\r\n * ρ 里出现、但**当前图上一概不存在**的边类型 ⇒ 直接返回零结果。\r\n *\r\n * 为什么不是「裁剪掉缺失类型继续枚举」:裁剪会把 `call . deadtype` 静默\r\n * 降级成 `call`,调用方拿到的路径**不满足它写的 ρ**——这是本仓最忌讳的\r\n * 「静默近似」。为什么也不是「抛错」:ρ 语法本身合法,只是这张图上没有\r\n * 那种关系,这是**合法的零结果**(与 `graph_cycles` 在没有环的图上返回空同族)。\r\n *\r\n * 两者必须在返回面上可区分:语法错 ⇒ 抛 `PathPatternError`(工具层渲染成\r\n * 带 ^ 的诊断);关系不存在 ⇒ 零结果 + `paths: []`。\r\n *\r\n * cycle-135 把「缺失」再拆成两半(本仓「拼错 ≠ 缺失」纪律的直接应用):\r\n * - `unknownEdgeTypes`:**不在 Σ** 里(`calls` / `Call`)——用户写错了;\r\n * - `absentFromGraph`:在 Σ 里、但这张图没有这种边——合法的零结果。\r\n * 两者都产出空路径,但诊断含义不同,因此分别暴露。\r\n *\r\n * 这同时绕开 `assertRule` 的 Σ_R ⊆ Σ 静态校验(它按「边类型必须出现在图里」\r\n * 判非法规则)——那个校验适用于 `findPathsBetween` 的手写 edgeTypes,\r\n * 对本方法会把「合法的零结果」误判成「非法规则」。\r\n */\r\n const present = this.space.edgeTypes();\r\n const unknownEdgeTypes = [...compiled.edgeTypes]\r\n .filter((type) => !KNOWN_ADAPTER_EDGE_TYPES.has(type as AdapterEdgeType))\r\n .sort();\r\n const absentFromGraph = [...compiled.edgeTypes]\r\n .filter((type) => KNOWN_ADAPTER_EDGE_TYPES.has(type as AdapterEdgeType) && !present.has(type))\r\n .sort();\r\n if (unknownEdgeTypes.length > 0 || absentFromGraph.length > 0) {\r\n return {\r\n paths: [], truncated: false, total: 0, nextCursor: null,\r\n pattern, patternEdgeTypes: [...compiled.edgeTypes].sort(), stateCount: compiled.stateCount,\r\n unknownEdgeTypes,\r\n };\r\n }\r\n\r\n // ρ 的长度区间与调用方窗口取交集后,若为空窗口则直接返回空(不必进枚举)。\r\n const minHops = Math.max(0, options.minHops ?? 0);\r\n const maxHops = Math.max(minHops, options.maxHops ?? 8);\r\n if (compiled.maxEdges < minHops || compiled.minEdges > maxHops) {\r\n return {\r\n paths: [], truncated: false, total: 0, nextCursor: null,\r\n pattern, patternEdgeTypes: [...compiled.edgeTypes].sort(), stateCount: compiled.stateCount,\r\n unknownEdgeTypes,\r\n };\r\n }\r\n\r\n const cursor = Math.max(0, Math.floor(options.cursor ?? 0));\r\n const pageSize = Math.max(1, Math.floor(options.pageSize ?? options.limit ?? 20));\r\n\r\n const rule = compilePathRule(pattern, {\r\n name: `pattern:${pattern}`,\r\n minHops,\r\n maxHops,\r\n simple: options.simple ?? true,\r\n ...(options.closed === true ? { closed: true } : {}),\r\n ...(options.startNode === undefined ? {} : { start: (id: string) => id === options.startNode }),\r\n ...(options.endNode === undefined ? {} : { end: (id: string) => id === options.endNode }),\r\n // 闭合 ρ 的规范化必须与 `cycles()` 一致(cycle-134 实测缺陷修复)。\r\n //\r\n // 缺陷形态:`closed: true` 但 `normalize` 走 `compilePathRule` 的缺省 `'none'`,\r\n // 于是三节点环 `a→b→c→a` 会以**三个起点各报一次**(`[a,b,c,a]` / `[b,c,a,b]` /\r\n // `[c,a,b,c]`)——同一首诗从不同字起读被当成三首。`cycles()` 传 `'canonical'`\r\n // 所以只报一条。两者对「同一个环有几条」给出了不同答案,而这是**同一个库**。\r\n //\r\n // 只在闭合时改为 canonical:开放路径的 `normalize: 'none'` 是刻意的\r\n // (`findPathsBetween` 也如此)——开放路径的「起点」有语义(从谁出发),\r\n // 旋转等价不成立,规范化会改变结果集。\r\n ...(options.closed === true ? { normalize: 'canonical' as const } : {}),\r\n });\r\n\r\n // 校验闸门:与 findPathsBetween 同源(不是另写一份校验)。\r\n this.assertRule(rule);\r\n\r\n const index = new PathIndex();\r\n const entries = enumerate(this.space, rule, starts, index, {\r\n maxPaths: cursor + pageSize + 1,\r\n ...(options.truncation ? { truncation: options.truncation } : {}),\r\n });\r\n const truncated = entries.length > cursor + pageSize;\r\n const page = readPathPage(rule, entries, { cursor, pageSize });\r\n return {\r\n paths: page.entries.map((entry) => [...entry.nodes]),\r\n truncated,\r\n total: page.entries.length,\r\n labels: page.labels,\r\n nextCursor: page.nextCursor,\r\n pattern,\r\n patternEdgeTypes: [...compiled.edgeTypes].sort(),\r\n stateCount: compiled.stateCount,\r\n unknownEdgeTypes,\r\n };\r\n }\r\n\r\n /**\r\n * ρ 枚举的起点集合。\r\n *\r\n * 缺省「全部节点」是**故意**的:ρ 本身已表达类型序列,起点常常未知\r\n * (「哪些地方存在 call→import 这条链」)。指定 `startNode` 时只从该点出发\r\n * ——注意这既缩小了搜索,也改变了语义(不是「过滤结果」而是「缩小起点」)。\r\n */\r\n private resolvePatternStarts(options: PatternEnumerationOptions): string[] {\r\n if (options.startNode !== undefined) {\r\n return this.space.hasNode(options.startNode) ? [options.startNode] : [];\r\n }\r\n // simple 且无界 ρ 时全节点枚举可能很贵;调用方用 `options.starts` 显式缩小\r\n // (工具层默认传「前 N 个节点」以外的做法会静默改语义,故不设隐式上限)。\r\n if (options.starts !== undefined) {\r\n return [...options.starts].filter((id) => this.space.hasNode(id));\r\n }\r\n return [...this.space.nodeIds()];\r\n }\r\n\r\n /** 从一批起点沿边类型可正向到达的节点集合(BFS,与 DeadCodeAnalyzer 语义一致)。 */\r\n reachableFrom(sources: Iterable<string>, edgeTypes?: readonly AdapterEdgeType[]): Set<string> { const types = new Set<AdapterEdgeType>(edgeTypes ?? DEFAULT_PATH_EDGE_TYPES);\r\n const visited = new Set<string>();\r\n const queue: string[] = [];\r\n for (const source of sources) {\r\n if (!this.space.hasNode(source) || visited.has(source)) continue;\r\n visited.add(source);\r\n queue.push(source);\r\n }\r\n let head = 0;\r\n while (head < queue.length) {\r\n const current = queue[head++]!;\r\n for (const next of this.space.outNeighbors(current, types)) {\r\n if (visited.has(next)) continue;\r\n visited.add(next);\r\n queue.push(next);\r\n }\r\n }\r\n return visited;\r\n }\r\n\r\n /** 图空间只读视图(测试与不变量检查用)。 */\r\n get graph(): GraphSpace {\r\n return this.space;\r\n }\r\n\r\n stats(): { nodes: number; edges: number; version: number } {\r\n return { nodes: this.space.nodeCount, edges: this.space.edgeCount, version: this.space.version };\r\n }\r\n\r\n /**\r\n * 图完整性报告(D-06 清偿:`drainChangeLog` 的**显式**生产消费点;D-10 剩余半边:\r\n * 注册面/计数器/日志水位统一白盒面)。语义契约见 {@link GraphIntegrityReport} 头注\r\n * 的三条设计决定。\r\n */\r\n integrity(options: { drainChanges?: boolean } = {}): GraphIntegrityReport {\r\n const rules = [...this.indexes.entries()].map(([name, index]) => ({\r\n name,\r\n counters: { total: index.counters.total, stale: index.counters.stale },\r\n }));\r\n const incrementalIdle = this.loaded && this.indexes.size === 0;\r\n if (options.drainChanges === true) {\r\n // 排水在快照之后:先读水位(pending 是排水前的口径),再取走事件。\r\n const drained = this.space.drainChangeLog();\r\n return {\r\n loaded: this.loaded,\r\n graph: { nodes: this.space.nodeCount, edges: this.space.edgeCount, version: this.space.version },\r\n changeLog: {\r\n pending: 0,\r\n dropped: this.space.droppedChangeEvents,\r\n drained: drained.length,\r\n },\r\n rules,\r\n incrementalIdle,\r\n };\r\n }\r\n return {\r\n loaded: this.loaded,\r\n graph: { nodes: this.space.nodeCount, edges: this.space.edgeCount, version: this.space.version },\r\n changeLog: {\r\n pending: this.space.pendingChangeEvents,\r\n dropped: this.space.droppedChangeEvents,\r\n drained: null,\r\n },\r\n rules,\r\n incrementalIdle,\r\n };\r\n }\r\n\r\n /**\r\n * 内联读法规则的**必然执行**的校验关口:构造错误(lMin/lMax/normalize 非法等)绝不能静默枚举。\r\n *\r\n * 与 {@link registerRule} 共用同一个 {@link validateRule}。两条内联读法的合法输入\r\n * (已把 edgeTypes 裁剪到图中存在者)不会触发它,但**调用本身是每次枚举都执行的**——\r\n * 验收断言依赖这一点(spy `validateRule` 即可证明),并有一个用例用非法参数\r\n * (非有限的 minHops/maxLength)证明它确实会拒绝,而不是恒真。\r\n */\r\n private assertRule(rule: PathRule): void {\r\n const validation = this.validateRule(rule);\r\n if (validation.errors.length > 0) {\r\n throw new RuleCompositionError(\r\n `invalid path rule \"${rule.name}\": ${validation.errors.join('; ')}`,\r\n );\r\n }\r\n }\r\n\r\n private presentEdgeTypes(requested: Iterable<AdapterEdgeType>): Set<AdapterEdgeType> {\r\n const present = this.space.edgeTypes();\r\n const result = new Set<AdapterEdgeType>();\r\n for (const type of requested) if (present.has(type)) result.add(type);\r\n return result;\r\n }\r\n\r\n private sameStamp(a: SyncStamp, b: SyncStamp): boolean {\r\n return (\r\n a.timestamp === b.timestamp &&\r\n a.nodeCount === b.nodeCount &&\r\n a.edgeCount === b.edgeCount\r\n );\r\n }\r\n\r\n /** 逐节点(含属性)与逐边(三元组)比较 `data` 与当前图空间。 */\r\n private diffAgainstSpace(data: SyncGraphInput): SpaceDiff {\r\n const knownNodes = new Set<string>();\r\n const addedNodes: GraphNode[] = [];\r\n for (const node of data.nodes) {\r\n const stored = this.space.getNodeAttrs(node.id);\r\n if (stored === undefined) {\r\n addedNodes.push(node);\r\n continue;\r\n }\r\n // 属性参与规则谓词(role / tags / isExported),变了就不能当作「旧节点还在」。\r\n if (!attrsEqual(stored, nodeAttrs(node))) return { kind: 'incompatible' };\r\n knownNodes.add(node.id);\r\n }\r\n // 图空间里还有 data 没提到的节点 ⇒ 这版图删了节点,只能全量重建。\r\n if (knownNodes.size !== this.space.nodeCount) return { kind: 'incompatible' };\r\n\r\n const knownEdgeKeys = new Set<string>();\r\n const addedEdges: GraphEdge[] = [];\r\n for (const edge of data.edges) {\r\n if (this.space.hasEdge(edge.source, edge.target, edge.type)) {\r\n knownEdgeKeys.add(`${edge.source}->${edge.target}->${edge.type}`);\r\n continue;\r\n }\r\n addedEdges.push(edge);\r\n }\r\n if (knownEdgeKeys.size !== this.space.edgeCount) return { kind: 'incompatible' };\r\n\r\n if (addedNodes.length === 0 && addedEdges.length === 0) return { kind: 'identical' };\r\n\r\n // 新增边必须能解析端点(否则增量后的图与全量装载的图不一致)。\r\n const addedNodeIds = new Set<string>(addedNodes.map((node) => node.id));\r\n for (const edge of addedEdges) {\r\n if (!this.space.hasNode(edge.source) && !addedNodeIds.has(edge.source)) {\r\n return { kind: 'incompatible' };\r\n }\r\n if (!this.space.hasNode(edge.target) && !addedNodeIds.has(edge.target)) {\r\n return { kind: 'incompatible' };\r\n }\r\n }\r\n return { kind: 'additive', addedNodes, addedEdges };\r\n }\r\n}\r\n\r\n/**\r\n * 按 repoId 缓存 {@link XuanjiAdapter} 的 LRU 池(XJ-02)。\r\n *\r\n * 存在的理由:`graph_path_enumerate` / `graph_cycles` 之前每次调用都 `new XuanjiAdapter()`\r\n * + `loadGraph()`,于是库里的增量引擎(`onEdgeAdded` / `onEdgeRemovedDetailed`)在生产上\r\n * 没有任何宿主入口。池化之后同一个图空间被复用,`syncGraph` 才有意义。\r\n *\r\n * 容量默认 2(同时挂两个仓库即可覆盖「当前工作区 + 刚切换的工作区」)。\r\n */\r\nexport class XuanjiAdapterPool {\r\n private readonly adapters = new Map<string, XuanjiAdapter>();\r\n private readonly capacity: number;\r\n\r\n constructor(capacity = 2) {\r\n this.capacity = Number.isFinite(capacity) ? Math.max(1, Math.floor(capacity)) : 2;\r\n }\r\n\r\n /** 取(必要时新建)某仓库的适配层;命中即刷新 LRU 顺序。 */\r\n get(repoId: string): XuanjiAdapter {\r\n const existing = this.adapters.get(repoId);\r\n if (existing) {\r\n // Map 保序:先删后插 = 移到 MRU 端。\r\n this.adapters.delete(repoId);\r\n this.adapters.set(repoId, existing);\r\n return existing;\r\n }\r\n const created = new XuanjiAdapter();\r\n this.adapters.set(repoId, created);\r\n while (this.adapters.size > this.capacity) {\r\n const oldest = this.adapters.keys().next().value;\r\n if (oldest === undefined) break;\r\n this.adapters.delete(oldest);\r\n }\r\n return created;\r\n }\r\n\r\n /**\r\n * 把一条真实 delta 应用到该仓库已缓存的适配层。\r\n *\r\n * 前置条件(任一不满足就**不应用**,绝不把两版图混在一起):\r\n * - 该仓库有缓存且已装载过图;\r\n * - `canApplyDelta` 认可这条 delta 的基线就在当前图空间里。\r\n *\r\n * 基线不匹配时直接丢弃缓存,让下一次 `get` 从零重建(正确性优先于命中率)。\r\n */\r\n applyDelta(repoId: string, delta: GraphDelta): PoolDeltaResult {\r\n const adapter = this.adapters.get(repoId);\r\n if (!adapter || !adapter.isReady) return DELTA_SKIPPED;\r\n if (!adapter.canApplyDelta(delta)) {\r\n this.invalidate(repoId);\r\n return DELTA_SKIPPED;\r\n }\r\n const result = adapter.applyDelta(delta);\r\n return { applied: true, ...result };\r\n }\r\n\r\n /**\r\n * 丢弃某仓库的派生状态(图空间 + 增量索引)。\r\n *\r\n * 用于「拿到了没有缓存基线的全新图」:此时适配层手里的可能是另一版修订,\r\n * 继续复用它虽然会被 `syncGraph` 的结构比较拦下,但先丢掉更直白也更省内存。\r\n */\r\n invalidate(repoId: string): void {\r\n this.adapters.delete(repoId);\r\n }\r\n\r\n /** 插件卸载时清空所有缓存(`createGraphTools` 的 dispose 调用)。 */\r\n dispose(): void {\r\n this.adapters.clear();\r\n }\r\n}\r\n","/**\n * git 变更频率(churn)的纯函数层。\n *\n * 研究报告 §六 #11(CodeScene)指出:现有热点分析**只有入度维度**\n * (`HotspotAnalyzer.ts` + `HotspotPanel.tsx` 显示 `h.indegree`),\n * 缺「变更频率 × 复杂度 = 热点分数」里的变更频率一侧。\n *\n * 本文件把「解析 git 输出」和「算热点分数」两件**没有副作用**的事放在 `src/shared/`,\n * 这样:① 宿主(`src/host/gitChurn.ts`)与浏览器端都能 import;② 单测不必真的跑 git;\n * ③ 「churn 缺失时退化为纯入度」这条关键不变量可以在纯函数层直接钉死。\n *\n * 设计约束(照抄本仓库已验证的三条教训):\n * - **退化默认值必须「拒绝」而不是「承诺」**:git 不可用 / 不是仓库时返回空 churn,\n * 而不是编造一个「所有文件都是 0 次」的结果——后者会让面板显示一份看起来正常的假数据。\n * - **热点分数在 churn 为 0 时必须等于入度**:否则 git 不可用的工作区里,\n * 热点排序会和升级前不一致,等于悄悄改变了既有行为。\n * - **路径归一化只有一个实现**:`normalizeChurnPath` 是两侧共用的唯一真源,\n * 宿主侧的 `git log` 输出和客户端的 `node.filePath` 都过它。\n */\n\n/** 一次 git churn 采集的结果。 */\nexport interface ChurnResult {\n /** 归一化文件路径 → 被多少个提交触及过。 */\n byFile: Record<string, number>;\n /** 本次统计覆盖的提交数。 */\n commitCount: number;\n /** 采集时的 HEAD;采集失败为 `null`。 */\n revision: string | null;\n /**\n * 是否被 `MAX_COMMITS` / `MAX_OUTPUT_BYTES` 截断。\n *\n * 刻意做成显式字段而不是「静默少算」:截断时面板必须能告诉用户\n * 「这份变更频率只覆盖最近 N 个提交」,否则一份被截断的统计看起来和完整统计一模一样。\n */\n truncated: boolean;\n /**\n * 采集**是否真的拿到了 git 数据**。\n *\n * `false` 表示 git 不可用 / 不是仓库 / 超时——此时 `byFile` 为空。\n * 面板据此显示「未获取到变更频率(仅按依赖关系排序)」,而不是假装 0 次变更。\n */\n available: boolean;\n /** 采集失败时的原因(人类可读);成功为 `null`。刻意保留在载荷里:静默失败无法排查。 */\n error: string | null;\n}\n\nexport const CHURN = {\n /** 最多统计多少个提交(超出则 `truncated = true`)。 */\n MAX_COMMITS: 2000,\n /** `git log` 输出上限(字节)。超过则 `truncated = true`,避免把巨型仓库的输出整体读进内存。 */\n MAX_OUTPUT_BYTES: 8 * 1024 * 1024,\n /** 单次 `git log` 的超时(毫秒)。超时按「采集失败」处理,不抛给调用方。 */\n TIMEOUT_MS: 15_000,\n /** 热点分数里 churn 的归一化标度:churn 达到该次数时倍数取到上限。 */\n SCALE: 20,\n /** churn 缺失(0)时的倍数——此时 `score === indegree`,与升级前完全一致。 */\n MIN_MULTIPLIER: 1,\n /** churn 充足时的倍数上限。 */\n MAX_MULTIPLIER: 4,\n} as const;\n\n/** 把一条路径归一化成「git log 输出 ↔ `node.filePath`」可比对的形状。 */\nexport function normalizeChurnPath(rawPath: string): string {\n return String(rawPath ?? '')\n // 先 trim:`git log` 的输出行可能带前导/尾随空白(重命名行切片尤其容易留一个空格)。\n // 键错位是静默的——`\" new/a.ts\"` 与 `\"new/a.ts\"` 是两个不同的键,\n // 查表永远落空,面板上表现为「改了很多次的文件 churn 是 0」,且没有任何报错。\n .trim()\n .replace(/\\\\/g, '/')\n .replace(/^\\.\\//, '')\n .replace(/\\/+$/, '');\n}\n\n/**\n * 解析 `git log --name-only --format=%x1e` 的原始输出。\n *\n * 输出形状(每个提交一段,段间用 `\\x1e` 分隔):\n *\n * ```\n * \\x1esrc/a.ts\n * src/b.ts\n * \\x1esrc/c.ts\n * ```\n *\n * 用记录分隔符而不是空行:文件名本身可能为空(被删除的文件在 `--name-only` 下会留空行),\n * 空行无法区分「提交之间」和「这个提交里有一个空文件名」。\n *\n * 这里是**纯函数**:`byFile` 只统计在 `output` 里出现的路径,不做任何过滤。\n */\nexport function parseGitLogNames(output: string): { byFile: Record<string, number>; commitCount: number } {\n const byFile: Record<string, number> = {};\n const segments = String(output ?? '').split('\\x1e');\n let commitCount = 0;\n\n for (const segment of segments) {\n // git 的 `--name-only` 每行一个路径;跳过空行与 \"path -> path\" 形式的重命名残留。\n let touched = false;\n for (const lineRaw of segment.split(/\\r?\\n/)) {\n const line = lineRaw.trim();\n if (!line) continue;\n // 重命名行(`--no-renames` 已禁用,但保留这层防御:git 的某些版本仍可能输出)。\n // `' -> '` 是**四**个字符,取新路径要跳过整段(`arrow + 4`)。\n // 早期写成 `arrow + 3` 会留下一个前导空格,使 `\" new/a.ts\"` 永远匹配不上\n // `node.filePath` 里的 `new/a.ts`——这类「差一个字符」的键错位在面板上\n // 表现为「这个文件明明改了很多次,churn 却是 0」,且没有任何报错。\n const arrow = line.indexOf(' -> ');\n const filePath = normalizeChurnPath(arrow >= 0 ? line.slice(arrow + 4) : line);\n if (!filePath) continue;\n byFile[filePath] = (byFile[filePath] ?? 0) + 1;\n touched = true;\n }\n if (touched) commitCount += 1;\n }\n\n return { byFile, commitCount };\n}\n\n/**\n * CodeScene 口径的热点分数 = 变更频率 × 复杂度(入度代理)。\n *\n * 两个量纲不能直接相乘:`churn` 可以到数百,`indegree` 一般几十。\n * 所以先把 churn 压到 `[MIN_MULTIPLIER, MAX_MULTIPLIER]` 的倍数区间再与 indegree 相乘。\n *\n * **关键不变量**:`churn <= 0`(含 git 不可用、文件从未被改动)时返回值**恰等于** `indegree`。\n * 这是「退化默认值必须拒绝而不是承诺」在本函数里的具体形态——没有 churn 数据时,\n * 热点排序必须与升级前逐位相同,否则等于用一次失败的采集悄悄改掉了既有排序。\n */\nexport function computeHotspotScore(indegree: number, churn: number): number {\n const base = indegree <= 0 ? 0 : indegree;\n if (!churn || churn <= 0) return base;\n\n const ratio = Math.max(0, Math.min(1, (churn - 1) / CHURN.SCALE));\n const multiplier = CHURN.MIN_MULTIPLIER\n + (CHURN.MAX_MULTIPLIER - CHURN.MIN_MULTIPLIER) * ratio;\n return Math.round(base * multiplier);\n}\n\n/** 把分数映射到与升级前同一套三档阈值(churn 为 0 时逐位复现旧行为)。 */\nexport function hotspotLevel(score: number): 'high' | 'medium' | 'low' {\n if (score >= 20) return 'high';\n if (score >= 10) return 'medium';\n return 'low';\n}\n\n/**\n * 热点候选门槛:入度不足 5 的节点不进热点列表。\n *\n * 刻意**只**看入度,不看 churn:\n * ① 入度是「复杂度」一侧的代理,churn 是另一侧;用 churn 放宽门槛会让\n * 「一个被改了 50 次但没人调用的工具函数」挤进热点榜,那不是热点,是噪音;\n * ② 更重要的工程理由:churn 可能整体缺失(git 不可用),若门槛依赖 churn,\n * 那么一次采集失败会**改变候选集合**,而不仅仅是改变排序——这违反\n * 「采集失败不能悄悄改变既有行为」这条不变量。\n */\nexport function isHotspotEligible(indegree: number): boolean {\n return indegree >= 5;\n}\n","/**\n * 时序视图(研究方案 §4 的 $\\mathcal{T}$ 维)的**纯函数层**。\n *\n * 形式化定义:$\\mathcal{G} = (V, E, \\Sigma, \\mathcal{T}, \\Pi)$,其中 $\\mathcal{T}$ 是\n * commit(或 commit 区间)索引。本模块把「以 commit 为切片单位的图切片」与\n * 「边的挥发度」这两件**没有副作用**的事放在 `src/shared/`,原因与 `churn.ts` 相同:\n * ① 宿主与浏览器端都能 import;② 单测不必真的跑 git;③ 关键不变量(缺数据时必须\n * 拒绝而不是编造)可以在纯函数层直接钉死。\n *\n * ## 数据来源(刻意不新建存储层)\n *\n * 仓库已有 `src/host/gitChurn.ts` 用 `git log --name-only` 采集变更频率。本模块复用\n * 同一份原始输出,只多解析出**每个提交的 hash 与时间戳**。分隔符必须放在格式\n * **开头**(`%x1e` 前置),与 `churn.ts` 的 `parseGitLogNames` 完全一致——若放在末尾,\n * 文件列表会落进下一个提交的分段,解析出的提交数与文件归属全部错位。\n *\n * ```\n * git log --name-only --format=%x1e%H%x1f%ct\n * ```\n *\n * 输出形状(`\\x1e` 是提交分隔符,`\\x1f` 分隔 hash 与 unix 时间戳):\n *\n * ```\n * \\x1e<hash>\\x1f<unix-ts>\n * src/a.ts\n * src/b.ts\n * \\x1e<hash2>\\x1f<unix-ts2>\n * src/c.ts\n * ```\n *\n * `parseCommitLog` 把这段原始文本解析成 `CommitSlice[]`;宿主只需在既有的\n * `git log` 调用上换一个 `--format`,不必引入新的索引或数据库。\n *\n * ## 挥发度的语义(研究方案 §4 原文)\n *\n * $$\n * \\text{vol}(u,v) = \\frac{|\\{t : (u,v,t) \\in E\\}|}{|\\{t : u,v \\text{ 均存在}\\}|}\n * $$\n *\n * 分子 = 该边在**多少个 commit 切片中存在**;分母 = 两端点同时存在的切片数。\n * 比值接近 1 = 稳定关系,接近 0 = 只在个别切片里出现过(volatile)。\n *\n * **为什么缺边级事实时必须拒绝**:若 `CommitSlice.edges` 未提供,我们只能用\n * 「两端文件都被触及」近似「边存在」,此时分子恒等于分母、挥发度恒为 1——\n * 那是一份看起来正常、实则毫无信息的假数据。因此 `edgeVolatility` 在切片没有\n * 边级事实时返回 `available: false`,而不是返回一排 1.0。\n *\n * @module shared/temporal\n */\n\nimport type { GraphData, GraphEdge, GraphNode } from '../types/index.ts';\nimport { normalizeChurnPath } from './churn.ts';\n\n/** 一个 commit 切片。 */\nexport interface CommitSlice {\n /** 提交 hash(完整或短 hash)。 */\n hash: string;\n /** 提交时间戳(unix 秒)。 */\n timestamp: number;\n /** 该提交触及的文件(已归一化路径,去空)。 */\n files: readonly string[];\n /**\n * 该切片中「已存在」的边(可选,**边级事实**)。\n *\n * 只有提供它才能算出有意义的挥发度分子;缺省时 `edgeVolatility` 拒绝计算。\n */\n edges?: ReadonlyArray<{ source: string; target: string }>;\n}\n\n/** 一次 commit 历史的采集结果。 */\nexport interface CommitHistory {\n slices: readonly CommitSlice[];\n /** 采集时的 HEAD;不可用为 `null`。 */\n head: string | null;\n /** 采集是否真的拿到了 git 数据。 */\n available: boolean;\n /** 失败原因(成功为 `null`)。 */\n error?: string | null;\n}\n\n/** commit 区间。`days` 与 `from`/`to` 可组合;`now` 可注入以便测试确定性。 */\nexport interface CommitRange {\n /** 只保留该 hash 及其之后的提交(含)。 */\n from?: string;\n /** 只保留到该 hash 为止(含)。 */\n to?: string;\n /** 只保留最近 N 天内的提交。 */\n days?: number;\n /** 参考时间(unix 毫秒);缺省 `Date.now()`。测试注入固定值。 */\n now?: number;\n}\n\n/** `commitSliceGraph` 的结果。 */\nexport interface TemporalSliceResult {\n /** 是否真的拿到了可用切片。 */\n available: boolean;\n /** 命中的切片数。 */\n sliceCount: number;\n /** 命中区间的首个提交 hash。 */\n from: string | null;\n /** 命中区间的末个提交 hash。 */\n to: string | null;\n /** 区间是否把历史截断了(有提交被排除在外)。 */\n truncated: boolean;\n nodes: GraphNode[];\n edges: GraphEdge[];\n /** 不可用/空区间时的人类可读原因。 */\n reason?: string;\n}\n\n/** 单条边的挥发度。 */\nexport interface EdgeVolatilityEntry {\n edgeId: string;\n /** 分子:该边存在的切片数。 */\n presentSlices: number;\n /** 分母:两端点同时存在的切片数。 */\n endpointSlices: number;\n /** 挥发度 = presentSlices / endpointSlices;分母为 0 时取 0。 */\n volatility: number;\n /** 该边首次出现的切片时间戳(用于「过去 N 天内新增」这类谓词)。 */\n firstSeenAt?: number;\n}\n\n/** `edgeVolatility` 的结果。 */\nexport interface EdgeVolatilityReport {\n available: boolean;\n /** 参与计算的历史切片数。 */\n slices: number;\n edges: EdgeVolatilityEntry[];\n reason?: string;\n}\n\n/** 时序谓词的输入事实(来自 `EdgeVolatilityEntry`)。 */\nexport interface TemporalEdgeFact {\n firstSeenAt?: number;\n volatility?: number;\n}\n\n/** `temporalPredicate` 的规格。 */\nexport interface TemporalPredicateSpec {\n /** 只保留「首次出现时间在最近 N 天内」的边。 */\n days?: number;\n /** 只保留「首次出现时间不早于该 unix 毫秒」的边。 */\n since?: number;\n /** 挥发度下限(含)。 */\n minVolatility?: number;\n /** 挥发度上限(含)。 */\n maxVolatility?: number;\n /** 参考时间(unix 毫秒);缺省 `Date.now()`。 */\n now?: number;\n}\n\n/** `temporalPredicate` 产出的谓词:接在 ρ 的边匹配之后做时序过滤。 */\nexport type TemporalPredicate = (fact: TemporalEdgeFact) => boolean;\n\n/**\n * 解析 `git log --name-only --format=%x1e%H%x1f%ct` 的原始输出。\n *\n * 这是**纯函数**:只按 `\\x1e` 分段,不访问 git、不抛异常。空行被跳过,\n * 重命名行(`a -> b`)取新路径——与 `churn.ts` 的 `parseGitLogNames` 同一套\n * 归一化口径(`normalizeChurnPath` 是唯一真源)。\n */\nexport function parseCommitLog(raw: string): CommitSlice[] {\n const segments = String(raw ?? '').split('\\x1e');\n const slices: CommitSlice[] = [];\n\n for (let segIndex = 0; segIndex < segments.length; segIndex += 1) {\n const lines = segments[segIndex]?.split(/\\r?\\n/) ?? [];\n const header = (lines[0] ?? '').trim();\n // 头部可能是 `<hash>\\x1f<unix-ts>`(增强格式)或直接是文件名(旧版 `%x1e`)。\n // 刻意**不用**带控制字符的正则(`\\x1f`)—— oxlint 的 no-control-regex 会拦;\n // 先按 `\\x1f` 切分、再用纯 ASCII 正则判定,语义完全等价。\n const headerParts = header.split('\\x1f');\n const hashPart = (headerParts[0] ?? '').trim();\n const tsPart = (headerParts[1] ?? '').trim();\n const isEnriched = /^[0-9a-f]{7,40}$/.test(hashPart) && /^\\d+$/.test(tsPart);\n\n // 兼容旧版 `--format=%x1e`:该格式只输出文件名、没有 hash 与时间戳。\n // 此时给一个稳定的合成 hash(seg-<序号>)与时间戳 0——**时间窗过滤会如实落空**,\n // 因为「不知道时间」不能伪装成「发生在此刻」。\n const hash = isEnriched ? hashPart : `seg-${segIndex}`;\n const timestamp = isEnriched ? Number(tsPart) : 0;\n if (!isEnriched && !header) continue; // 段首空行:不是提交。\n\n const files: string[] = [];\n for (const lineRaw of lines.slice(isEnriched ? 1 : 0)) {\n const line = lineRaw.trim();\n if (!line) continue;\n const arrow = line.indexOf(' -> ');\n const filePath = normalizeChurnPath(arrow >= 0 ? line.slice(arrow + 4) : line);\n if (filePath) files.push(filePath);\n }\n\n slices.push({ hash, timestamp, files });\n }\n return slices;\n}\n\n/**\n * 按区间过滤切片。\n *\n * 顺序:先 `days`(时间窗),再 `from`(含),再 `to`(含)。任何一步找不到目标\n * 都返回**空切片 + truncated**:宁可说「这个区间里没有东西」,也不要悄悄放宽边界。\n */\nexport function filterSlices(\n history: CommitHistory,\n range: CommitRange,\n now: number = Date.now(),\n): { slices: CommitSlice[]; truncated: boolean } {\n let slices = [...history.slices];\n let truncated = false;\n\n if (range.days !== undefined && Number.isFinite(range.days)) {\n const cutoff = now - range.days * 86_400_000;\n const kept = slices.filter((slice) => slice.timestamp * 1000 >= cutoff);\n truncated = kept.length !== slices.length;\n slices = kept;\n }\n\n if (range.from) {\n const index = slices.findIndex((slice) => slice.hash === range.from);\n if (index < 0) return { slices: [], truncated: true };\n slices = slices.slice(index);\n }\n\n if (range.to) {\n const index = slices.findIndex((slice) => slice.hash === range.to);\n if (index < 0) return { slices: [], truncated: true };\n slices = slices.slice(0, index + 1);\n }\n\n return { slices, truncated };\n}\n\n/**\n * 以 commit 区间为切片,取「区间内至少被触及过一次的文件」所诱导的子图。\n *\n * 节点保留条件:其 `filePath`(归一化)出现在区间内任一切片的 `files` 中。\n * 边保留条件:两端点都保留。这是「commit 切片」在只给文件触及数据时最诚实的近似——\n * 我们无法知道某文件在区间内是否被删除,因此把「被触及」当作「在该切片存在」。\n *\n * **缺历史时返回 `available: false` 的空图**:没有 $\\mathcal{T}$ 就没有时序切片,\n * 把它渲染成「空图」比渲染成「整个仓库」更接近事实。\n */\nexport function commitSliceGraph(\n data: Pick<GraphData, 'nodes' | 'edges'>,\n commitRange: CommitRange,\n history?: CommitHistory | null,\n): TemporalSliceResult {\n if (!history || !history.available || history.slices.length === 0) {\n return {\n available: false,\n sliceCount: 0,\n from: null,\n to: null,\n truncated: false,\n nodes: [],\n edges: [],\n reason: history && history.available ? 'commit 历史为空' : '未提供可用的 commit 历史',\n };\n }\n\n const now = commitRange.now ?? Date.now();\n const { slices, truncated } = filterSlices(history, commitRange, now);\n const activeFiles = new Set<string>();\n for (const slice of slices) {\n for (const file of slice.files) activeFiles.add(file);\n }\n\n const nodes = data.nodes.filter((node) => activeFiles.has(normalizeChurnPath(node.filePath)));\n const nodeIds = new Set(nodes.map((node) => node.id));\n const edges = data.edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target));\n\n const first = slices[0];\n const last = slices[slices.length - 1];\n return {\n available: true,\n sliceCount: slices.length,\n from: first?.hash ?? null,\n to: last?.hash ?? null,\n truncated,\n nodes,\n edges,\n ...(slices.length === 0 ? { reason: '区间内没有提交' } : {}),\n };\n}\n\n/**\n * 计算一组边的挥发度。\n *\n * 分母 = 两端点文件同时出现在该切片 `files` 里的切片数;\n * 分子 = 在这些切片里 `slice.edges` 也确实包含该边的切片数(边级事实)。\n *\n * **拒绝假数据**:只要有一个切片没有提供 `edges`,就无法区分「边不存在」与\n * 「我们不知道」,于是整体返回 `available: false`(而不是用文件近似把分子凑成分母)。\n *\n * `nodeFiles` 是 nodeId → 文件路径的映射,来自图节点;缺它同样拒绝计算。\n */\nexport function edgeVolatility(\n edges: readonly GraphEdge[],\n commitHistory: CommitHistory,\n nodeFiles?: Readonly<Record<string, string>> | null,\n): EdgeVolatilityReport {\n const emptyReport = (reason: string): EdgeVolatilityReport => ({\n available: false,\n slices: commitHistory.slices.length,\n edges: [],\n reason,\n });\n\n if (!commitHistory || !commitHistory.available || commitHistory.slices.length === 0) {\n return emptyReport('未提供可用的 commit 历史');\n }\n // 每个切片都必须给出边级事实,否则分子无从谈起(见文件头「拒绝假数据」)。\n if (!commitHistory.slices.every((slice) => slice.edges !== undefined)) {\n return emptyReport('commit 切片未提供边级事实(edges)');\n }\n if (!nodeFiles) {\n return emptyReport('缺少 nodeId → 文件路径映射,无法判定端点存在性');\n }\n\n const results: EdgeVolatilityEntry[] = [];\n for (const edge of edges) {\n const fileU = nodeFiles[String(edge.source)];\n const fileV = nodeFiles[String(edge.target)];\n if (!fileU || !fileV) continue; // 端点不在已知文件里:不猜。\n\n let endpointSlices = 0;\n let presentSlices = 0;\n let firstSeenAt: number | undefined;\n\n for (const slice of commitHistory.slices) {\n if (!slice.files.includes(fileU) || !slice.files.includes(fileV)) continue;\n endpointSlices += 1;\n const present = slice.edges?.some(\n (e) =>\n (e.source === String(edge.source) && e.target === String(edge.target)) ||\n (e.source === String(edge.target) && e.target === String(edge.source)),\n );\n if (present) {\n presentSlices += 1;\n if (firstSeenAt === undefined) firstSeenAt = slice.timestamp * 1000;\n }\n }\n\n results.push({\n edgeId: String(edge.id),\n presentSlices,\n endpointSlices,\n volatility: endpointSlices > 0 ? presentSlices / endpointSlices : 0,\n ...(firstSeenAt === undefined ? {} : { firstSeenAt }),\n });\n }\n\n return { available: true, slices: commitHistory.slices.length, edges: results };\n}\n\n/**\n * 构造时序谓词,供 `graph_path_pattern` 的 ρ 在边类型匹配之后再过滤。\n *\n * 语义:默认(空规格)恒真;给出 `days`/`since`/挥发度区间时,缺少对应事实的边\n * **不进入结果**(把「不知道」当成「不符合」是安全方向——宁可少返回,也不要把\n * 一条无法判定时序的边当成新边放出来)。\n */\nexport function temporalPredicate(spec: TemporalPredicateSpec = {}): TemporalPredicate {\n const now = spec.now ?? Date.now();\n return (fact: TemporalEdgeFact) => {\n if (spec.days !== undefined) {\n if (fact.firstSeenAt === undefined) return false;\n if (fact.firstSeenAt < now - spec.days * 86_400_000) return false;\n }\n if (spec.since !== undefined) {\n if (fact.firstSeenAt === undefined) return false;\n if (fact.firstSeenAt < spec.since) return false;\n }\n if (spec.minVolatility !== undefined) {\n if (fact.volatility === undefined || fact.volatility < spec.minVolatility) return false;\n }\n if (spec.maxVolatility !== undefined) {\n if (fact.volatility === undefined || fact.volatility > spec.maxVolatility) return false;\n }\n return true;\n };\n}\n\n// ── cycle-138:文件级边事实(firstSeenAt 的健全下界推导) ──────────────────\n\n/** `fileLevelEdgeFacts` 的结果。 */\nexport interface FileLevelEdgeFactsReport {\n /** 是否真的解析出了非空切片(false 时 `facts` 为空且 `reason` 说明)。 */\n available: boolean;\n /**\n * 边事实表。**双向键**:`${source}->${target}` 与 `${target}->${source}` 指向\n * 同一个事实对象(与 `edgeVolatility` 的双向 present 匹配同一口径——路径可以\n * 顺走也可以逆走,关系的时序不因方向改变)。\n *\n * `volatility` **刻意缺省**:raw `git log --name-only` 没有边级事实,把它凑成\n * 1.0 正是本文件头「拒绝假数据」拒绝的那种看似正常、实则无信息的数。\n */\n facts: ReadonlyMap<string, TemporalEdgeFact>;\n /** 不可用/空区间时的人类可读原因。 */\n reason?: string;\n}\n\n/**\n * 从 raw commit log 推导**文件级**的边时序事实(cycle-138)。\n *\n * ## 语义与健全性方向(为什么这是下界而不是猜测)\n *\n * 一条边 (u,v) 的**引入提交**必然同时触及两端文件(不改两个文件就无法在它们之间\n * 建立关系)。因此「首次有提交同时触及 u 与 v 所在文件」的时刻\n * `firstSeenAt_file` 满足 `firstSeenAt_file ≤ 引入时刻`——它是引入时间的**下界**。\n * 把它喂给 `temporalPredicate` 的 `since`/`days` 过滤时:\n *\n * - `firstSeenAt_file ≥ T` ⇒ 真实引入时刻 ≥ T,**保留的边保证真正新**(无误留);\n * - `firstSeenAt_file < T` ⇒ 真实引入时刻可能任一侧,丢弃可能错杀新边\n * (偏少返回)。这与 `temporalPredicate` 自己的「缺少事实 ⇒ 不符合」是同一\n * 安全方向:宁可少返回,也不把旧关系当成新的放出来。\n *\n * ## `volatility` 为什么不给\n *\n * 挥发度需要**边级事实**(每个切片里这条边是否真的存在);`--name-only` 只有\n * 文件级事实,用「两端文件都被触及」凑分子会让挥发度恒为 1。因此本函数产出的\n * 事实只有 `firstSeenAt`——挥发度过滤由调用方显式拒绝(`filterEdgesByTemporalPredicate`\n * 对 `minVolatility`/`maxVolatility` 返回 `available: false` + reason),等将来有\n * 边级采集渠道时在事实产出端点亮 `volatility` 即可,谓词与调用方零改动。\n */\nexport function fileLevelEdgeFacts(\n nodes: readonly Pick<GraphNode, 'id' | 'filePath'>[],\n edges: readonly Pick<GraphEdge, 'id' | 'source' | 'target'>[],\n commitLog: string,\n range?: CommitRange,\n): FileLevelEdgeFactsReport {\n const facts = new Map<string, TemporalEdgeFact>();\n if (typeof commitLog !== 'string' || commitLog.trim() === '') {\n return { available: false, facts, reason: '未提供 commit 历史' };\n }\n const allSlices = parseCommitLog(commitLog);\n const slices = range\n ? filterSlices({ slices: allSlices, head: null, available: true }, range).slices\n : allSlices;\n if (slices.length === 0) {\n return { available: false, facts, reason: '区间内没有提交' };\n }\n\n // nodeId → 归一化文件路径(与 commitSliceGraph 的节点过滤同一口径)。\n const nodeFiles = new Map<string, string>();\n for (const node of nodes) nodeFiles.set(String(node.id), normalizeChurnPath(node.filePath));\n\n for (const edge of edges) {\n const fileU = nodeFiles.get(String(edge.source));\n const fileV = nodeFiles.get(String(edge.target));\n if (!fileU || !fileV) continue; // 端点不在已知文件里:不猜。\n\n let firstSeenAt: number | undefined;\n for (const slice of slices) {\n if (!slice.files.includes(fileU) || !slice.files.includes(fileV)) continue;\n // 取 min 而不是「第一条命中」:parseCommitLog 保留 git log 的输出序(新→旧),\n // 「首次共同触及」必须与遍历方向无关(顺序独立性有测试钉住)。\n const ts = slice.timestamp * 1000;\n if (firstSeenAt === undefined || ts < firstSeenAt) firstSeenAt = ts;\n }\n if (firstSeenAt === undefined) continue; // 两端文件从未同时被触及:无事实(不猜)。\n\n const fact: TemporalEdgeFact = { firstSeenAt };\n facts.set(`${String(edge.source)}->${String(edge.target)}`, fact);\n facts.set(`${String(edge.target)}->${String(edge.source)}`, fact);\n }\n\n return { available: true, facts };\n}\n","/**\n * ρ 枚举的 commit 切片(cycle-137 / 研究方案 §4 的 $\\\\mathcal{T}$ 维)。\n *\n * ## 它解决什么\n *\n * 研究方案 §4 把路径查询定义为「在给定 commit 切片 `t` 下枚举」。cycle-134/135 的 ρ\n * 枚举只能在整个图上跑——`𝒯` 维度没有数据面。本模块把 `src/shared/temporal.ts` 的\n * `commitSliceGraph` 接进 ρ:给定 `commitLog`(`git log --name-only\n * --format=%x1e%H%x1f%ct` 的原始输出)与一个区间,先按区间切片,再在切片上枚举。\n *\n * ## 为什么切片而不是边级谓词\n *\n * 研究方案 §4 的原话是「在给定 commit 切片 `t` 下枚举」——切片是**图级**操作,\n * ρ 是**图级**操作。把切片放在枚举之前,等于复用既有的 `commitSliceGraph`\n * (已经测过、已经用于 `graph_slice`),而不必改枚举引擎的核心。\n *\n * 边级时序谓词(`temporalPredicate` 的挥发度过滤)是更细的语义,属于后续轮。\n *\n * ## 为什么调用方必须用**临时**适配器\n *\n * 切片后的图是原图的子集。把它 `syncGraph` 进按 repoId 缓存的共享池,会让池里的图\n * 变成切片——下一次不带 `commitLog` 的 ρ 调用会拿到切片图,静默少查。因此工具层\n * 必须为时序路径建一个**一次性**适配器。\n */\nimport type { GraphData } from '../../types/index.ts';\nimport {\n commitSliceGraph,\n fileLevelEdgeFacts,\n parseCommitLog,\n temporalPredicate,\n type CommitRange,\n type CommitHistory,\n} from '../temporal.ts';\nimport { XuanjiAdapterPool } from './XuanjiAdapter.ts';\n\n/**\n * ρ 时序路径的**独立**适配器池(cycle-137)。\n *\n * ## 为什么独立于 tools.ts 的主池\n *\n * 切片图是原图的**子集**。把它 `syncGraph` 进按 repoId 缓存的主池,会让池里的图变成\n * 切片——下一次不带 `commitLog` 的 ρ 调用会拿到切片图,静默少查(与 cycle-135 的\n * 「所见即所查」同族缺陷)。因此时序路径必须用一个与主池**互不污染**的池。\n *\n * ## 为什么放在本模块而不是 tools.ts\n *\n * 源码级守卫(`tests/unit/xuanjiReachability.test.ts` 的 XJ-02 / `xuanjiIntegrationContract.test.ts`\n * 的 XJ-13)要求:\n * - `src/tools.ts` 里 `new XuanjiAdapterPool(` **恰好一次**;\n * - `src/tools.ts` 里 `new XuanjiAdapter()` **零次**;\n * - 全 `src/` 里唯一直接构造 `XuanjiAdapter` 的文件是池自身。\n *\n * 把时序池放在这里,tools.ts 只 import 它——三条守卫的计数与负向断言都不受影响。\n * 容量取 1:时序路径每次的切片都可能不同,本没有缓存收益,独立池只为「不污染主池」。\n */\nexport const temporalXuanjiPool = new XuanjiAdapterPool(1);\n\n/** ρ 时序切片的请求参数。 */\nexport interface TemporalEnumerationRequest {\n /** 原始 `git log --name-only --format=%x1e%H%x1f%ct` 输出。 */\n commitLog?: string;\n /** 只保留最近 N 天内的提交。 */\n days?: number;\n /** 只保留该 hash 及之后的提交(含)。 */\n from?: string;\n /** 只保留到该 hash 为止(含)。 */\n to?: string;\n /** 参考时间(unix 毫秒);测试注入固定值。 */\n now?: number;\n}\n\n/** `sliceGraphForTemporal` 的时序元信息(未请求时为 `null`)。 */\nexport interface TemporalEnumerationMeta {\n /** 调用方是否提供了 commitLog。 */\n requested: boolean;\n /** 是否真的拿到了可用切片。 */\n available: boolean;\n /** 命中的切片数。 */\n sliceCount: number;\n /** 命中区间的首个提交 hash。 */\n from: string | null;\n /** 命中区间的末个提交 hash。 */\n to: string | null;\n /** 区间是否把历史截断了。 */\n truncated: boolean;\n /** 不可用/空区间时的人类可读原因。 */\n reason?: string;\n}\n\n/** `sliceGraphForTemporal` 的结果。 */\nexport interface TemporalEnumerationOutcome {\n /** 用于枚举的图(未提供 commitLog 时为原图;提供且可用时为切片)。 */\n data: GraphData;\n /** 时序元信息;未请求时为 `null`(调用方据此区分「没请求」与「请求了但失败」)。 */\n temporal: TemporalEnumerationMeta | null;\n}\n\n/**\n * 按 commit 区间切片 ρ 枚举用的图。\n *\n * 契约:\n * - 未提供 / 空 `commitLog` ⇒ 返回原图 + `temporal: null`(**行为与既有调用逐字节一致**);\n * - 提供了但切片不可用(历史为空 / 区间未命中)⇒ 返回**原图** + `temporal.available: false`\n * 与原因(调用方据此做诚实降级,**不**假装切片为空图);\n * - 切片可用 ⇒ 返回切片图 + 完整元信息。\n */\nexport function sliceGraphForTemporal(\n data: GraphData,\n request: TemporalEnumerationRequest = {},\n): TemporalEnumerationOutcome {\n const commitLog = request.commitLog;\n if (typeof commitLog !== 'string' || commitLog.trim() === '') {\n return { data, temporal: null };\n }\n\n const history: CommitHistory = {\n slices: parseCommitLog(commitLog),\n head: null,\n available: true,\n };\n\n const range: CommitRange = {\n ...(request.days !== undefined && Number.isFinite(request.days) ? { days: request.days } : {}),\n ...(request.from ? { from: request.from } : {}),\n ...(request.to ? { to: request.to } : {}),\n ...(request.now !== undefined && Number.isFinite(request.now) ? { now: request.now } : {}),\n };\n\n const slice = commitSliceGraph(data, range, history);\n if (!slice.available) {\n return {\n data,\n temporal: {\n requested: true,\n available: false,\n sliceCount: 0,\n from: null,\n to: null,\n truncated: false,\n reason: slice.reason ?? 'commit 切片不可用',\n },\n };\n }\n\n const sliced: GraphData = {\n nodes: slice.nodes,\n edges: slice.edges,\n metadata: { ...data.metadata },\n };\n return {\n data: sliced,\n temporal: {\n requested: true,\n available: true,\n sliceCount: slice.sliceCount,\n from: slice.from,\n to: slice.to,\n truncated: slice.truncated,\n ...(slice.reason ? { reason: slice.reason } : {}),\n },\n };\n}\n\n/**\n * 边级时序过滤的请求参数(cycle-138 / 研究方案 §4 的边级时序谓词)。\n *\n * 语义:在 commit 切片(图级)**之后**,再按「边的引入时间」做**边级**\n * 过滤。两者是正交的两刀:切片决定「哪些文件存在」,谓词决定「哪些关系够新」。\n */\nexport interface EdgeTemporalFilterRequest {\n /** 原始 `git log --name-only --format=%x1e%H%x1f%ct` 输出(与图级切片同一份)。 */\n commitLog?: string;\n /**\n * 挥发度下限(含)。**当前被显式拒绝**:挥发度需要边级事实(每个切片里边的\n * 存在性),raw `--name-only` 只有文件级事实 ⇒ 请求即返回 `available: false`\n * + reason(见 `filterEdgesByTemporalPredicate` 头注)。\n */\n minVolatility?: number;\n /** 挥发度上限(含)。同上,当前被显式拒绝。 */\n maxVolatility?: number;\n /** 只保留首次共同触及时刻(引入时刻的**下界**)≥ since 的边(unix 毫秒)。 */\n since?: number;\n /** 参考时间(unix 毫秒);测试注入固定值,透传给 `temporalPredicate` 的 `days` 判定。 */\n now?: number;\n}\n\n/** 边级时序过滤的元信息(未请求时为 `null`)。 */\nexport interface EdgeTemporalMeta {\n /** 调用方是否请求了边级过滤(给了 commitLog 且给了任一谓词参数)。 */\n requested: boolean;\n /** 文件级事实是否可用(不可用时**全图保留**,不做静默清空)。 */\n available: boolean;\n /** 有事实的边数。 */\n factCount: number;\n /** 被谓词过滤掉的边数。 */\n filtered: number;\n /** 过滤后剩余的边数。 */\n kept: number;\n /** 不可用/未过滤时的人类可读原因。 */\n reason?: string;\n}\n\n/** 边级时序过滤的结果。 */\nexport interface EdgeTemporalOutcome {\n data: GraphData;\n meta: EdgeTemporalMeta | null;\n}\n\n/**\n * 按**边级时序谓词**过滤图(cycle-138)。\n *\n * ## 为什么放在图级切片之后、枚举之前\n *\n * 与 §14.3 的「切片是图级操作」同构:谓词是**边级**操作,ρ 是**图级**操作。\n * 把谓词放在枚举之前,等于先把不满足时序约束的边从图里**摘掉**再枚举——ρ 的\n * 长度区间、分页游标、截断预算全部按过滤后的图工作,语义不与分页纠缠。\n * (若把谓词做成枚举中的每步回调,`cursor` 的「第 k 条」语义会被过滤吞掉:\n * 同一游标翻页两次可能因过滤顺序产生不同结果——那正是 cycle-86 边聚合要修的\n * 「聚合与分页顺序」同族缺陷。)\n *\n * ## 三种状态(调用方——工具层——按 meta 分流)\n *\n * - 未请求(无 commitLog 或无谓词参数)⇒ 原图原样返回 + `meta: null`;\n * - 请求了但不可用 ⇒ **原图原样返回** + `available: false` + reason。这里刻意\n * **不**在函数内清空边:过滤函数只负责诚实报告,「请求了但不可用 ⇒ 诚实空结果」\n * 的裁决权在工具层(与图级切片的既有契约同形,两处共用同一个早退分支);\n * - 事实可用 ⇒ 只保留「有事实且通过谓词」的边;**无事实的边一律移除**\n * (不知道 ≠ 符合,`temporalPredicate` 自己的语义就是缺事实判不符合)。\n *\n * ## `volatility` 谓词当前被显式拒绝(不是静默缺失)\n *\n * raw `git log --name-only` 只有文件级事实;挥发度需要**每个 commit 切片里这条边\n * 是否真的存在**(边级事实),当前没有任何数据面提供它。因此请求了\n * `minVolatility`/`maxVolatility` 时返回 `available: false` + 指明原因——这是一个\n * **可达且可测**的降级态,而不是「参数存在但永远产出空结果」的陷阱(那会让下一个\n * 调用方把「数据面缺失」误读成「图上没有稳定关系」)。等将来有边级采集渠道\n * (逐 commit 的边存在性),在 `fileLevelEdgeFacts` 的产出端点亮 `volatility`\n * 字段即可,谓词与工具面零改动。\n */\nexport function filterEdgesByTemporalPredicate(\n data: GraphData,\n request: EdgeTemporalFilterRequest = {},\n): EdgeTemporalOutcome {\n const hasPredicate =\n request.minVolatility !== undefined ||\n request.maxVolatility !== undefined ||\n request.since !== undefined;\n if (typeof request.commitLog !== 'string' || request.commitLog.trim() === '' || !hasPredicate) {\n return { data, meta: null };\n }\n\n // 挥发度谓词需要边级事实:当前数据面(--name-only,文件级)不提供 ⇒ 显式拒绝。\n if (request.minVolatility !== undefined || request.maxVolatility !== undefined) {\n return {\n data,\n meta: {\n requested: true,\n available: false,\n factCount: 0,\n filtered: 0,\n kept: data.edges.length,\n reason: '挥发度过滤需要边级事实(每个 commit 切片里边的存在性);当前数据面只有文件级事实(--name-only),挥发度谓词被显式拒绝',\n },\n };\n }\n\n // now 是 CommitRange 的可选字段:给就带上(测试注入固定值),不给则让\n // temporalPredicate 用自己的 Date.now() 缺省(与既有行为一致)。\n const range: CommitRange = request.now !== undefined && Number.isFinite(request.now)\n ? { now: request.now }\n : {};\n const report = fileLevelEdgeFacts(data.nodes, data.edges, request.commitLog as string, range);\n if (!report.available) {\n return {\n data,\n meta: {\n requested: true,\n available: false,\n factCount: 0,\n filtered: 0,\n kept: data.edges.length,\n reason: report.reason ?? 'commit 历史不可用',\n },\n };\n }\n\n const predicate = temporalPredicate({\n ...(request.since !== undefined ? { since: request.since } : {}),\n ...(request.now !== undefined ? { now: request.now } : {}),\n });\n\n const keptEdges = data.edges.filter((edge) => {\n const fact = report.facts.get(`${String(edge.source)}->${String(edge.target)}`);\n if (!fact) return false; // 无事实 ≠ 符合(与 temporalPredicate 的缺字段语义一致)。\n return predicate(fact);\n });\n return {\n data: { nodes: data.nodes, edges: keptEdges, metadata: { ...data.metadata } },\n meta: {\n requested: true,\n available: true,\n factCount: report.facts.size / 2, // 双向键指向同一事实。\n filtered: data.edges.length - keptEdges.length,\n kept: keptEdges.length,\n },\n };\n}\n","/**\n * 关键词查询(graphify P0-2 借鉴):关键词 → 种子 → 子图 → 预算内文本。\n *\n * 璇玑图原型:读法不只有「按路径规则枚举」,还有一种最常用的读法——**按字找诗**。\n * 本模块把「找种子 / 取邻域 / 渲染成有 token 预算的文本」做成纯函数:\n * 无 IO、无 React、无 cordis,宿主(`src/tools.ts` 的 `graph_query`)只负责取图与序列化。\n *\n * 为什么放在库内而不是宿主 `src/shared/`:这三件事都只依赖「id / label / 边」这种\n * 图空间级概念(id / label / 边),不需要宿主的 metadata 与 branded id;放在库内可以继续维持\n * 「`src/xuanji` 零宿主依赖」的守卫,也让纯函数用例与实际运行的是同一份实现。\n *\n * 三条从 graphify 抄来的关键设计(照抄理由写在各自函数上):\n * 1. **每词保底一个种子**:多词查询不能被单个精确命中劫持(否则第二个词等于没查);\n * 2. **超预算三分支**:装得下 / 只有边装不下 / 节点也装不下——最容易做错的是中间那支;\n * 3. **中文 token 估算不能抄 `len/3`**:那个系数按英文校准,中文会被少算 2~3 倍,\n * 于是截断永不触发、调用方以为看全了。\n *\n * @module xuanji/query/keyword-query\n */\n\nimport type { EdgeType, NodeId } from '../core/graph-space.ts';\nimport { topK } from './topk.ts';\n\n/** 参与查询的最小节点视图(宿主节点结构的字段子集:id / label / filePath)。 */\nexport interface KeywordNode {\n readonly id: NodeId;\n readonly label: string;\n /** 文件路径(可作为弱匹配维度;缺省不参与)。 */\n readonly filePath?: string;\n}\n\n/** 参与渲染的最小边视图(宿主边结构的字段子集:source / target / type)。 */\nexport interface KeywordEdge {\n readonly source: NodeId;\n readonly target: NodeId;\n readonly type: EdgeType;\n}\n\n/** 一个种子及其得分。 */\nexport interface SeedMatch {\n readonly id: NodeId;\n readonly label: string;\n readonly score: number;\n /** 该种子命中的查询词(用于解释「为什么它被选中」)。 */\n readonly matchedTerms: readonly string[];\n}\n\n/** `selectSeeds` 的选项。 */\nexport interface SelectSeedOptions {\n /** 最多几个种子(graphify 用 3);缺省 3。 */\n readonly maxK?: number;\n /**\n * 断层截断比例:总分低于 `最高分 × gapRatio` 的候选直接丢弃(缺省 0.2)。\n * 取 0(或负数)时不做断层截断。\n */\n readonly gapRatio?: number;\n}\n\n/** `renderSubgraphText` 的选项。 */\nexport interface RenderOptions {\n /** 从种子向外扩几跳;缺省 1,上限 3。 */\n readonly depth?: number;\n /** token 预算;缺省 1200。 */\n readonly tokenBudget?: number;\n}\n\n/** 渲染结果的三种形态(对应 graphify 的三分支)。 */\nexport type RenderReason = 'fits' | 'nodes-only' | 'truncated';\n\n/** 渲染结果。 */\nexport interface RenderedSubgraph {\n readonly text: string;\n /** 渲染出的文本的 token 估算值(与 `estimateTokens` 同一把尺子)。 */\n readonly tokens: number;\n /** 是否发生了截断(`fits` 之外都为 true)。 */\n readonly truncated: boolean;\n readonly reason: RenderReason;\n readonly nodeCount: number;\n readonly edgeCount: number;\n}\n\n/** 单个 ASCII 词 / 标识符的最小长度(过短的词会匹配一切,等同噪声)。 */\nconst MIN_TERM_LENGTH = 2;\n\n/**\n * BFS 展开深度的**库级硬上界**(cycle-112 继承项 E:此前在 `renderSubgraphText` 里\n * 裸写 `Math.min(3, …)`,未导出——宿主 route 只好镜像一份常量,而镜像在库改上界时会\n * 静默撒谎,见 `src/host/routes/queryRoute.ts` 的 `LIBRARY_MAX_DEPTH`)。现在导出,\n * 宿主改为引用这里。\n *\n * 为什么是 3:关键词检索的种子已经是**精确节点**,展开只为「看上下文」;3 跳之后的\n * 邻域对定位几乎没有增量信息,而 token 预算(真正的稀缺资源)在 2-3 跳内通常已耗尽。\n * 要更深的邻域探索请用 `graph_path_find`(两点间精确路径),不是调大这个值。\n */\nexport const MAX_QUERY_DEPTH = 3;\n\n/** 得分档位(照 graphify `serve.py:339-351`):精确 > 前缀 > 子串 > 路径。 */\nconst SCORE_EXACT = 1000;\nconst SCORE_PREFIX = 100;\nconst SCORE_SUBSTRING = 1;\nconst SCORE_PATH = 0.5;\n\nconst CJK = /[\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff]/;\n\n/**\n * 估算文本的 token 数。\n *\n * `ceil(cjkCount × 1 + otherCount / 3.5)`:\n * - 一个汉字约 1 token(中文分词器的常见经验值);\n * - 其余字符按 3.5 字符/token(英文混合标识符的保守值,比 `len/3` 更保守)。\n * **偏向高估**:宁可早截断,也别让调用方以为看全了(研究 §P0-2 风险栏)。\n */\nexport function estimateTokens(text: string): number {\n if (text.length === 0) return 0;\n let cjk = 0;\n for (const ch of text) if (CJK.test(ch)) cjk++;\n const other = text.length - cjk;\n return Math.ceil(cjk * 1 + other / 3.5);\n}\n\n/**\n * 把查询串切成词。\n *\n * - ASCII 标识符按 `[A-Za-z0-9_$]+` 取整段(`buildForwardAdjacency` 保持为一个词);\n * - CJK 按**单字**切(中文没有空格,单字召回是唯一可靠的做法),同时保留连续 CJK\n * 片段作为一个整体词(「依赖环」既能整体命中,也能被「依赖」「环」分别召回);\n * - 去重、保持首次出现顺序(确定性);丢弃长度 < 2 的纯 ASCII 词(噪声)。\n */\nexport function tokenizeQuery(query: string): string[] {\n const terms: string[] = [];\n const push = (term: string): void => {\n const normalized = term.trim().toLowerCase();\n if (normalized.length === 0) return;\n if (!CJK.test(normalized) && normalized.length < MIN_TERM_LENGTH) return;\n if (!terms.includes(normalized)) terms.push(normalized);\n };\n\n // 单趟扫描,保持**出现顺序**(确定性来自顺序,不能按类别分两趟拼起来):\n // 连续 CJK 片段 → 整体词 + 逐字词;ASCII 标识符 → 整段词。\n for (const run of query.match(/[\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff]+|[A-Za-z0-9_$]+/g) ?? []) {\n if (CJK.test(run)) {\n push(run);\n for (const ch of run) push(ch);\n } else {\n push(run);\n }\n }\n\n return terms;\n}\n\n/** 单个词对单个节点的得分(档位见 SCORE_*)。 */\nfunction scoreOnce(label: string, filePath: string | undefined, term: string): number {\n if (label === term) return SCORE_EXACT;\n if (label.startsWith(term)) return SCORE_PREFIX;\n if (label.includes(term)) return SCORE_SUBSTRING;\n if (filePath !== undefined && filePath.toLowerCase().includes(term)) return SCORE_PATH;\n return 0;\n}\n\n/**\n * 选种子:**分级加分 + 断层截断 + 每词保底**。\n *\n * 每词保底这一条必须抄(graphify `serve.py:862-887`):两词查询里,一个词精确命中一个\n * 无关节点(1000 分)会把另一个词的相关节点(1 分)挤出前 K,于是第二个词等于没查。\n * 保底规则:每个词各自的最佳候选**无条件**入选,即使它低于断层阈值。\n *\n * 输出顺序:得分降序 → label 升序 → id 升序(完全确定,便于断言与复现)。\n * `maxK` 只约束「非保底」部分:保底条目可以超出 `maxK`(多词查询的必然结果)。\n */\nexport function selectSeeds(\n nodes: readonly KeywordNode[],\n terms: readonly string[],\n options: SelectSeedOptions = {},\n): SeedMatch[] {\n if (nodes.length === 0 || terms.length === 0) return [];\n const maxK = Math.max(1, Math.floor(options.maxK ?? 3));\n const gapRatio = options.gapRatio ?? 0.2;\n\n const scored: SeedMatch[] = [];\n for (const node of nodes) {\n const label = node.label.toLowerCase();\n let score = 0;\n const matched: string[] = [];\n for (const term of terms) {\n const points = scoreOnce(label, node.filePath, term);\n if (points > 0) {\n score += points;\n matched.push(term);\n }\n }\n if (score > 0) scored.push({ id: node.id, label: node.label, score, matchedTerms: matched });\n }\n if (scored.length === 0) return [];\n\n scored.sort((a, b) => b.score - a.score || a.label.localeCompare(b.label) || a.id.localeCompare(b.id));\n\n // 断层截断:低于最高分 × gapRatio 的候选不做主力(保底词仍可把它们拉回来)。\n const topScore = scored[0]!.score;\n const kept = gapRatio > 0 ? scored.filter((match) => match.score >= topScore * gapRatio) : scored;\n\n // topK 负责「取前 maxK 个」;HOST 不直接关心排序细节,但这一步必须走同一个实现。\n const primary = topK(kept, maxK, (match) => match.score);\n\n // 每词保底:每个词的最佳候选必须在内(无条件,越过断层阈值)。\n const guaranteed: SeedMatch[] = [];\n for (const term of terms) {\n let best: SeedMatch | undefined;\n for (const match of scored) {\n if (!match.matchedTerms.includes(term)) continue;\n if (!best || match.score > best.score) best = match;\n }\n if (best) guaranteed.push(best);\n }\n\n const chosen = new Map<NodeId, SeedMatch>();\n for (const match of [...primary, ...guaranteed]) chosen.set(match.id, match);\n return [...chosen.values()].sort(\n (a, b) => b.score - a.score || a.label.localeCompare(b.label) || a.id.localeCompare(b.id),\n );\n}\n\n/** 去掉控制字符与换行(LLM 上下文注入防护,对应 graphify `sanitize_label`)。 */\nfunction sanitize(value: string): string {\n // eslint-disable-next-line no-control-regex\n return value.replace(/[\\u0000-\\u001f\\u007f]+/g, ' ').trim();\n}\n\n/**\n * 渲染子图文本:种子优先 → 其余按「跳数 → 度数降序 → id 升序」→ NODE 先 EDGE 后。\n *\n * 三分支(graphify `serve.py:1252-1313`):\n * 1. `fits`:整份文本装得下预算,原样返回;\n * 2. `nodes-only`:**所有节点装得下、边装不下** ⇒ 返回全部节点 + 一行 over-budget 告示\n * (这是最容易做错的一支:丢掉节点等于让调用方以为图里没这些节点);\n * 3. `truncated`:连节点都装不下 ⇒ 按优先级截断,顶部打 `[!] TRUNCATED`,\n * 并给出**真的够用**的预算值(整份文本的估算值),否则告示在骗人(graphify 自己的 issue 2784 就是这样被发现的)。\n */\nexport function renderSubgraphText(\n graph: { readonly nodes: readonly KeywordNode[]; readonly edges: readonly KeywordEdge[] },\n seeds: readonly SeedMatch[],\n options: RenderOptions = {},\n): RenderedSubgraph {\n const depth = Math.min(MAX_QUERY_DEPTH, Math.max(0, Math.floor(options.depth ?? 1)));\n const tokenBudget = Math.max(1, Math.floor(options.tokenBudget ?? 1200));\n const budgetLabel = `token_budget=${tokenBudget}`;\n\n const byId = new Map<NodeId, KeywordNode>();\n for (const node of graph.nodes) byId.set(node.id, node);\n\n const seedIds = new Set<NodeId>(seeds.map((seed) => seed.id).filter((id) => byId.has(id)));\n const adjacency = new Map<NodeId, Set<NodeId>>();\n for (const edge of graph.edges) {\n if (!byId.has(edge.source) || !byId.has(edge.target)) continue;\n if (!adjacency.has(edge.source)) adjacency.set(edge.source, new Set());\n if (!adjacency.has(edge.target)) adjacency.set(edge.target, new Set());\n adjacency.get(edge.source)!.add(edge.target);\n adjacency.get(edge.target)!.add(edge.source);\n }\n\n // 从种子做有界 BFS(跳数用 hop 记录,seed 自身 hop=0)\n const hop = new Map<NodeId, number>();\n const frontier: NodeId[] = [];\n for (const id of [...seedIds].sort()) {\n hop.set(id, 0);\n frontier.push(id);\n }\n for (let head = 0; head < frontier.length; head++) {\n const current = frontier[head]!;\n const currentHop = hop.get(current)!;\n if (currentHop >= depth) continue;\n for (const next of [...(adjacency.get(current) ?? [])].sort()) {\n if (hop.has(next)) continue;\n hop.set(next, currentHop + 1);\n frontier.push(next);\n }\n }\n\n // 未找到任何种子时不渲染空壳:直接把「无匹配」当作结果(调用方据此报错/换词)。\n if (hop.size === 0) {\n return {\n text: `No seed matched. (${budgetLabel})`,\n tokens: estimateTokens(`No seed matched. (${budgetLabel})`),\n truncated: false,\n reason: 'fits',\n nodeCount: 0,\n edgeCount: 0,\n };\n }\n\n const pickedNodes = [...hop.keys()]\n .filter((id) => byId.has(id))\n .sort((a, b) => {\n const hopDiff = hop.get(a)! - hop.get(b)!;\n if (hopDiff !== 0) return hopDiff;\n const degreeDiff = (adjacency.get(b)?.size ?? 0) - (adjacency.get(a)?.size ?? 0);\n if (degreeDiff !== 0) return degreeDiff;\n return a.localeCompare(b);\n });\n\n const nodeLines = pickedNodes.map((id) => {\n const label = sanitize(byId.get(id)!.label);\n const seedMark = seedIds.has(id) ? ' *' : '';\n return `NODE ${id} ${label}${seedMark}`;\n });\n\n const pickedSet = new Set(pickedNodes);\n const edgeLines = graph.edges\n .filter((edge) => pickedSet.has(edge.source) && pickedSet.has(edge.target))\n .map((edge) => `EDGE ${edge.source} -> ${edge.target} (${edge.type})`);\n\n const header = `seeds=${seeds.filter((seed) => seedIds.has(seed.id)).map((seed) => seed.id).join(',') || '(none)'}`;\n const fullText = [header, ...nodeLines, ...edgeLines].join('\\n');\n const fullTokens = estimateTokens(fullText);\n if (fullTokens <= tokenBudget) {\n return {\n text: fullText,\n tokens: fullTokens,\n truncated: false,\n reason: 'fits',\n nodeCount: pickedNodes.length,\n edgeCount: edgeLines.length,\n };\n }\n\n // 中间那一支:节点装得下、边装不下 ⇒ 完整节点 + over-budget 告示。\n const nodesOnlyText = [header, ...nodeLines].join('\\n');\n const nodesOnlyTokens = estimateTokens(nodesOnlyText);\n const overBudgetNotice = `[!] over budget: edges omitted (${budgetLabel}, full=${fullTokens})`;\n if (nodesOnlyTokens + estimateTokens(overBudgetNotice) <= tokenBudget) {\n const text = `${nodesOnlyText}\\n${overBudgetNotice}`;\n return {\n text,\n tokens: estimateTokens(text),\n truncated: true,\n reason: 'nodes-only',\n nodeCount: pickedNodes.length,\n edgeCount: 0,\n };\n }\n\n // 最坏分支:逐个塞节点直到预算用尽;告示里的预算值取整份文本的估算值(真的够)。\n const prefix = `[!] TRUNCATED (${budgetLabel}; full=${fullTokens})`;\n const lines: string[] = [prefix, header];\n let used = estimateTokens(`${prefix}\\n${header}`);\n let kept = 0;\n for (const line of nodeLines) {\n const cost = estimateTokens(line) + 1; // +1 近似换行\n if (used + cost > tokenBudget) break;\n lines.push(line);\n used += cost;\n kept++;\n }\n const text = lines.join('\\n');\n return {\n text,\n tokens: estimateTokens(text),\n truncated: true,\n reason: 'truncated',\n nodeCount: kept,\n edgeCount: 0,\n };\n}\n","import type { AdjacencyMap } from './AdjacencyList.ts';\r\n\r\nexport interface BFSResult {\r\n path: string[];\r\n length: number;\r\n truncated: boolean;\r\n}\r\n\r\nexport function bfsShortestPath(\r\n adjacency: AdjacencyMap,\r\n source: string,\r\n target: string,\r\n maxNodes: number,\r\n): BFSResult | null {\r\n if (source === target) return { path: [source], length: 0, truncated: false };\r\n\r\n const visited = new Set<string>([source]);\r\n const predecessor = new Map<string, string>();\r\n const queue: string[] = [source];\r\n let head = 0;\r\n\r\n while (head < queue.length) {\r\n if (visited.size > maxNodes) return { path: [], length: 0, truncated: true };\r\n const id = queue[head++]!;\r\n if (id === target) {\r\n const path: string[] = [];\r\n let cur: string | undefined = id;\r\n while (cur !== undefined) {\r\n path.unshift(cur);\r\n cur = predecessor.get(cur);\r\n }\r\n return { path, length: path.length - 1, truncated: false };\r\n }\r\n const neighbors = adjacency.get(id) ?? [];\r\n for (const next of neighbors) {\r\n if (!visited.has(next)) {\r\n visited.add(next);\r\n predecessor.set(next, id);\r\n queue.push(next);\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nexport function bfsFrontierExpansion(\r\n reverseAdjacency: AdjacencyMap,\r\n startNode: string,\r\n depth: number,\r\n maxResults: number,\r\n): { impacted: string[]; count: number; truncated: boolean } {\r\n const impacted = new Set<string>();\r\n let frontier = new Set<string>([startNode]);\r\n\r\n for (let d = 0; d < depth && impacted.size < maxResults; d++) {\r\n const nextFrontier = new Set<string>();\r\n for (const id of frontier) {\r\n if (impacted.size >= maxResults) break;\r\n const sources = reverseAdjacency.get(id) ?? [];\r\n for (const src of sources) {\r\n if (impacted.size >= maxResults) break;\r\n if (!impacted.has(src)) {\r\n impacted.add(src);\r\n nextFrontier.add(src);\r\n }\r\n }\r\n }\r\n frontier = nextFrontier;\r\n }\r\n return {\r\n impacted: Array.from(impacted),\r\n count: impacted.size,\r\n truncated: impacted.size >= maxResults,\r\n };\r\n}\r\nexport function bfsReachCount(\r\n adjacency: AdjacencyMap,\r\n source: string,\r\n maxNodes: number,\r\n): number {\r\n const visited = new Set<string>([source]);\r\n const queue: string[] = [source];\r\n let head = 0;\r\n while (head < queue.length && visited.size < maxNodes) {\r\n const current = queue[head++]!;\r\n const neighbors = adjacency.get(current) ?? [];\r\n for (const target of neighbors) {\r\n if (!visited.has(target)) {\r\n visited.add(target);\r\n queue.push(target);\r\n }\r\n }\r\n }\r\n return visited.size - 1;\r\n}","import type { GraphNode, GraphEdge } from '../types/index.ts';\r\nimport { scoped } from './Logger.ts';\r\n\r\nconst log = scoped('graphology');\r\n\r\nexport interface CommunityResult {\r\n nodeId: string;\r\n community: number;\r\n}\r\n\r\nexport interface CentralityResult {\r\n nodeId: string;\r\n centrality: number;\r\n}\r\n\r\ninterface IGGraph {\r\n order: number;\r\n size: number;\r\n addNode(id: string, attrs?: Record<string, unknown>): void;\r\n addEdge(source: string, target: string, attrs?: Record<string, unknown>): void;\r\n hasNode(id: string): boolean;\r\n hasEdge(source: string, target: string): boolean;\r\n degree(id: string): number;\r\n forEachNode(cb: (id: string, attrs: Record<string, unknown>) => void): void;\r\n forEachNeighbor(id: string, cb: (neighbor: string, attrs: Record<string, unknown>) => void): void;\r\n clear(): void;\r\n}\r\n\r\nasync function loadGraphCtor(): Promise<new (opts: Record<string, unknown>) => IGGraph> {\r\n const mod = await import('graphology');\r\n const Ctor = (mod as unknown as { default?: new (opts: Record<string, unknown>) => IGGraph; Graph?: new (opts: Record<string, unknown>) => IGGraph }).default ?? (mod as unknown as { Graph: new (opts: Record<string, unknown>) => IGGraph }).Graph;\r\n if (!Ctor) throw new Error('graphology: no Graph constructor found');\r\n return Ctor;\r\n}\r\n\r\nasync function loadLouvain(): Promise<(graph: unknown, opts?: Record<string, unknown>) => Record<string, number>> {\r\n const mod = await import('graphology-communities-louvain');\r\n const fn = (mod as unknown as { default?: (graph: unknown, opts?: Record<string, unknown>) => Record<string, number> }).default;\r\n if (!fn) throw new Error('graphology-communities-louvain: no default export found');\r\n return fn;\r\n}\r\n\r\nexport class GraphologyAdapter {\r\n private graph: IGGraph | null = null;\r\n\r\n async buildGraph(nodes: GraphNode[], edges: GraphEdge[]): Promise<void> {\r\n const GraphCtor = await loadGraphCtor();\r\n const g = new GraphCtor({ multi: false, type: 'undirected' });\r\n\r\n for (const node of nodes) {\r\n g.addNode(node.id, { type: node.type, label: node.label, filePath: node.filePath });\r\n }\r\n\r\n for (const edge of edges) {\r\n if (g.hasNode(edge.source) && g.hasNode(edge.target) && !g.hasEdge(edge.source, edge.target)) {\r\n g.addEdge(edge.source, edge.target, { type: edge.type, weight: edge.weight ?? 1 });\r\n }\r\n }\r\n\r\n this.graph = g;\r\n log.info(`built graph: ${g.order} nodes, ${g.size} edges`);\r\n }\r\n\r\n async detectCommunities(): Promise<CommunityResult[]> {\r\n if (!this.graph) return [];\r\n const louvain = await loadLouvain();\r\n const communities = louvain(this.graph, { resolution: 1.0 });\r\n const results: CommunityResult[] = [];\r\n for (const [nodeId, community] of Object.entries(communities)) {\r\n results.push({ nodeId, community });\r\n }\r\n log.info(`detected ${new Set(results.map(r => r.community)).size} communities`);\r\n return results;\r\n }\r\n\r\n computeDegreeCentrality(): CentralityResult[] {\r\n if (!this.graph) return [];\r\n const results: CentralityResult[] = [];\r\n const maxDegree = this.graph.order - 1;\r\n this.graph.forEachNode((nodeId: string) => {\r\n const degree = this.graph!.degree(nodeId);\r\n const centrality = maxDegree > 0 ? degree / maxDegree : 0;\r\n results.push({ nodeId, centrality });\r\n });\r\n return results.sort((a, b) => b.centrality - a.centrality);\r\n }\r\n\r\n bfs(startNodeId: string, maxDepth?: number): string[] {\r\n if (!this.graph || !this.graph.hasNode(startNodeId)) return [];\r\n const visited = new Set<string>();\r\n const queue: Array<{ id: string; depth: number }> = [{ id: startNodeId, depth: 0 }];\r\n const result: string[] = [];\r\n\r\n while (queue.length > 0) {\r\n const { id, depth } = queue.shift()!;\r\n if (visited.has(id)) continue;\r\n if (maxDepth !== undefined && depth > maxDepth) continue;\r\n visited.add(id);\r\n result.push(id);\r\n\r\n this.graph.forEachNeighbor(id, (neighbor: string) => {\r\n if (!visited.has(neighbor)) {\r\n queue.push({ id: neighbor, depth: depth + 1 });\r\n }\r\n });\r\n }\r\n\r\n return result;\r\n }\r\n\r\n shortestPath(fromId: string, toId: string): string[] | null {\r\n if (!this.graph || !this.graph.hasNode(fromId) || !this.graph.hasNode(toId)) return null;\r\n if (fromId === toId) return [fromId];\r\n\r\n const visited = new Set<string>([fromId]);\r\n const queue: Array<{ id: string; path: string[] }> = [{ id: fromId, path: [fromId] }];\r\n let found: string[] | null = null;\r\n\r\n while (queue.length > 0 && !found) {\r\n const { id, path } = queue.shift()!;\r\n this.graph.forEachNeighbor(id, (neighbor: string) => {\r\n if (visited.has(neighbor) || found) return;\r\n visited.add(neighbor);\r\n const newPath = [...path, neighbor];\r\n if (neighbor === toId) {\r\n found = newPath;\r\n return;\r\n }\r\n queue.push({ id: neighbor, path: newPath });\r\n });\r\n }\r\n\r\n return found;\r\n }\r\n\r\n findConnectedComponents(): string[][] {\r\n if (!this.graph) return [];\r\n const visited = new Set<string>();\r\n const components: string[][] = [];\r\n\r\n this.graph.forEachNode((nodeId: string) => {\r\n if (visited.has(nodeId)) return;\r\n const component: string[] = [];\r\n const stack = [nodeId];\r\n while (stack.length > 0) {\r\n const id = stack.pop()!;\r\n if (visited.has(id)) continue;\r\n visited.add(id);\r\n component.push(id);\r\n this.graph!.forEachNeighbor(id, (neighbor: string) => {\r\n if (!visited.has(neighbor)) stack.push(neighbor);\r\n });\r\n }\r\n components.push(component);\r\n });\r\n\r\n return components.sort((a, b) => b.length - a.length);\r\n }\r\n\r\n findOrphanNodes(): string[] {\r\n if (!this.graph) return [];\r\n const orphans: string[] = [];\r\n this.graph.forEachNode((nodeId: string) => {\r\n if (this.graph!.degree(nodeId) === 0) orphans.push(nodeId);\r\n });\r\n return orphans;\r\n }\r\n\r\n findLeafNodes(): string[] {\r\n if (!this.graph) return [];\r\n const leaves: string[] = [];\r\n this.graph.forEachNode((nodeId: string) => {\r\n if (this.graph!.degree(nodeId) === 1) leaves.push(nodeId);\r\n });\r\n return leaves;\r\n }\r\n\r\n findHubNodes(threshold = 10): string[] {\r\n if (!this.graph) return [];\r\n const hubs: string[] = [];\r\n this.graph.forEachNode((nodeId: string) => {\r\n if (this.graph!.degree(nodeId) >= threshold) hubs.push(nodeId);\r\n });\r\n return hubs.sort((a, b) => this.graph!.degree(b) - this.graph!.degree(a));\r\n }\r\n\r\n clear(): void {\r\n if (this.graph) {\r\n this.graph.clear();\r\n this.graph = null;\r\n }\r\n }\r\n}","/**\n * 所有权/知识视图(研究方案 §4 的 $\\Pi_{\\text{own}}$ 维)的**纯函数层**。\n *\n * 定义:以 `git blame` 为数据源,把「符号 → 作者」映射出来,进而支撑 bus factor、\n * 知识孤岛等度量。codeknow 的 `owners` 命令展示了这一视图的实用价值。\n *\n * ## 数据来源\n *\n * 宿主用 `git blame --porcelain <file>` 采集逐行作者事实,本模块负责:\n * ① 解析 porcelain 原始输出(`parseGitBlamePorcelain`);\n * ② 按归一化文件路径分组(`groupBlameByFile`);\n * ③ 与 `GraphNode` 对齐,产出「作者 → 符号」映射(`buildOwnershipMap`);\n * ④ 计算 bus factor(`computeBusFactor`)。\n *\n * **刻意不新建存储层**:宿主每次查询现跑一次 blame,与 `gitChurn.ts` 的\n * 「短 TTL 缓存 + 失败可见」同一套取舍;这里只做纯函数,单测不必真跑 git。\n *\n * ## 证据纪律\n *\n * - 节点没有 `lineNumber` 时,归到该文件最后一行 blame 的作者(文件最新改动者),\n * 并在 `reason` 里说明用了「文件级近似」——不做无根据的猜测。\n * - 文件在 blame 里没有记录(例如新文件)时该节点**不被归属**;`attributedCount`\n * 会小于 `nodeCount`,调用方据此知道覆盖不全,而不是看到一个看起来完整的假映射。\n *\n * @module shared/ownership\n */\n\nimport type { GraphNode } from '../types/index.ts';\nimport { normalizeChurnPath } from './churn.ts';\n\n/** `git blame --porcelain` 的一行记录(已抽出字段)。 */\nexport interface BlameLine {\n /** 提交 hash。 */\n commit: string;\n /** 该行在文件中的最终行号(1-based)。 */\n line: number;\n /** 作者名。 */\n author: string;\n /** 作者邮箱(已去掉尖括号)。 */\n email: string;\n /** 归一化文件路径。 */\n filename: string;\n}\n\n/** 单个作者的归属汇总。 */\nexport interface OwnershipEntry {\n author: string;\n email: string;\n nodeIds: string[];\n files: string[];\n lineCount: number;\n}\n\n/** `buildOwnershipMap` 的结果。 */\nexport interface OwnershipReport {\n /** blame 数据是否可用(与「是否有节点被归属」是两件事)。 */\n available: boolean;\n nodeCount: number;\n /** 成功归属到作者的节点数。 */\n attributedCount: number;\n authorCount: number;\n entries: OwnershipEntry[];\n /** nodeId → 作者名。 */\n nodeLabels: Record<string, string>;\n reason?: string;\n}\n\n/** `computeBusFactor` 的结果。 */\nexport interface BusFactorResult {\n /** 覆盖 threshold 所需的最少作者数;无数据为 0。 */\n busFactor: number;\n /** 累计覆盖的行数。 */\n covered: number;\n /** 参与统计的总行数。 */\n total: number;\n available: boolean;\n reason?: string;\n}\n\n/**\n * 解析 `git blame --porcelain <file>` 的原始输出。\n *\n * porcelain 形状(每个记录):\n *\n * ```\n * <sha> <srcLine> <dstLine> <count>\\n\n * author <name>\\n\n * author-mail <<email>>\\n\n * author-time <unix>\\n\n * ...\n * filename <path>\\n\n * \\n\n * <行内容>\\n\n * ```\n *\n * 状态机:遇到记录头开启新记录;随后收集头部字段直到空行;空行后是行内容,跳过。\n * 刻意不用「按空行切分」——`author-mail` 等字段的值本身不含空行,但重命名残留行\n * 可能让朴素切分错位;逐行状态机对字段顺序更宽容。\n *\n * 纯函数:不抛异常;解析不出记录头时跳过该行。\n */\nexport function parseGitBlamePorcelain(raw: string): BlameLine[] {\n const lines = String(raw ?? '').split(/\\r?\\n/);\n const out: BlameLine[] = [];\n let index = 0;\n\n while (index < lines.length) {\n const header = /^([0-9a-f]{7,40}) \\d+ (\\d+) \\d+/.exec(lines[index] ?? '');\n if (!header) {\n index += 1;\n continue;\n }\n const commit = header[1] as string;\n const line = Number(header[2]);\n index += 1;\n\n let author = '';\n let email = '';\n let filename = '';\n // cycle-146:字段循环的硬边界是 tab 前缀 content 行(porcelain 头块与行内容\n // 之间没有空行;旧「等到空行」的假设在拼接输入上把后续记录头整个吞掉)。\n while (index < lines.length) {\n const field = lines[index] ?? '';\n if (field === '' || field.startsWith('\\t')) break;\n if (field.startsWith('author ')) author = field.slice('author '.length);\n else if (field.startsWith('author-mail ')) email = field.slice('author-mail '.length).replace(/^<|>$/g, '');\n else if (field.startsWith('filename ')) filename = normalizeChurnPath(field.slice('filename '.length));\n index += 1;\n }\n // content 行本身(tab 前缀;缺失容忍:截断输入的尾部可能只剩头块)。\n if (index < lines.length && (lines[index] ?? '').startsWith('\\t')) index += 1;\n\n out.push({ commit, line, author, email, filename });\n }\n\n return out;\n}\n\n/** 把逐行 blame 按归一化文件路径分组。 */\nexport function groupBlameByFile(lines: readonly BlameLine[]): Record<string, BlameLine[]> {\n const byFile: Record<string, BlameLine[]> = {};\n for (const record of lines) {\n if (!record.filename) continue;\n const bucket = byFile[record.filename];\n if (bucket) bucket.push(record);\n else byFile[record.filename] = [record];\n }\n return byFile;\n}\n\n/**\n * 把 blame 事实与图节点对齐,产出作者 → 符号映射。\n *\n * 归属规则:\n * - 有 `lineNumber` 且该文件有对应行 → 精确归属;\n * - 有 `lineNumber` 但找不到对应行 → 归到该文件最后一行 blame 的作者(近似);\n * - 没有 `lineNumber` → 同样归到最后一行 blame 的作者(文件级近似)。\n *\n * 两种近似都会在 `reason` 里留痕,避免调用方把近似当成精确证据。\n */\nexport function buildOwnershipMap(\n nodes: readonly GraphNode[],\n blameByFile: Readonly<Record<string, BlameLine[]>> | null | undefined,\n): OwnershipReport {\n if (!blameByFile || Object.keys(blameByFile).length === 0) {\n return {\n available: false,\n nodeCount: nodes.length,\n attributedCount: 0,\n authorCount: 0,\n entries: [],\n nodeLabels: {},\n reason: '未提供 git blame 数据',\n };\n }\n\n const authors = new Map<string, OwnershipEntry>();\n const nodeLabels: Record<string, string> = {};\n let attributedCount = 0;\n let approximated = 0;\n\n for (const node of nodes) {\n const key = normalizeChurnPath(node.filePath);\n const fileLines = blameByFile[key];\n if (!fileLines || fileLines.length === 0) continue;\n\n const exact = node.lineNumber === undefined\n ? undefined\n : fileLines.find((record) => record.line === node.lineNumber);\n const match = exact ?? fileLines[fileLines.length - 1];\n if (!match) continue;\n if (!exact) approximated += 1;\n\n const author = match.author || '(未知作者)';\n let entry = authors.get(author);\n if (!entry) {\n entry = { author, email: match.email, nodeIds: [], files: [], lineCount: 0 };\n authors.set(author, entry);\n }\n entry.nodeIds.push(String(node.id));\n if (!entry.files.includes(key)) entry.files.push(key);\n entry.lineCount += 1;\n nodeLabels[String(node.id)] = author;\n attributedCount += 1;\n }\n\n const entries = [...authors.values()].sort((a, b) => b.lineCount - a.lineCount || a.author.localeCompare(b.author));\n const reason = approximated > 0 ? `${approximated} 个节点使用了文件级近似归属` : undefined;\n\n return {\n available: true,\n nodeCount: nodes.length,\n attributedCount,\n authorCount: entries.length,\n entries,\n nodeLabels,\n ...(reason === undefined ? {} : { reason }),\n };\n}\n\n/**\n * 计算 bus factor:覆盖 `threshold` 比例行数所需的最少作者数。\n *\n * 缺 blame 数据时返回 `available: false`(不编造「1 个作者」);行数为 0 同样拒绝。\n * 作者按归属行数降序累加,返回刚好跨过阈值的那一位的作者数。\n */\nexport function computeBusFactor(\n nodes: readonly GraphNode[],\n blameByFile: Readonly<Record<string, BlameLine[]>> | null | undefined,\n threshold = 0.5,\n): BusFactorResult {\n if (!blameByFile || Object.keys(blameByFile).length === 0) {\n return { busFactor: 0, covered: 0, total: 0, available: false, reason: '未提供 git blame 数据' };\n }\n const report = buildOwnershipMap(nodes, blameByFile);\n const entries = report.entries.filter((entry) => entry.lineCount > 0);\n const total = entries.reduce((sum, entry) => sum + entry.lineCount, 0);\n if (total === 0) {\n return { busFactor: 0, covered: 0, total: 0, available: false, reason: '没有任何节点可归属' };\n }\n\n let covered = 0;\n let busFactor = 0;\n for (const entry of entries) {\n covered += entry.lineCount;\n busFactor += 1;\n if (covered / total >= threshold) break;\n }\n\n return { busFactor, covered, total, available: true };\n}\n","/**\n * 璇玑图研究方案 §4 的 **Π 视图划分函数**(cycle-138)。\n *\n * ## 它解决什么\n *\n * §4 把图谱定义为 $\\\\mathcal{G} = (V, E, \\\\Sigma, \\\\mathcal{T}, \\\\Pi)$,其中\n * $\\\\Pi: V \\\\cup E \\\\to 2^{\\\\mathcal{V}_{\\\\text{view}}}$ 是**视图划分函数**,\n * 把每个节点/边映射到它所属的视图集合。\n *\n * cycle-137 之前,四个视图(structural / temporal / ownership / domain)只是\n * `graph_slice` 的**经验划分**:同一个视图名在 `domain.ts` 的 `GRAPH_SLICE_VIEWS`\n * 与各处的切片逻辑里各写一份,没有一份正式的「某个节点属于哪些视图」的映射。\n *\n * 本模块把这条映射**正式化**:\n * - `ViewName` 是唯一的视图名集合(单一真源);\n * - `partitionByView(input)` 是正式的 Π 函数:给定图 + 各视图的输入事实,\n * 返回每个节点/边属于哪些视图;\n * - `graph_slice` 与 `graph_views` 工具**共用**这份定义,不再各写一份。\n *\n * ## 为什么「结构视图」恒属于所有节点\n *\n * 结构视图是**基图**:所有节点与边都属于它。其余三个视图是**叠加**的:\n * 一个节点是否属于时序视图,取决于它的文件是否被 commit 切片触及;\n * 是否属于所有权视图,取决于它是否有 blame 记录;\n * 是否属于领域视图,取决于它是否被分配到社区。\n *\n * 这正是 §4 的「多向自洽连接」:同一节点同时属于多个视图。\n *\n * ## 边级视图的口径\n *\n * §4 的 Π 覆盖 $V \\\\cup E$。边级口径:\n * - 时序:两端点都在时序切片里;\n * - 所有权:两端点都有 blame 记录;\n * - 领域:两端点在同一社区(跨社区边**不**属于任何单一领域视图——\n * 它是「桥」,属于结构视图但不属于某个领域)。\n *\n * 这是「边属于哪个领域」这个问题的诚实回答:跨社区边不属于任何领域。\n */\nimport type { GraphData, GraphEdge, GraphNode } from '../types/index.ts';\nimport { normalizeChurnPath } from './churn.ts';\n\n/** 四个正交视图(§4 的 $\\\\mathcal{V}_{\\\\text{view}}$)。 */\nexport type ViewName = 'structural' | 'temporal' | 'ownership' | 'domain';\n\n/** 视图名的**唯一真源**:`graph_slice` 的 `GRAPH_SLICE_VIEWS` 从它派生。 */\nexport const VIEW_NAMES: readonly ViewName[] = [\n 'structural',\n 'temporal',\n 'ownership',\n 'domain',\n] as const;\n\n/** 一个节点/边属于哪些视图。`ReadonlySet` 保证调用方不能改。 */\nexport type ViewMembership = ReadonlySet<ViewName>;\n\n/** Π 的输入:图 + 各视图的**事实**(缺省即「该视图不提供任何成员」)。 */\nexport interface ViewPartitionInput {\n /** 基图。 */\n data: Pick<GraphData, 'nodes' | 'edges'>;\n /**\n * 时序事实:被 commit 切片触及的文件(**归一化路径**集合)。\n *\n * 为什么是文件而不是节点:commit 切片是文件级的(`commitSliceGraph` 按\n * `node.filePath` 过滤)。一个文件里的所有节点共享同一个时序归属。\n */\n temporalFiles?: ReadonlySet<string>;\n /**\n * 所有权事实:有 blame 记录的节点 id 集合。\n */\n attributedNodeIds?: ReadonlySet<string>;\n /**\n * 领域事实:节点 id → 社区 id 的映射。\n *\n * 只有**被分配到某个社区**的节点才属于领域视图;未分配的节点不属于。\n */\n domainByNodeId?: ReadonlyMap<string, string>;\n}\n\n/** Π 的输出。 */\nexport interface ViewPartition {\n /** 每个节点属于哪些视图(`nodeViews` 的 key 是节点 id 字符串)。 */\n nodeViews: Map<string, ViewMembership>;\n /** 每个边属于哪些视图(`edgeViews` 的 key 是边 id 字符串)。 */\n edgeViews: Map<string, ViewMembership>;\n /** 每个视图的节点数(用于展示与「各视图是否真的有成员」的校验)。 */\n viewCounts: Record<ViewName, number>;\n /** 各视图的边数。 */\n edgeViewCounts: Record<ViewName, number>;\n}\n\n/** 构造一个视图集合(结构视图恒在)。 */\nfunction membership(extra: readonly ViewName[]): ViewMembership {\n return new Set<ViewName>(['structural', ...extra]);\n}\n\n/**\n * 正式的 Π 函数。\n *\n * 契约:\n * - **结构视图恒属所有节点与边**(它是基图);\n * - 时序视图 ⇒ 节点的文件在 `temporalFiles` 里;\n * - 所有权视图 ⇒ 节点 id 在 `attributedNodeIds` 里;\n * - 领域视图 ⇒ 节点在 `domainByNodeId` 里有社区;\n * - 边级口径见模块头注(两端点都在才算)。\n */\nexport function partitionByView(input: ViewPartitionInput): ViewPartition {\n const { data, temporalFiles, attributedNodeIds, domainByNodeId } = input;\n\n const nodeViews = new Map<string, ViewMembership>();\n const edgeViews = new Map<string, ViewMembership>();\n const viewCounts: Record<ViewName, number> = { structural: 0, temporal: 0, ownership: 0, domain: 0 };\n const edgeViewCounts: Record<ViewName, number> = { structural: 0, temporal: 0, ownership: 0, domain: 0 };\n\n for (const node of data.nodes) {\n const id = String(node.id);\n const extra: ViewName[] = [];\n if (temporalFiles && temporalFiles.has(normalizeChurnPath(node.filePath))) extra.push('temporal');\n if (attributedNodeIds && attributedNodeIds.has(id)) extra.push('ownership');\n if (domainByNodeId && domainByNodeId.has(id)) extra.push('domain');\n const views = membership(extra);\n nodeViews.set(id, views);\n for (const v of views) viewCounts[v] += 1;\n }\n\n for (const edge of data.edges) {\n const id = String(edge.id);\n const src = nodeViews.get(String(edge.source));\n const tgt = nodeViews.get(String(edge.target));\n const extra: ViewName[] = [];\n // 边级口径:两端点都在该视图里,边才算。\n if (src && tgt && src.has('temporal') && tgt.has('temporal')) extra.push('temporal');\n if (src && tgt && src.has('ownership') && tgt.has('ownership')) extra.push('ownership');\n if (src && tgt && src.has('domain') && tgt.has('domain')) {\n // 只有两端点在**同一**社区时,边才属于某个领域视图。\n // 跨社区边是「桥」:属于结构视图,不属于任何单一领域。\n const srcDomain = domainByNodeId?.get(String(edge.source));\n const tgtDomain = domainByNodeId?.get(String(edge.target));\n if (srcDomain !== undefined && srcDomain === tgtDomain) extra.push('domain');\n }\n const views = membership(extra);\n edgeViews.set(id, views);\n for (const v of views) edgeViewCounts[v] += 1;\n }\n\n return { nodeViews, edgeViews, viewCounts, edgeViewCounts };\n}\n\n/**\n * 把 `ViewPartition` 转成「节点 id → 视图名数组」的可序列化形状(工具输出用)。\n *\n * 为什么单独一个函数:`Map`/`Set` 不能直接 `JSON.stringify`,而工具的输出要经\n * `toJsonValue`。集中转换避免每个调用点各写一份。\n */\nexport function serializeViewPartition(partition: ViewPartition): {\n nodes: Array<{ id: string; views: string[] }>;\n edges: Array<{ id: string; views: string[] }>;\n viewCounts: Record<string, number>;\n edgeViewCounts: Record<string, number>;\n} {\n return {\n nodes: [...partition.nodeViews.entries()].map(([id, views]) => ({\n id,\n views: [...views].sort(),\n })),\n edges: [...partition.edgeViews.entries()].map(([id, views]) => ({\n id,\n views: [...views].sort(),\n })),\n viewCounts: { ...partition.viewCounts },\n edgeViewCounts: { ...partition.edgeViewCounts },\n };\n}\n\n/** 便于测试与工具使用的「空 Π」:只有结构视图。 */\nexport function emptyViewPartition(data: Pick<GraphData, 'nodes' | 'edges'>): ViewPartition {\n return partitionByView({ data });\n}\n\n/** 类型守卫:`GraphNode` / `GraphEdge` 的 id 转字符串(避免到处 `String()`)。 */\nexport function nodeIdOf(node: GraphNode): string {\n return String(node.id);\n}\n\nexport function edgeIdOf(edge: GraphEdge): string {\n return String(edge.id);\n}\n","/**\n * 领域视图(研究方案 §4 的 $\\Pi_{\\text{dom}}$ 维)与**统一切片接口** `graphSlice`。\n *\n * ## 领域视图\n *\n * 通过社区发现把结构图投影到功能域/业务域:复用 `GraphologyAdapter` 已有的 Louvain\n * 检测,把每个节点映射到一个领域标签。标签用**规范序**(按社区内最小 nodeId 字典序\n * 编号 `domain-0`、`domain-1` …)而不是 Louvain 的内部编号,因此同一输入无论\n * Louvain 内部如何编号都得到稳定标签——否则可视化每次刷新都会换色。\n *\n * ## 统一切片接口 `graphSlice(view, granularity, focus?)`\n *\n * 研究方案 §7.1 把它列为 6 个核心工具之一。它把四个正交视图收敛成**一个**入口:\n *\n * - `structural`:AST 符号与静态关系(结构视图 $\\mathcal{V}_{\\text{struct}}$);\n * - `temporal`:commit 切片 + 边挥发度(时序视图 $\\mathcal{T}$,见 `temporal.ts`);\n * - `ownership`:git blame 作者归属 + bus factor(所有权视图 $\\Pi_{\\text{own}}$);\n * - `domain`:Louvain 社区 → 领域标签(领域视图 $\\Pi_{\\text{dom}}$)。\n *\n * 三个维度正交组合:`view` 决定**标注什么**,`granularity` 决定**聚合到哪一层**\n * (`repo` 原样 / `file` 折叠成文件节点 / `symbol` 符号级),`focus` 决定**切哪一块**\n * (按 nodeId/label 取 1 跳邻域,或按文件路径取整文件)。\n *\n * **失败必须可见**:`available: false` + `reason`,而不是返回一份看起来完整、\n * 其实缺了时序/作者维度的图。`metadata.viewDataAvailable` 单独标记「视图专属数据」\n * 是否到位,因为「图切出来了」与「标注齐了」是两件事。\n *\n * @module shared/domain\n */\n\nimport type { GraphData, GraphEdge, GraphNode } from '../types/index.ts';\nimport { EdgeId, NodeId } from '../types/index.ts';\nimport { GraphologyAdapter } from './GraphologyAdapter.ts';\nimport { normalizeChurnPath } from './churn.ts';\nimport { commitSliceGraph, edgeVolatility, type CommitHistory, type TemporalSliceResult } from './temporal.ts';\nimport { buildOwnershipMap, type BlameLine } from './ownership.ts';\nimport { scoped } from './Logger.ts';\nimport { VIEW_NAMES, type ViewName } from './views.ts';\n\nconst log = scoped('domain');\n\n/**\n * 视图类型。\n *\n * cycle-138 起从 `src/shared/views.ts` 的 `VIEW_NAMES` **派生**,不再是本文件里\n * 再写一份字面量——四视图的名单从此只有一个真源(§4 的 Π 视图划分函数)。\n */\nexport type GraphSliceView = ViewName;\n\n/** 聚合粒度。 */\nexport type GraphSliceGranularity = 'repo' | 'file' | 'symbol';\n\n/** `graphSlice` 的请求。 */\nexport interface GraphSliceRequest {\n view: GraphSliceView;\n granularity?: GraphSliceGranularity;\n /** nodeId / label(取 1 跳邻域)或文件路径(取整文件)。 */\n focus?: string;\n /** 时序视图所需的 commit 历史。 */\n commitHistory?: CommitHistory | null;\n /** 所有权视图所需的 blame 分组。 */\n blameByFile?: Readonly<Record<string, BlameLine[]>> | null;\n /** 参考时间(unix 毫秒)。 */\n now?: number;\n /** focus 邻域的节点上限(防御高扇出节点把整张图拉进来)。 */\n maxNeighbors?: number;\n}\n\n/** `graphSlice` 的结果。 */\nexport interface GraphSliceResult {\n view: GraphSliceView;\n granularity: GraphSliceGranularity;\n nodes: GraphNode[];\n edges: GraphEdge[];\n /** nodeId → 视图标签(作者名 / 领域名 / 变更次数)。 */\n labels: Record<string, string>;\n /** 切片本身是否切出来了。 */\n available: boolean;\n /** 视图专属标注是否到位(区别于「图切出来了」)。 */\n viewDataAvailable: boolean;\n metadata: Record<string, unknown>;\n reason?: string;\n}\n\n/** focus 邻域默认上限。 */\n/** focus 邻域的默认上限(cycle-138 起导出,供 `graph_views` 复用同一口径)。 */\nexport const DEFAULT_MAX_NEIGHBORS = 200;\n\n/** 构造一个「没切出来」的结果:`available: false` + 原因,不编造图。 */\nfunction emptyResult(view: GraphSliceView, granularity: GraphSliceGranularity, reason: string): GraphSliceResult {\n return {\n view,\n granularity,\n nodes: [],\n edges: [],\n labels: {},\n available: false,\n viewDataAvailable: false,\n metadata: { nodeCount: 0, edgeCount: 0 },\n reason,\n };\n}\n\n/** 建无向邻接表(focus 邻域用;与 `AdjacencyList` 的有向版不同,这里双向)。 */\nfunction buildUndirectedAdjacency(edges: readonly GraphEdge[]): Map<string, string[]> {\n const adjacency = new Map<string, string[]>();\n for (const edge of edges) {\n const s = String(edge.source);\n const t = String(edge.target);\n const sBucket = adjacency.get(s);\n if (sBucket) { if (!sBucket.includes(t)) sBucket.push(t); } else adjacency.set(s, [t]);\n const tBucket = adjacency.get(t);\n if (tBucket) { if (!tBucket.includes(s)) tBucket.push(s); } else adjacency.set(t, [s]);\n }\n return adjacency;\n}\n\n/** 按 keep 集合过滤边(两端都在集合内才保留)。 */\nfunction filterEdgesByIds(edges: readonly GraphEdge[], keep: ReadonlySet<string>): GraphEdge[] {\n return edges.filter((edge) => keep.has(String(edge.source)) && keep.has(String(edge.target)));\n}\n\n/**\n * 按 `focus` 取子图。\n *\n * 优先把 focus 当作 nodeId/label:命中则取该节点 + 1 跳邻域(受 `maxNeighbors` 上限\n * 保护,否则一个 hub 会把整张图拉进来)。没命中再当作文件路径:取该文件下全部节点。\n * 两者都没命中 → 空结果(原因由调用方写)。\n */\n/** 取 focus 的 1-hop 邻域(或整图)。cycle-138 起导出,供 `graph_views` 复用。 */\nexport function focusNeighborhood(\n nodes: readonly GraphNode[],\n edges: readonly GraphEdge[],\n focus: string | undefined,\n maxNeighbors: number,\n): { nodes: GraphNode[]; edges: GraphEdge[] } {\n if (!focus) return { nodes: [...nodes], edges: [...edges] };\n\n const matched = nodes.filter((node) => String(node.id) === focus || node.label === focus);\n if (matched.length > 0) {\n const adjacency = buildUndirectedAdjacency(edges);\n const keep = new Set<string>();\n for (const node of matched) {\n if (keep.size >= maxNeighbors) break;\n keep.add(String(node.id));\n for (const neighbor of adjacency.get(String(node.id)) ?? []) {\n if (keep.size >= maxNeighbors) break;\n keep.add(neighbor);\n }\n }\n return { nodes: nodes.filter((node) => keep.has(String(node.id))), edges: filterEdgesByIds(edges, keep) };\n }\n\n const normalized = normalizeChurnPath(focus);\n const inFile = nodes.filter((node) => normalizeChurnPath(node.filePath) === normalized);\n if (inFile.length === 0) return { nodes: [], edges: [] };\n const keep = new Set(inFile.map((node) => String(node.id)));\n return { nodes: nodes.filter((node) => keep.has(String(node.id))), edges: filterEdgesByIds(edges, keep) };\n}\n\n/**\n * 把符号图折叠成文件级图:每个文件一个 `module` 节点,跨文件边聚合成一条\n * `dependency` 边(同向去重)。这是「文件级语义缩放」的最小实现。\n */\nfunction collapseToFiles(\n nodes: readonly GraphNode[],\n edges: readonly GraphEdge[],\n): { nodes: GraphNode[]; edges: GraphEdge[] } {\n const byFile = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const key = normalizeChurnPath(node.filePath) || String(node.id);\n const bucket = byFile.get(key);\n if (bucket) bucket.push(node);\n else byFile.set(key, [node]);\n }\n\n const idByFile = new Map<string, NodeId>();\n const fileNodes: GraphNode[] = [];\n for (const [file, members] of byFile) {\n const id = NodeId(`file:${file}`);\n idByFile.set(file, id);\n fileNodes.push({\n id,\n label: file,\n type: 'module',\n filePath: file,\n properties: { memberCount: members.length },\n });\n }\n\n const nodeToFile = new Map<string, string>();\n for (const [file, members] of byFile) {\n for (const member of members) nodeToFile.set(String(member.id), file);\n }\n\n const seen = new Set<string>();\n const fileEdges: GraphEdge[] = [];\n for (const edge of edges) {\n const sourceFile = nodeToFile.get(String(edge.source));\n const targetFile = nodeToFile.get(String(edge.target));\n if (!sourceFile || !targetFile || sourceFile === targetFile) continue;\n const key = `${sourceFile}->${targetFile}`;\n if (seen.has(key)) continue;\n seen.add(key);\n fileEdges.push({\n id: EdgeId(`fileedge:${sourceFile}->${targetFile}`),\n source: idByFile.get(sourceFile) as NodeId,\n target: idByFile.get(targetFile) as NodeId,\n type: 'dependency',\n properties: {},\n });\n }\n\n return { nodes: fileNodes, edges: fileEdges };\n}\n\n/** 取一个文件路径的顶层目录(领域名的人类可读来源)。 */\nfunction topDirectory(filePath: string): string {\n const normalized = normalizeChurnPath(filePath);\n const slash = normalized.indexOf('/');\n return slash > 0 ? normalized.slice(0, slash) : normalized;\n}\n\n/** 字典序最小的字符串(用于社区规范排序,与 Louvain 内部编号无关)。 */\nfunction minString(values: readonly string[]): string {\n let min = '';\n for (const value of values) {\n if (min === '' || value < min) min = value;\n }\n return min;\n}\n\n/**\n * 用 Louvain 把节点映射到领域标签。\n *\n * 标签编号按「社区内最小 nodeId」字典序规范重排,因此**不依赖 Louvain 的内部编号**;\n * 没有被 Louvain 覆盖到的孤立节点各自成一个领域,保证「每个节点都有标签」。\n */\n/** 用 Louvain 社区检测给节点打领域标签。cycle-138 起导出,供 `graph_views` 复用。 */\nexport async function computeDomainLabels(\n nodes: readonly GraphNode[],\n edges: readonly GraphEdge[],\n): Promise<{ labels: Record<string, string>; domainNames: Record<string, string>; domainCount: number }> {\n const labels: Record<string, string> = {};\n const domainNames: Record<string, string> = {};\n if (nodes.length === 0) return { labels, domainNames, domainCount: 0 };\n\n const adapter = new GraphologyAdapter();\n await adapter.buildGraph([...nodes], [...edges]);\n const communities = await adapter.detectCommunities();\n adapter.clear();\n\n const groups = new Map<number, string[]>();\n for (const record of communities) {\n const bucket = groups.get(record.community);\n if (bucket) bucket.push(record.nodeId);\n else groups.set(record.community, [record.nodeId]);\n }\n\n const ordered = [...groups.entries()].sort((a, b) => minString(a[1] ?? []).localeCompare(minString(b[1] ?? [])));\n\n const nodeById = new Map(nodes.map((node) => [String(node.id), node]));\n ordered.forEach(([, members], index) => {\n const label = `domain-${index}`;\n const representative = members.map((id) => nodeById.get(id)).find((node) => node !== undefined);\n domainNames[label] = representative ? topDirectory(representative.filePath) : 'unknown';\n for (const member of members) labels[member] = label;\n });\n\n // 孤立节点(Louvain 可能不覆盖)各自成一个领域,避免出现无标签节点。\n let next = ordered.length;\n for (const node of nodes) {\n const id = String(node.id);\n if (labels[id] !== undefined) continue;\n const label = `domain-${next}`;\n domainNames[label] = topDirectory(node.filePath);\n labels[id] = label;\n next += 1;\n }\n\n log.debug('domains computed', { nodes: nodes.length, domainCount: next });\n return { labels, domainNames, domainCount: next };\n}\n\n/** 计算时序视图的节点标注:每个节点 = 其文件在历史中被触及的提交数。 */\nfunction temporalNodeLabels(nodes: readonly GraphNode[], history: CommitHistory): Record<string, string> {\n const churn = new Map<string, number>();\n for (const slice of history.slices) {\n for (const file of slice.files) churn.set(file, (churn.get(file) ?? 0) + 1);\n }\n const labels: Record<string, string> = {};\n for (const node of nodes) {\n labels[String(node.id)] = String(churn.get(normalizeChurnPath(node.filePath)) ?? 0);\n }\n return labels;\n}\n\n/**\n * 统一切片接口。\n *\n * 流程:`focus` 取邻域 → 时序视图先做 commit 切片 → `granularity` 折叠 →\n * 按 `view` 计算标注。任一步的数据缺失都走**显式降级**(`available` /\n * `viewDataAvailable` / `reason`),绝不静默返回缺维度的图。\n */\nexport async function graphSlice(\n data: Pick<GraphData, 'nodes' | 'edges'>,\n request: GraphSliceRequest,\n): Promise<GraphSliceResult> {\n const view = request.view;\n const granularity = request.granularity ?? 'symbol';\n const maxNeighbors = request.maxNeighbors ?? DEFAULT_MAX_NEIGHBORS;\n\n let base = focusNeighborhood(data.nodes, data.edges, request.focus, maxNeighbors);\n if (base.nodes.length === 0) {\n return emptyResult(view, granularity, request.focus ? `focus \"${request.focus}\" 未命中任何节点` : '图为空');\n }\n\n // 时序视图先做 commit 切片;把切片结果留到后面写 metadata(from/to/sliceCount)。\n let temporalSlice: TemporalSliceResult | null = null;\n if (view === 'temporal') {\n const slice = commitSliceGraph(base, { now: request.now }, request.commitHistory ?? null);\n if (!slice.available) {\n return emptyResult(view, granularity, slice.reason ?? 'commit 历史不可用');\n }\n base = { nodes: slice.nodes, edges: slice.edges };\n temporalSlice = slice;\n }\n\n const collapsed = granularity === 'file' ? collapseToFiles(base.nodes, base.edges) : base;\n\n const metadata: Record<string, unknown> = {\n nodeCount: collapsed.nodes.length,\n edgeCount: collapsed.edges.length,\n };\n let labels: Record<string, string> = {};\n let viewDataAvailable = true;\n let reason: string | undefined;\n\n if (view === 'temporal') {\n const history = request.commitHistory;\n if (history && history.available) {\n labels = temporalNodeLabels(collapsed.nodes, history);\n const nodeFiles: Record<string, string> = {};\n for (const node of collapsed.nodes) nodeFiles[String(node.id)] = node.filePath;\n metadata.volatility = edgeVolatility(collapsed.edges, history, nodeFiles);\n metadata.commitRange = { from: temporalSlice?.from ?? null, to: temporalSlice?.to ?? null };\n metadata.sliceCount = temporalSlice?.sliceCount ?? history.slices.length;\n metadata.truncated = temporalSlice?.truncated ?? false;\n } else {\n viewDataAvailable = false;\n reason = 'commit 历史不可用:时序标注缺失(图本身仍按结构切出)';\n }\n } else if (view === 'ownership') {\n const report = buildOwnershipMap(collapsed.nodes, request.blameByFile ?? null);\n labels = report.nodeLabels;\n metadata.authorCount = report.authorCount;\n metadata.attributedCount = report.attributedCount;\n if (!report.available) {\n viewDataAvailable = false;\n reason = report.reason ?? 'git blame 数据不可用:作者标注缺失';\n } else if (report.attributedCount < collapsed.nodes.length) {\n reason = `${collapsed.nodes.length - report.attributedCount} 个节点在 blame 中无记录`;\n }\n } else if (view === 'domain') {\n const domain = await computeDomainLabels(collapsed.nodes, collapsed.edges);\n labels = domain.labels;\n metadata.domainCount = domain.domainCount;\n metadata.domainNames = domain.domainNames;\n }\n\n return {\n view,\n granularity,\n nodes: collapsed.nodes,\n edges: collapsed.edges,\n labels,\n available: true,\n viewDataAvailable,\n metadata,\n ...(reason === undefined ? {} : { reason }),\n };\n}\n\n/** 四个视图的合法集合(供工具参数校验与文档生成共用)。\n * cycle-138:从 `VIEW_NAMES` 派生,不再写第二份字面量。 */\nexport const GRAPH_SLICE_VIEWS: readonly GraphSliceView[] = [...VIEW_NAMES];\n\n/** 三个粒度的合法集合。 */\nexport const GRAPH_SLICE_GRANULARITIES: readonly GraphSliceGranularity[] = ['repo', 'file', 'symbol'] as const;\n","// Pure utility functions for graph tools — extracted from tools.ts to avoid\r\n// indirect adapter module loading in tests (v8 coverage module-isolation bug).\r\nimport type { GraphData } from './types/index.ts';\r\n\r\nexport function summarizeGraph(data: GraphData): string {\r\n const nodeByType = new Map<string, number>();\r\n const edgeByType = new Map<string, number>();\r\n for (const n of data.nodes) nodeByType.set(n.type, (nodeByType.get(n.type) ?? 0) + 1);\r\n for (const e of data.edges) edgeByType.set(e.type, (edgeByType.get(e.type) ?? 0) + 1);\r\n\r\n const nodeStats = [...nodeByType.entries()].map(([t, c]) => `${t}:${c}`).join(', ');\r\n const edgeStats = [...edgeByType.entries()].map(([t, c]) => `${t}:${c}`).join(', ');\r\n\r\n const topNodes = data.nodes.slice(0, 10)\r\n .map(n => ` • ${n.label} (${n.type}) @ ${n.filePath}:${n.lineNumber}`)\r\n .join('\\n');\r\n\r\n const topEdges = data.edges.slice(0, 10)\r\n .map(e => ` ${e.source} →${e.type}→ ${e.target}`)\r\n .join('\\n');\r\n\r\n return [\r\n `Graph: ${data.metadata.nodeCount} nodes [${nodeStats}], ${data.metadata.edgeCount} edges [${edgeStats}]`,\r\n topNodes ? `\\nTop nodes:\\n${topNodes}` : '',\r\n topEdges ? `\\nTop edges:\\n${topEdges}` : '',\r\n ].join('');\r\n}\r\n\r\nexport function normalizeImpact(raw: unknown): { affected: string[]; depth: number } | null {\r\n if (typeof raw === 'string') {\r\n try { raw = JSON.parse(raw) as unknown; } catch { /* JSON parse failed */ return null; }\r\n }\r\n if (!raw || typeof raw !== 'object') return null;\r\n const obj = raw as Record<string, unknown>;\r\n const list = obj.affected ?? obj.affectedNodes ?? obj.nodes;\r\n if (!Array.isArray(list)) return null;\r\n return {\r\n affected: list.map((it) => {\r\n if (typeof it === 'string') return it;\r\n const rec = it as Record<string, unknown>;\r\n return String(rec.name ?? rec.id ?? '?');\r\n }),\r\n depth: typeof obj.depth === 'number' ? obj.depth : 2,\r\n };\r\n}\r\n\r\nexport function pickBestMatch(raw: unknown, symbolId: string): Record<string, unknown> | null {\r\n let payload = raw;\r\n if (typeof payload === 'string') {\r\n try { payload = JSON.parse(payload) as unknown; } catch { /* JSON parse failed */ return null; }\r\n }\r\n const items = Array.isArray(payload)\r\n ? payload\r\n : (payload as Record<string, unknown>)?.results ?? (payload as Record<string, unknown>)?.nodes;\r\n if (!Array.isArray(items) || items.length === 0) return null;\r\n const wanted = symbolId.toLowerCase();\r\n const best = items.find((it) => {\r\n const rec = it as Record<string, unknown>;\r\n return String(rec.name ?? '').toLowerCase() === wanted || rec.id === symbolId;\r\n }) as Record<string, unknown> | undefined;\r\n const chosen = best ?? (items[0] as Record<string, unknown>);\r\n return {\r\n symbolId,\r\n name: chosen.name ?? chosen.qualified_name ?? symbolId,\r\n category: chosen.kind ?? 'unknown',\r\n file: chosen.filePath ?? chosen.file_path ?? '?',\r\n line: chosen.startLine ?? chosen.start_line ?? 0,\r\n signature: chosen.signature ?? null,\r\n };\r\n}\r\n\r\nexport function renderGraphStatus(_args: unknown, value: unknown) {\r\n const v = value as { status: string; nodeCount: number; edgeCount: number; sources?: Record<string, boolean> };\r\n const srcInfo = v.sources ? ` [codegraph:${v.sources.codegraph ? '✓' : '✗'} lens:${v.sources.lens ? '✓' : '✗'}]` : '';\r\n return [\r\n { type: 'text' as const, text: `Graph status: ${v.status} (${v.nodeCount} nodes, ${v.edgeCount} edges)${srcInfo}` },\r\n ];\r\n}\r\n\r\nexport function renderGraphData(_args: unknown, value: unknown) {\r\n const data = value as unknown as GraphData;\r\n return [{ type: 'text' as const, text: summarizeGraph(data) }];\r\n}\r\n\r\nexport function renderGraphSymbol(_args: unknown, value: unknown) {\r\n const v = value as { name?: string; file?: string; line?: number; category?: string; symbolId?: string } | null;\r\n if (!v) return [{ type: 'text' as const, text: 'Symbol not found.' }];\r\n return [{ type: 'text' as const, text: `${v.name ?? v.symbolId ?? '?'} (${v.category ?? 'unknown'}) @ ${v.file ?? '?'}:${v.line ?? '?'}` }];\r\n}\r\n\r\nexport function renderGraphImpact(_args: unknown, value: unknown) {\r\n const v = value as { affected?: string[]; depth?: number };\r\n const count = v.affected?.length ?? 0;\r\n const list = v.affected?.slice(0, 10).join(', ') ?? '';\r\n return [{ type: 'text' as const, text: `Impact: ${count} symbols affected (depth ${v.depth ?? 0})${list ? `\\n ${list}` : ''}` }];\r\n}","// Graph tool definitions: adapters + merger wrapped as DSH `defineTool` tools.\r\nimport type { Context } from '@deepseek-ai/cordis';\r\nimport { defineTool } from '@deepseek-ai/dsh-tools';\r\nimport { ToolCallId } from '@deepseek-ai/dsh-llm';\r\n\r\nimport type { CodeGraphAdapter } from './adapters/CodeGraphAdapter.ts';\r\nimport type { LensAdapter } from './adapters/LensAdapter.ts';\r\nimport { GraphDataMerger } from './merger/GraphDataMerger.ts';\r\nimport { computeGraphDiff } from './merger/DiffEngine.ts';\r\nimport type { AdapterResult, GraphData, GraphUpdatedEvent, GraphDataEvent, HealthScore, RuleViolation } from './types/index.ts';\r\nimport { RepoId as makeRepoId, NodeId } from './types/index.ts';\r\nimport { HealthScorer } from './shared/analysis/HealthScorer.ts';\r\nimport { RuleEngine, type RuleDefinition } from './shared/analysis/RuleEngine.ts';\r\nimport { buildForwardAdjacency } from './shared/analysis/AdjacencyList.ts';\r\nimport { XuanjiAdapterPool, formatPatternError, type AdapterEdgeType } from './shared/analysis/XuanjiAdapter.ts';\r\nimport { sliceGraphForTemporal, temporalXuanjiPool } from './shared/analysis/temporalEnumeration.ts';\r\n// ρ 语法错误的**类型判定**用 instanceof,不用 `e.name === 'PathPatternError'`:\r\n// name 是运行期字符串,重命名/minify/子类化都会让它静默失效;class 身份是编译期绑定的。\r\nimport { PathPatternError } from './xuanji/core/path-pattern.ts';\r\nimport { renderSubgraphText, selectSeeds, tokenizeQuery } from './xuanji/query/keyword-query.ts';\r\nimport { bfsShortestPath } from './shared/analysis/GraphBFS.ts';\r\nimport { scoped } from './shared/Logger.ts';\r\nimport {\r\n graphSlice as sliceGraph,\r\n focusNeighborhood,\r\n computeDomainLabels,\r\n GRAPH_SLICE_VIEWS,\r\n GRAPH_SLICE_GRANULARITIES,\r\n DEFAULT_MAX_NEIGHBORS,\r\n type GraphSliceView,\r\n type GraphSliceGranularity,\r\n type GraphSliceResult,\r\n} from './shared/domain.ts';\r\nimport { parseCommitLog, filterSlices, type CommitRange } from './shared/temporal.ts';\r\nimport { parseGitBlamePorcelain, groupBlameByFile, buildOwnershipMap } from './shared/ownership.ts';\r\nimport { partitionByView, serializeViewPartition } from './shared/views.ts';\r\nimport { filterEdgesByTemporalPredicate } from './shared/analysis/temporalEnumeration.ts';\r\nimport type { ApiCompatLayer } from './host/compat/ApiCompatLayer.ts';\r\nimport { CACHE, PATH_FINDER, TOOL_MESSAGES, API } from './shared/constants.ts';\r\nimport {\r\n summarizeGraph,\r\n normalizeImpact,\r\n pickBestMatch,\r\n renderGraphStatus,\r\n renderGraphData,\r\n renderGraphSymbol,\r\n renderGraphImpact,\r\n} from './graphUtils.ts';\r\n\r\n// Re-export pure functions for backward compatibility\r\nexport {\r\n summarizeGraph,\r\n normalizeImpact,\r\n pickBestMatch,\r\n renderGraphStatus,\r\n renderGraphData,\r\n renderGraphSymbol,\r\n renderGraphImpact,\r\n};\r\n\r\nconst log = scoped('tools');\r\n\r\ntype JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\r\n\r\nfunction toJsonValue<T>(value: T): JsonValue {\r\n return JSON.parse(JSON.stringify(value)) as JsonValue;\r\n}\r\n\r\n/** `graph_path_enumerate` 允许的边类型全集(Flow — 路径读法可选的 Σ_R)。 */\r\nconst PATH_EDGE_TYPE_DOMAIN: ReadonlySet<string> = new Set<AdapterEdgeType>([\r\n 'call',\r\n 'import',\r\n 'extend',\r\n 'implement',\r\n 'dependency',\r\n 'contains',\r\n 'association',\r\n 'aggregation',\r\n 'composition',\r\n]);\r\n\r\n/**\r\n * 解析 `edgeTypes` 逗号列表:只保留域内类型;未提供或全部非法时返回 null(用默认读法)。\r\n * 空集规则匹配不到任何边(璇玑图 §八),所以「全部非法」不能静默变成空集过滤。\r\n */\r\nfunction parseEdgeTypes(raw: string | undefined): readonly AdapterEdgeType[] | null {\r\n if (!raw) return null;\r\n const parsed = raw\r\n .split(',')\r\n .map((part) => part.trim())\r\n .filter((part): part is AdapterEdgeType => PATH_EDGE_TYPE_DOMAIN.has(part));\r\n return parsed.length > 0 ? parsed : null;\r\n}\r\n\r\n/** 把可选的数字参数夹紧到 [min, max];缺失/NaN 时用 fallback。 */\r\nfunction clampInt(value: number | undefined, min: number, max: number, fallback: number): number {\r\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\r\n return Math.min(max, Math.max(min, Math.trunc(value)));\r\n}\r\n\r\nexport type { UpstreamInvoker } from './adapters/IAdapter.ts';\r\nimport type { UpstreamInvoker } from './adapters/IAdapter.ts';\r\n\r\nconst GRAPH_CACHE_LIMIT = CACHE.GRAPH_CACHE_LIMIT;\r\n\r\nexport interface GraphToolDeps {\r\n codegraphAdapter: CodeGraphAdapter;\r\n lensAdapter: LensAdapter;\r\n merger: GraphDataMerger;\r\n /**\r\n * 原生代码图谱通道(cycle-127):目录结构 + TreeSitter 符号 + AST 边。\r\n * `null` = 宿主未注入文件系统能力(headless/受限环境),auto 降级链跳过该级。\r\n */\r\n nativeAdapter: NativeGraphAdapter | null;\r\n}\r\n\r\n/**\r\n * 原生图适配的最小契约(cycle-127)。\r\n *\r\n * 刻意用结构类型而非直接 import FileSystemAdapter:依赖方向是 tools → 契约,\r\n * 具体实现(Node fs 注入版)由宿主组装方决定,测试也可注入内存 fs 桩。\r\n * `fetchDataWithSymbols` 一次产出「目录树 + 符号 + 边」的完整原生图。\r\n */\r\nexport interface NativeGraphAdapter {\r\n readonly source: 'tree-sitter';\r\n fetchDataWithSymbols(repoId: string): Promise<AdapterResult>;\r\n}\r\n\r\nexport async function createGraphToolDeps(): Promise<GraphToolDeps> {\r\n const [{ CodeGraphAdapter: CGA }, { LensAdapter: LA }] = await Promise.all([\r\n import('./adapters/CodeGraphAdapter.ts'),\r\n import('./adapters/LensAdapter.ts'),\r\n ]);\r\n return {\r\n codegraphAdapter: new CGA(),\r\n lensAdapter: new LA(),\r\n merger: new GraphDataMerger(),\r\n // cycle-131 · P0 第二层修复:原生适配器走**模块级单例**(ensureNativeAdapter),\r\n // 不再依赖 createGraphTools 工厂内的 options.nativeWorkspaceRoot 组装——\r\n // 生产主路径(watcher / /scan / service / /init)调 fetchMergedGraph 不传 deps,\r\n // 缺省 createGraphToolDeps() 此前永远得到 nativeAdapter: null,原生层在宿主面\r\n // 零可达(真机取证:registry 37 能力全注册但 scan 0 节点 empty)。Node 运行时\r\n // 检测失败(非 Node / fs 不可用)才诚实降级 null。\r\n nativeAdapter: await ensureNativeAdapter(),\r\n };\r\n}\r\n\r\n/**\r\n * 原生图适配器的模块级单例(cycle-131 · P0 第二层修复)。\r\n *\r\n * 组装一次全 host 复用(Node fs + TreeSitterAdapter 各自内部已单例化 wasm);\r\n * 失败结果也缓存(null = 环境不支持,避免每次调用重试)。\r\n */\r\nlet nativeAdapterSingleton: NativeGraphAdapter | null | undefined;\r\nasync function ensureNativeAdapter(): Promise<NativeGraphAdapter | null> {\r\n if (nativeAdapterSingleton !== undefined) return nativeAdapterSingleton;\r\n nativeAdapterSingleton = null; // 失败缺省\r\n try {\r\n const [{ FileSystemAdapter: FSA }, { TreeSitterAdapter: TSA }, nodeFs] = await Promise.all([\r\n import('./adapters/FileSystemAdapter.ts'),\r\n import('./adapters/TreeSitterAdapter.ts'),\r\n import('node:fs'),\r\n ]);\r\n const fsAdapter = new FSA();\r\n fsAdapter.setFileSystem({\r\n readdir: (p) => nodeFs.readdirSync(p, { encoding: 'utf-8' }),\r\n readFile: (p) => nodeFs.readFileSync(p, 'utf-8'),\r\n stat: (p) => ({ isDirectory: nodeFs.statSync(p).isDirectory() }),\r\n });\r\n fsAdapter.setTreeSitterAdapter(new TSA());\r\n nativeAdapterSingleton = fsAdapter;\r\n log.info('native graph layer adapter ready (module singleton)');\r\n } catch (e) {\r\n log.warn('native graph layer init failed — native fallback disabled', { error: String(e) });\r\n }\r\n return nativeAdapterSingleton;\r\n}\r\n\r\nfunction cacheGraph(cache: Map<string, GraphData>, repoId: string, data: GraphData): void {\r\n cache.delete(repoId);\r\n cache.set(repoId, data);\r\n if (cache.size > GRAPH_CACHE_LIMIT) {\r\n const oldest = cache.keys().next().value;\r\n if (oldest !== undefined) cache.delete(oldest);\r\n }\r\n}\r\n\r\nexport async function fetchMergedGraph(\r\n invoke: UpstreamInvoker,\r\n repoId: string,\r\n source: 'codegraph' | 'lens' | 'both' | 'auto' = 'both',\r\n deps?: GraphToolDeps,\r\n): Promise<GraphData> {\r\n const d = deps ?? await createGraphToolDeps();\r\n const tasks: Promise<AdapterResult>[] = [];\r\n if (source === 'codegraph' || source === 'both' || source === 'auto') {\r\n tasks.push(d.codegraphAdapter.fetchData(repoId, invoke));\r\n }\r\n if (source === 'lens' || source === 'both' || source === 'auto') {\r\n tasks.push(d.lensAdapter.fetchData(repoId, invoke));\r\n }\r\n const settled = await Promise.allSettled(tasks);\r\n const results: AdapterResult[] = settled\r\n .filter((r): r is PromiseFulfilledResult<AdapterResult> => r.status === 'fulfilled')\r\n .map((r) => r.value);\r\n const merged = d.merger.merge(results, makeRepoId(repoId));\r\n // cycle-127 · T3:图层来源声明——上游任一通道有产出即 'upstream'(原生通道\r\n // 不再启用);否则进入降级链评估,最终标记见下方 native 分支与空态。\r\n (merged.metadata as GraphData['metadata']).graphLayer = merged.nodes.length > 0 ? 'upstream' : 'empty';\r\n // cycle-127 · T1:auto 降级链第三级——上游 DB 与 lens 双缺席时启用原生通道\r\n // (目录结构 + TreeSitter 符号 + AST 边)。此前该场景 merge 结果为空图,而\r\n // :134 的日志宣称「built-in analysis always available」——假承诺。原生通道\r\n // 只在**外部源双双缺席**时启用:上游在场时行为逐字节不变(兼容矩阵见规划 §3)。\r\n // cycle-128 · T2:第三级升级为「索引库优先」——原生 SQLite 索引(上游 schema\r\n // 兼容)可读时直接走既有 DB 分页读取(毫秒级),缺席才实时 batchScan 并回写。\r\n // cycle-131 · P0 修复:原生降级链的触发条件从 `source === 'auto'` 扩到\r\n // 「外部源双缺席」本身。此前生产主路径(watcher 槽填充 / /scan / service /\r\n // /init 共 7 处)硬编码 'both',而降级链只认 'auto' —— 原生层在真实宿主里\r\n // 从未可达(真机取证:上游双缺席工作区 graphLayer='empty' 0 节点,registry\r\n // 37 能力全注册但原生四轮交付零消费)。'both' 语义升级为「上游两源 + 缺席\r\n // 补位」:上游在场行为逐字节不变;'auto' 保持查询面显式全降级语义(含\r\n // graphLayer 评估),两者共用同一条降级链实现。\r\n const allowNativeFallback = merged.nodes.length === 0 && d.nativeAdapter !== null\r\n && (source === 'auto' || source === 'both');\r\n if (allowNativeFallback) {\r\n try {\r\n const [{ readGraphFromDbPaged }, { mapRowsToGraphNodes, mapRowsToGraphEdges }, { resolveNativeIndexPath, readNativeIndexMeta, writeNativeIndex }] = await Promise.all([\r\n import('./adapters/codeGraphDb.ts'),\r\n import('./adapters/codeGraphRows.ts'),\r\n import('./adapters/nativeIndex.ts'),\r\n ]);\r\n // ① 索引库优先:库在场且有 meta ⇒ 既有读侧函数分页读取(零改动兼容)。\r\n try {\r\n const indexPath = resolveNativeIndexPath(repoId);\r\n const meta = readNativeIndexMeta(repoId);\r\n if (meta) {\r\n const dbNodes: unknown[] = [];\r\n const dbEdges: unknown[] = [];\r\n let cursor: import('./adapters/codeGraphDb.ts').GraphPageCursor | null = null;\r\n for (;;) {\r\n const page = readGraphFromDbPaged(indexPath, cursor, 500);\r\n if (!page) break;\r\n dbNodes.push(...page.nodes);\r\n dbEdges.push(...page.edges);\r\n if (!page.nextCursor) break;\r\n cursor = page.nextCursor;\r\n }\r\n if (dbNodes.length > 0) {\r\n const validIds = new Set(dbNodes.map((n) => String((n as { id: unknown }).id)));\r\n const nodes = mapRowsToGraphNodes(dbNodes as Parameters<typeof mapRowsToGraphNodes>[0]);\r\n const edges = mapRowsToGraphEdges(dbEdges as Parameters<typeof mapRowsToGraphEdges>[0], validIds);\r\n const fromIndex = d.merger.merge([{ nodes, edges, source: 'tree-sitter', timestamp: meta.indexedAt }], makeRepoId(repoId));\r\n (fromIndex.metadata as GraphData['metadata']).graphLayer = 'native';\r\n (fromIndex.metadata as GraphData['metadata']).nativeIndex = { hit: true, indexedAt: meta.indexedAt, fingerprint: meta.fingerprint };\r\n log.info('fetchMergedGraph: native graph served from index', { nodes: fromIndex.nodes.length, edges: fromIndex.edges.length, indexedAt: meta.indexedAt });\r\n try {\r\n const { enhanceInheritanceDataCached } = await import('./host/inheritanceEnhancer.ts');\r\n await enhanceInheritanceDataCached(fromIndex, repoId);\r\n } catch (e) {\r\n log.warn('inheritance enhancement failed on native index graph', { error: String(e) });\r\n }\r\n return fromIndex;\r\n }\r\n }\r\n } catch (e) {\r\n log.warn('native index read failed — falling back to live scan', { error: String(e) });\r\n }\r\n // ② 实时扫描(库缺席/空库/读失败)。allowNativeFallback 已保证非 null,\r\n // 但 TS 无法跨块追踪——非空断言安全(块内无 await 前的置空路径)。\r\n const nativeAdapter = d.nativeAdapter as NativeGraphAdapter;\r\n const nativeResult = await nativeAdapter.fetchDataWithSymbols(repoId);\r\n if (nativeResult.nodes.length > 0) {\r\n // ③ 回写索引(写失败不阻塞图返回——索引是缓存层)。coverage 统计来自\r\n // fetchData 的 gitignore 裁决(AdapterResult.scanCoverage,缺席 = 旧调用方)。\r\n try {\r\n const { computeGraphFingerprint } = await import('./shared/graphFingerprint.ts');\r\n const fp = computeGraphFingerprint({ nodes: nativeResult.nodes, edges: nativeResult.edges, metadata: { repoId: makeRepoId(repoId), timestamp: 0, nodeCount: nativeResult.nodes.length, edgeCount: nativeResult.edges.length } } as GraphData);\r\n const cov = (nativeResult as { scanCoverage?: { gitignorePresent: boolean; gitignoreRules: number; ignoredEntries: number } }).scanCoverage;\r\n writeNativeIndex(repoId, fp, nativeResult.nodes, nativeResult.edges, cov ?? { gitignorePresent: false, gitignoreRules: 0, ignoredEntries: 0 });\r\n } catch (e) {\r\n log.warn('native index write-back failed', { error: String(e) });\r\n }\r\n const nativeMerged = d.merger.merge([nativeResult], makeRepoId(repoId));\r\n log.info('fetchMergedGraph: auto fallback to native graph layer (live scan)', {\r\n nodes: nativeMerged.nodes.length,\r\n edges: nativeMerged.edges.length,\r\n source: nativeResult.source,\r\n });\r\n // 继承增强同样作用于原生图(AST 推断边是它的核心输入)。\r\n try {\r\n const { enhanceInheritanceDataCached } = await import('./host/inheritanceEnhancer.ts');\r\n await enhanceInheritanceDataCached(nativeMerged, repoId);\r\n } catch (e) {\r\n log.warn('inheritance enhancement failed on native graph', { error: String(e) });\r\n }\r\n (nativeMerged.metadata as GraphData['metadata']).graphLayer = 'native';\r\n return nativeMerged;\r\n }\r\n } catch (e) {\r\n log.warn('native graph layer failed in fetchMergedGraph', { error: String(e) });\r\n }\r\n }\r\n if (source === 'auto' && merged.nodes.length === 0) {\r\n log.info('fetchMergedGraph: auto mode — external sources returned empty and no native graph layer available');\r\n }\r\n // cycle-131 · P0 修复的另一半:'both' 双缺席且原生不可用时也标记 empty(诚实空态\r\n // 与 auto 一致——graphLayer 已在上方统一标记)。\r\n // 继承数据增强(cycle-125 下沉):在此单一合并点注入,watcher(客户端 /data 槽\r\n // 填充)/ queryRoute / graph_data 工具三路自动受益——此前只接在 executeGraphData\r\n // (模型工具)里,客户端继承视图永远看不到增强边(cycle-122 贯通家族的客户端断环)。\r\n // 指纹键缓存:同指纹跳过 4.6s AST 扫描(enhanceInheritanceDataCached 内部管理)。\r\n try {\r\n const { enhanceInheritanceDataCached } = await import('./host/inheritanceEnhancer.ts');\r\n await enhanceInheritanceDataCached(merged, repoId);\r\n } catch (e) {\r\n log.warn('inheritance enhancement failed in fetchMergedGraph', { error: String(e) });\r\n }\r\n return merged;\r\n}\r\n\r\n\r\n// ── Extracted execute/render functions for testability ──────────────────\r\n\r\nexport function createInvoke(ctx: Context, requestTimeout: number = API.INVOKE_DEFAULT_TIMEOUT_MS, apiCompat?: ApiCompatLayer): UpstreamInvoker {\r\n return async (tool, args) => {\r\n let resolvedName = tool;\r\n if (apiCompat) {\r\n const mapped = apiCompat.getAvailableToolNameFor(ctx, tool);\r\n if (mapped) resolvedName = mapped;\r\n }\r\n try {\r\n const result = await ctx.tools.execute({\r\n callId: ToolCallId(`codegraph:${resolvedName}`),\r\n name: resolvedName,\r\n arguments: args,\r\n signal: AbortSignal.timeout(requestTimeout),\r\n });\r\n if (result.isError) return null;\r\n return result.value ?? null;\r\n } catch (e) {\r\n log.warn('createInvoke: tool execution failed', { tool: resolvedName, error: e });\r\n return null;\r\n }\r\n };\r\n}\r\n\r\n\r\nexport async function executeGraphStatus(args: { repoId: string }, invoke: UpstreamInvoker, deps: GraphToolDeps) {\r\n const [cgResult, lensResult] = await Promise.allSettled([\r\n deps.codegraphAdapter.fetchData(args.repoId, invoke),\r\n deps.lensAdapter.fetchData(args.repoId, invoke),\r\n ]);\r\n const results: AdapterResult[] = [cgResult, lensResult]\r\n .filter((r): r is PromiseFulfilledResult<AdapterResult> => r.status === 'fulfilled')\r\n .map((r) => r.value);\r\n const data = deps.merger.merge(results, makeRepoId(args.repoId));\r\n return {\r\n status: data.nodes.length > 0 ? 'ready' : 'unavailable',\r\n nodeCount: data.metadata.nodeCount,\r\n edgeCount: data.metadata.edgeCount,\r\n sources: {\r\n codegraph: cgResult.status === 'fulfilled' && cgResult.value.nodes.length > 0,\r\n lens: lensResult.status === 'fulfilled' && lensResult.value.nodes.length > 0,\r\n },\r\n };\r\n}\r\n\r\n\r\nexport async function executeGraphData(\r\n args: { repoId: string; source?: string },\r\n invoke: UpstreamInvoker,\r\n emitUpdate: (event: GraphUpdatedEvent) => void,\r\n emitData: (event: GraphDataEvent) => void,\r\n graphCache: Map<string, GraphData>,\r\n deps: GraphToolDeps,\r\n xuanjiPool?: XuanjiAdapterPool,\r\n) {\r\n const source = (args.source ?? 'both') as 'codegraph' | 'lens' | 'both';\r\n const fresh = await fetchMergedGraph(invoke, args.repoId, source, deps);\r\n\r\n const cached = graphCache.get(args.repoId);\r\n let merged: GraphData;\r\n // XJ-11 消费端收口(cycle-122):增量引擎的降级/健康信号必须落到返回值里——\r\n // 「不可观测的降级等于没有降级」(incremental.ts 头注)。此前 pool.applyDelta\r\n // 的返回值在这里被整行丢弃,模型永远看不到引擎状态。\r\n let xuanjiSignal: GraphData['metadata']['xuanji'] | undefined;\r\n if (cached) {\r\n merged = deps.merger.applyDelta(cached, { nodes: fresh.nodes, edges: fresh.edges, source: source === 'both' ? 'codegraph' : source, timestamp: fresh.metadata.timestamp });\r\n // XJ-02:把 merger.applyDelta 产出的**真实**结构差(缓存基线 → 合并结果)转发给璇玑图适配层,\r\n // 让库里的增量引擎在生产上真的被调用;delta 不是这里手搓的,而是缓存与新结果的实际差异。\r\n if (xuanjiPool) {\r\n const delta = computeGraphDiff(cached.nodes, cached.edges, merged.nodes, merged.edges);\r\n const poolDelta = xuanjiPool.applyDelta(args.repoId, delta);\r\n xuanjiSignal = {\r\n applied: poolDelta.applied,\r\n // 仅 applied 时带数值:skipped 意味着引擎根本没跑,addedPaths=0 是假量化。\r\n ...(poolDelta.applied ? { addedPaths: poolDelta.addedPaths } : {}),\r\n ...(poolDelta.degradedRules.length > 0 ? { degradedRules: poolDelta.degradedRules } : {}),\r\n ...(poolDelta.invariants.violated.length > 0 ? { invariantViolations: poolDelta.invariants.violated } : {}),\r\n };\r\n }\r\n } else {\r\n // 没有缓存基线:适配层手里的可能是另一版修订,派生状态(图空间 + 增量索引)先丢掉。\r\n merged = fresh;\r\n xuanjiPool?.invalidate(args.repoId);\r\n // 全量重建:不带 xuanji 信号(undefined = 「这次没走增量」,与 skipped 显式区分)。\r\n }\r\n if (xuanjiSignal) merged = { ...merged, metadata: { ...merged.metadata, xuanji: xuanjiSignal } };\r\n\r\n // 继承增强已下沉到 fetchMergedGraph(cycle-125):fresh 的 metadata.inheritance\r\n // 在此透传(缓存路径由 applyDelta 的 ...current.metadata 保留旧值,此处用 fresh\r\n // 的最新值覆盖——它描述当前全量图的真实增强状态)。\r\n if (fresh.metadata.inheritance) {\r\n merged = { ...merged, metadata: { ...merged.metadata, inheritance: fresh.metadata.inheritance } };\r\n }\r\n\r\n cacheGraph(graphCache, args.repoId, merged);\r\n\r\n emitUpdate({\r\n repoId: makeRepoId(args.repoId),\r\n nodeCount: merged.metadata.nodeCount,\r\n edgeCount: merged.metadata.edgeCount,\r\n timestamp: merged.metadata.timestamp,\r\n });\r\n emitData({\r\n repoId: makeRepoId(args.repoId),\r\n nodes: merged.nodes,\r\n edges: merged.edges,\r\n timestamp: merged.metadata.timestamp,\r\n // XJ-11 消费端收口:信号随 SSE 图数据事件直达客户端 StatusBar(三层贯通:\r\n // 增量引擎 → 工具返回值 → SSE 载荷 → store → UI)。\r\n ...(xuanjiSignal ? { xuanji: xuanjiSignal } : {}),\r\n });\r\n return toJsonValue(merged);\r\n}\r\n\r\n\r\nexport async function executeGraphSymbol(args: { symbolId: string }, invoke: UpstreamInvoker) {\r\n const raw = await invoke('codegraph_query', { search: args.symbolId, limit: 1 });\r\n return toJsonValue(pickBestMatch(raw, args.symbolId));\r\n}\r\n\r\n\r\nexport async function executeGraphImpact(args: { symbolId: string }, invoke: UpstreamInvoker) {\r\n const raw = await invoke('codegraph_impact', { symbol: args.symbolId, depth: 2 });\r\n const normalized = normalizeImpact(raw);\r\n if (normalized) return toJsonValue(normalized);\r\n const lens = await invoke('lens_impact', { symbolId: args.symbolId });\r\n return toJsonValue(lens ?? { affected: [], depth: 0 });\r\n}\r\n\r\n/**\r\n * `graph_slice` 的执行体(cycle-135:多维视图切片)。\r\n *\r\n * 四个视图共用一条链路:先 `fetchMergedGraph` 取合并图,再交给 `graphSlice` 统一切片。\r\n * 时序/所有权视图的**外部事实**(commit 历史、blame)通过两个可选字符串参数注入:\r\n * - `commitLog`:`git log --name-only --format=%x1e%H%x1f%ct` 的原始输出;\r\n * - `blameLog`:`git blame --porcelain <file>` 的原始输出。\r\n *\r\n * 为什么让模型/调用方注入原始文本而不是在工具里直接跑 git:本工具是**读图**工具,\r\n * 与其余 `graph_*` 一样只依赖 `invoke` 上游通道;git 子进程属于宿主 `/churn`、\r\n * `/blame` 一类的分析路由职责(`gitChurn.ts` 已有实现)。这样切片逻辑保持纯函数、\r\n * 可单测,也避免在工具层再引入一条 git 依赖。缺这两个参数时,对应视图走**显式降级**\r\n * (`viewDataAvailable: false` + `reason`),而不是编造一份缺维度的图。\r\n */\r\nexport async function executeGraphSlice(\r\n args: {\r\n repoId: string;\r\n view: GraphSliceView;\r\n granularity?: GraphSliceGranularity;\r\n focus?: string;\r\n commitLog?: string;\r\n blameLog?: string;\r\n },\r\n invoke: UpstreamInvoker,\r\n): Promise<GraphSliceResult> {\r\n const data = await fetchMergedGraph(invoke, args.repoId);\r\n const commitHistory = args.commitLog\r\n ? { slices: parseCommitLog(args.commitLog), head: null, available: true, error: null }\r\n : null;\r\n const blameByFile = args.blameLog ? groupBlameByFile(parseGitBlamePorcelain(args.blameLog)) : null;\r\n\r\n return sliceGraph(data, {\r\n view: args.view,\r\n granularity: args.granularity,\r\n focus: args.focus,\r\n commitHistory,\r\n blameByFile,\r\n });\r\n}\r\n\r\n// ── Tool factory ────────────────────────────────────────────────────────\r\n\r\nexport const createGraphTools = async (ctx: Context, options: { requestTimeout?: number; apiCompat?: ApiCompatLayer; /** 原生图谱通道的工作区根(cycle-127);缺省 = 不启用原生通道。 */ nativeWorkspaceRoot?: string } = {}) => {\r\n const invoke = createInvoke(ctx, options.requestTimeout, options.apiCompat);\r\n const graphCache = new Map<string, GraphData>();\r\n const deps = await createGraphToolDeps();\r\n // cycle-131:原生适配器已由 createGraphToolDeps 的模块级单例自动挂载\r\n // (ensureNativeAdapter)——本工厂不再需要 options.nativeWorkspaceRoot 组装\r\n // (保留参数向后兼容,仅记录日志)。\r\n // XJ-02:按 repoId 缓存璇玑图适配层,让增量引擎有真正的宿主入口;\r\n // 生命周期挂在返回的 dispose 上(src/index.ts 的 ctx.effect 清理会调用它)。\r\n const xuanjiPool = new XuanjiAdapterPool();\r\n\r\n const emitUpdate = (event: GraphUpdatedEvent) => {\r\n ctx.emit('codegraph-visualizer/graph/updated', event);\r\n };\r\n\r\n const emitData = (event: GraphDataEvent) => {\r\n ctx.emit('codegraph-visualizer/graph/data', event);\r\n };\r\n\r\n const graphStatus = defineTool({\r\n name: 'graph_status',\r\n description: 'Check whether an interactive code graph is available for a repository.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: renderGraphStatus,\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string };\r\n return { card: 'generic' as const, title: 'Check graph status', kind: 'other' as const, rawInput: { repoId: a.repoId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Graph status failed' : 'Graph status',\r\n }),\r\n execute: (args) => executeGraphStatus(args as { repoId: string }, invoke, deps),\r\n });\r\n\r\n const graphData = defineTool({\r\n name: 'graph_data',\r\n description: 'Fetch the merged code relationship graph for a repository (calls + dependencies). Uses incremental delta merge on repeat calls.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n source: {\r\n type: 'string',\r\n enum: ['codegraph', 'lens', 'both', 'auto'],\r\n description: 'Data source; defaults to both. Use auto for graceful degradation to built-in analysis.',\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: renderGraphData,\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string; source?: string };\r\n return { card: 'generic' as const, title: 'Fetch code graph', kind: 'fetch' as const, rawInput: { repoId: a.repoId, source: a.source ?? 'both' } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Graph fetch failed' : 'Code graph fetched',\r\n }),\r\n execute: (args) => executeGraphData(args as { repoId: string; source?: string }, invoke, emitUpdate, emitData, graphCache, deps, xuanjiPool),\r\n });\r\n\r\n const graphSymbol = defineTool({\r\n name: 'graph_symbol',\r\n description: 'Resolve the details (file path and line number) of one graph node by symbol id.',\r\n parameters: {\r\n symbolId: { type: 'string', required: true, description: 'Symbol id.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: renderGraphSymbol,\r\n },\r\n presentCall: (args) => {\r\n const a = args as { symbolId: string };\r\n return { card: 'generic' as const, title: 'Resolve symbol', kind: 'search' as const, rawInput: { symbolId: a.symbolId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Symbol resolution failed' : 'Symbol resolved',\r\n }),\r\n execute: (args) => executeGraphSymbol(args as { symbolId: string }, invoke),\r\n });\r\n\r\n const graphImpact = defineTool({\r\n name: 'graph_impact',\r\n description: 'Analyze the impact of changing one symbol: which symbols are transitively affected.',\r\n parameters: {\r\n symbolId: { type: 'string', required: true, description: 'Symbol id to analyze.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: renderGraphImpact,\r\n },\r\n presentCall: (args) => {\r\n const a = args as { symbolId: string };\r\n return { card: 'generic' as const, title: 'Analyze impact', kind: 'search' as const, rawInput: { symbolId: a.symbolId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Impact analysis failed' : 'Impact analyzed',\r\n }),\r\n execute: (args) => executeGraphImpact(args as { symbolId: string }, invoke),\r\n });\r\n\r\n const graphPathFind = defineTool({\r\n name: 'graph_path_find',\r\n description: 'Find the shortest call path between two symbols in the code graph.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n sourceSymbol: { type: 'string', required: true, description: 'Source symbol id.' },\r\n targetSymbol: { type: 'string', required: true, description: 'Target symbol id.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as { path?: string[]; length?: number; truncated?: boolean } | null;\r\n if (!v || !v.path || v.path.length === 0) {\r\n if (v?.truncated) return [{ type: 'text' as const, text: TOOL_MESSAGES.GRAPH_TOO_LARGE_FOR_PATH?.en ?? 'Graph too large' }];\r\n return [{ type: 'text' as const, text: TOOL_MESSAGES.NO_PATH_FOUND?.en ?? 'No path found' }];\r\n }\r\n return [{ type: 'text' as const, text: `${TOOL_MESSAGES.PATH_FOUND?.en ?? 'Path found'} (${v.length} ${TOOL_MESSAGES.HOPS?.en ?? 'hops'}): ${v.path.join(' → ')}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { sourceSymbol: string; targetSymbol: string };\r\n return { card: 'generic' as const, title: 'Find call path', kind: 'search' as const, rawInput: { from: a.sourceSymbol, to: a.targetSymbol } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Path search failed' : 'Call path',\r\n }),\r\n execute: async (args) => {\r\n const a = args as { repoId: string; sourceSymbol: string; targetSymbol: string };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n const nodeIds = new Set(data.nodes.map((n) => n.id));\r\n if (!nodeIds.has(NodeId(a.sourceSymbol)) || !nodeIds.has(NodeId(a.targetSymbol))) {\r\n return null;\r\n }\r\n if (data.nodes.length > PATH_FINDER.MAX_NODES_FOR_BFS) return toJsonValue({ path: [], length: 0, truncated: true });\r\n const adjacency = buildForwardAdjacency(data.edges);\r\n const result = bfsShortestPath(adjacency, a.sourceSymbol, a.targetSymbol, data.nodes.length);\r\n if (!result) return null;\r\n return toJsonValue({ path: result.path, length: result.length });\r\n },\r\n });\r\n\r\n const graphPathEnumerate = defineTool({\r\n name: 'graph_path_enumerate',\r\n description: 'Enumerate simple call/dependency paths between two symbols with a hop range and edge-type filter (Xuanji path-rule reader: one graph space, many reading rules).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n sourceSymbol: { type: 'string', required: true, description: 'Source symbol id.' },\r\n targetSymbol: { type: 'string', required: true, description: 'Target symbol id.' },\r\n minHops: { type: 'number', description: 'Minimum path length in edges. Default: 1.' },\r\n maxHops: { type: 'number', description: 'Maximum path length in edges. Default: 5, capped at 10.' },\r\n limit: { type: 'number', description: 'Maximum number of paths returned. Default: 10, capped at 50.' },\r\n cursor: {\r\n type: 'number',\r\n description: 'Pagination cursor: number of paths already returned. Use the previous call\\'s nextCursor.',\r\n },\r\n pageSize: { type: 'number', description: 'Paths per page. Defaults to limit, capped at 50.' },\r\n edgeTypes: {\r\n type: 'string',\r\n description: 'Comma-separated edge types to traverse. Default: call,import,extend,implement,dependency.',\r\n },\r\n truncation: {\r\n type: 'string',\r\n description: \"How to choose which paths to keep when capping the result. 'dfs-order' (default) stops at the cap in deterministic DFS order; 'top-k-weight' enumerates ALL candidates first and keeps the paths with the highest total edge weight — slower on large graphs.\",\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as {\r\n paths?: string[][];\r\n truncated?: boolean;\r\n total?: number;\r\n nextCursor?: number | null;\r\n labels?: unknown[];\r\n } | null;\r\n if (!v || !v.paths || v.paths.length === 0) {\r\n return [{ type: 'text' as const, text: TOOL_MESSAGES.NO_PATH_FOUND?.en ?? 'No path found' }];\r\n }\r\n const lines = v.paths.slice(0, 10).map((p, i) => ` ${i + 1}. ${p.join(' → ')} (${p.length - 1} hops)`);\r\n const suffix = v.truncated ? ' (truncated)' : '';\r\n // XJ-03:分页游标必须出现在人读的摘要里,否则调用方无从翻页(\"返回了字段但没人知道\")。\r\n const next = v.nextCursor === null || v.nextCursor === undefined ? '' : ` (nextCursor=${String(v.nextCursor)})`;\r\n return [{ type: 'text' as const, text: `${v.total ?? v.paths.length} path(s)${suffix}${next}\\n${lines.join('\\n')}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { sourceSymbol: string; targetSymbol: string };\r\n return { card: 'generic' as const, title: 'Enumerate paths', kind: 'search' as const, rawInput: { from: a.sourceSymbol, to: a.targetSymbol } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Path enumeration failed' : 'Paths enumerated',\r\n }),\r\n execute: async (args) => {\r\n const a = args as {\r\n repoId: string;\r\n sourceSymbol: string;\r\n targetSymbol: string;\r\n minHops?: number;\r\n maxHops?: number;\r\n limit?: number;\r\n cursor?: number;\r\n pageSize?: number;\r\n edgeTypes?: string;\r\n truncation?: string;\r\n };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n const nodeIds = new Set(data.nodes.map((n) => n.id));\r\n if (!nodeIds.has(NodeId(a.sourceSymbol)) || !nodeIds.has(NodeId(a.targetSymbol))) return null;\r\n if (data.nodes.length > PATH_FINDER.MAX_NODES_FOR_BFS) {\r\n return toJsonValue({ paths: [], truncated: true, total: 0 });\r\n }\r\n\r\n const adapter = xuanjiPool.get(a.repoId);\r\n adapter.syncGraph(data);\r\n const parsedTypes = parseEdgeTypes(a.edgeTypes);\r\n const limit = clampInt(a.limit, 1, 50, 10);\r\n const result = adapter.findPathsBetween(a.sourceSymbol, a.targetSymbol, {\r\n ...(parsedTypes ? { edgeTypes: parsedTypes } : {}),\r\n minHops: clampInt(a.minHops, 1, 10, 1),\r\n maxHops: clampInt(a.maxHops, 1, 10, 5),\r\n limit,\r\n ...(a.cursor === undefined ? {} : { cursor: clampInt(a.cursor, 0, Number.MAX_SAFE_INTEGER, 0) }),\r\n ...(a.pageSize === undefined ? {} : { pageSize: clampInt(a.pageSize, 1, 50, limit) }),\r\n // cycle-119 修复:schema 暴露了 truncation 但 execute 从未透传——「参数加了、\r\n // 没人传」家族(与 P3-R18-02 同形)。数据面(confidence→weight)贯通后这个参数\r\n // 才真正有行为差异:无 weight 数据时 top-k-weight 与 dfs-order 结果一致。\r\n ...(a.truncation === 'top-k-weight' || a.truncation === 'dfs-order'\r\n ? { truncation: a.truncation }\r\n : {}),\r\n });\r\n return toJsonValue(result);\r\n },\r\n });\r\n\r\n\r\n /**\r\n * 路径模式 ρ(cycle-134):`graph_path_enumerate` 的**序列约束**版本。\r\n *\r\n * 与 `graph_path_enumerate` 的差别只在表达能力:\r\n * - 后者 `edgeTypes` 是扁平集合(「每步走这些之一」);\r\n * - 本工具 `pattern` 是边类型**序列正则**,能表达「先 call 再 import 再 dependency」\r\n * 这类跨视图路径(研究方案 §4 的 `P = (v_s, ρ, v_t)`)。\r\n *\r\n * 为什么仍是独立工具而不合并进 `graph_path_enumerate`:两者的**参数语义不同**\r\n * (`edgeTypes` 是集合语义、`pattern` 是序列语义),合并成一个可选参数会让模型\r\n * 无法从参数名判断该传哪个,且两者同时给出时的优先级无自然定义。模型侧的选择表\r\n * 写在 `src/host/skills.ts` 的 SKILL_CONTENT 第 8 条。\r\n */\r\n const graphPathPattern = defineTool({\r\n name: 'graph_path_pattern',\r\n description:\r\n 'Enumerate code paths matching a SEQUENCE pattern of edge types (Xuanji 璇玑图 path-pattern reader). Use this when the ORDER of relations matters — \"call then import then dependency\" — which graph_path_enumerate\\'s flat edgeTypes cannot express. Pattern syntax: edge type names (call/import/extend/implement/dependency/contains/association/aggregation/composition), concatenation by space or \".\", alternation \"|\", grouping \"(...)\", quantifiers \"?\" \"*\" \"+\" and \"{n,m}\". Examples: \"call+\" (transitive call closure), \"call* . import\" (any calls then a cross-file import), \"(import|dependency)+\" (any dependency closure), \"call{2,4}\" (2-4 hop call chains).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n pattern: {\r\n type: 'string',\r\n required: true,\r\n description: 'Edge-type sequence pattern (ρ). e.g. \"call+ . import? . dependency\".',\r\n },\r\n startNode: { type: 'string', description: 'Restrict the START to this node id (narrows the search, does not filter results).' },\r\n endNode: { type: 'string', description: 'Require the last node to be this node id.' },\r\n minHops: { type: 'number', description: 'Minimum edges; intersected with the pattern\\'s own lower bound. Default: 0.' },\r\n maxHops: { type: 'number', description: 'Maximum edges; intersected with the pattern\\'s own upper bound. Default: 8, capped at 12.' },\r\n limit: { type: 'number', description: 'Maximum paths returned. Default: 20, capped at 50.' },\r\n cursor: {\r\n type: 'number',\r\n description: 'Pagination cursor: number of paths already returned. Use the previous call\\'s nextCursor.',\r\n },\r\n pageSize: { type: 'number', description: 'Paths per page. Defaults to limit, capped at 50.' },\r\n truncation: {\r\n type: 'string',\r\n description: \"'dfs-order' (default) stops at the cap in deterministic DFS order; 'top-k-weight' enumerates ALL candidates first and keeps the highest total edge weight — slower on large graphs.\",\r\n },\r\n /**\r\n * ρ 的 commit 切片(cycle-137 / 研究方案 §4 的 $\\\\mathcal{T}$ 维)。\r\n *\r\n * 语义:「在给定 commit 切片 `t` 下枚举」——先按区间切片,再在切片上跑 ρ。\r\n * 三者互斥地组合:`days` 取时间窗、`from`/`to` 取 hash 区间。\r\n *\r\n * 为什么用 raw git 输出而不是让宿主自己跑 git:ρ 是**模型工具**,模型手里\r\n * 通常已经有 `git log` 的输出(或能跑);把解析放在库侧(`parseCommitLog`)\r\n * 让「切片」成为可单测的纯函数,而不是藏在宿主子进程里。\r\n *\r\n * 与 `graph_slice` 的 `commitLog` **同一份解析器**(`src/shared/temporal.ts`\r\n * 的 `parseCommitLog`),不是两份。\r\n */\r\n commitLog: {\r\n type: 'string',\r\n description: 'Raw `git log --name-only --format=%x1e%H%x1f%ct` output. When provided, ρ is enumerated on the commit-sliced graph (研究方案 §4 的 T 维).',\r\n },\r\n days: {\r\n type: 'number',\r\n description: 'Temporal slice: only commits within the last N days. Combined with commitLog.',\r\n },\r\n from: {\r\n type: 'string',\r\n description: 'Temporal slice: keep commits from this hash onward (inclusive). Combined with commitLog.',\r\n },\r\n to: {\r\n type: 'string',\r\n description: 'Temporal slice: keep commits up to this hash (inclusive). Combined with commitLog.',\r\n },\r\n /**\r\n * 边级时序谓词(cycle-138 / 研究方案 §4 的边级时序事实)。\r\n *\r\n * 与 `days`/`from`/`to`(**图级** commit 切片:决定哪些文件存在)正交:\r\n * `since` 是**边级**过滤(决定哪些关系够新)——只保留「首次有提交同时触及\r\n * 两端文件」的时刻 ≥ since 的边。该时刻是边引入时间的**下界**(引入提交必然\r\n * 同时触及两端文件),因此保留的边保证真正新;无事实的边一律不返回\r\n * (不知道 ≠ 符合)。\r\n */\r\n since: {\r\n type: 'number',\r\n description: 'Edge-level temporal predicate (unix ms): keep only edges whose first co-touched commit time (lower bound of the introduction time) is >= since. Requires commitLog. Edges without a temporal fact are excluded.',\r\n },\r\n /**\r\n * 挥发度谓词(研究方案 §4 的 vol(u,v))。**当前被显式拒绝**:挥发度需要\r\n * 边级事实(每个 commit 切片里这条边是否真的存在),raw `--name-only`\r\n * 只有文件级事实 ⇒ 请求即返回 `paths: []` + `edgeTemporal.available: false`\r\n * + reason。参数仍声明的原因:把「这个维度现在拿不到」变成**可达且可测**\r\n * 的诚实降级,而不是让模型静默忽略一个无效参数(拼错 ≠ 缺失纪律)。\r\n * 边级采集渠道到位后此参数直接生效,工具面零改动。\r\n */\r\n minVolatility: {\r\n type: 'number',\r\n description: 'Minimum edge volatility (0-1). Currently EXPLICITLY REJECTED: volatility requires per-commit edge-existence facts that the --name-only log does not carry; the response reports edgeTemporal.available=false with a reason instead of pretending.',\r\n },\r\n maxVolatility: {\r\n type: 'number',\r\n description: 'Maximum edge volatility (0-1). Same explicit-rejection contract as minVolatility.',\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as {\r\n paths?: string[][];\r\n truncated?: boolean;\r\n total?: number;\r\n nextCursor?: number | null;\r\n pattern?: string;\r\n patternEdgeTypes?: readonly string[];\r\n stateCount?: number;\r\n unknownEdgeTypes?: readonly string[];\r\n error?: string;\r\n diagnostic?: string;\r\n } | null;\r\n // ρ 语法错误必须与「零条路径」在返回面上可区分:把编译错误渲染成\r\n // 带 ^ 指示的诊断,而不是伪装成「这个模式在该仓库没有匹配」。\r\n if (v?.error) {\r\n return [{ type: 'text' as const, text: `${v.error}${v.diagnostic ? `\\n${v.diagnostic}` : ''}` }];\r\n }\r\n if (!v || !v.paths) {\r\n return [{ type: 'text' as const, text: TOOL_MESSAGES.PATH_PATTERN_FAILED?.en ?? 'Path pattern evaluation failed' }];\r\n }\r\n const head = `pattern \"${v.pattern ?? ''}\" (types: ${(v.patternEdgeTypes ?? []).join('|') || 'none'}, ${v.stateCount ?? 0} NFA states)`;\r\n // cycle-135:空结果要区分「拼错类型名」与「图上确实没有这种边」。\r\n // 两者在返回面上同形(`paths: []`),不区分会让模型把「我写错了」误读成\r\n // 「这个仓库没有 call 边」而放弃。\r\n const unknown = v.unknownEdgeTypes ?? [];\r\n if (unknown.length > 0) {\r\n return [{ type: 'text' as const, text: `${head}\\nUnknown edge type(s): ${unknown.join(', ')}. Valid types: call/import/extend/implement/dependency/contains/association/aggregation/composition.` }];\r\n }\r\n if (v.paths.length === 0) {\r\n return [{ type: 'text' as const, text: `${head}\\n${TOOL_MESSAGES.NO_PATH_FOUND?.en ?? 'No path found'}` }];\r\n }\r\n const lines = v.paths.slice(0, 10).map((p, i) => ` ${i + 1}. ${p.join(' → ')} (${p.length - 1} hops)`);\r\n const suffix = v.truncated ? ' (truncated)' : '';\r\n const next = v.nextCursor === null || v.nextCursor === undefined ? '' : ` (nextCursor=${String(v.nextCursor)})`;\r\n return [{ type: 'text' as const, text: `${head}\\n${v.total ?? v.paths.length} path(s)${suffix}${next}\\n${lines.join('\\n')}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { pattern: string; startNode?: string; endNode?: string };\r\n return {\r\n card: 'generic' as const,\r\n title: 'Enumerate paths by pattern',\r\n kind: 'search' as const,\r\n rawInput: { pattern: a.pattern, ...(a.startNode === undefined ? {} : { from: a.startNode }), ...(a.endNode === undefined ? {} : { to: a.endNode }) },\r\n };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Path pattern failed' : 'Pattern paths',\r\n }),\r\n execute: async (args) => {\r\n const a = args as {\r\n repoId: string;\r\n pattern: string;\r\n startNode?: string;\r\n endNode?: string;\r\n minHops?: number;\r\n maxHops?: number;\r\n limit?: number;\r\n cursor?: number;\r\n pageSize?: number;\r\n truncation?: string;\r\n commitLog?: string;\r\n days?: number;\r\n from?: string;\r\n to?: string;\r\n since?: number;\r\n minVolatility?: number;\r\n maxVolatility?: number;\r\n };\r\n const merged = await fetchMergedGraph(invoke, a.repoId);\r\n if (merged.nodes.length === 0) return null;\r\n\r\n // cycle-137:ρ 的 commit 切片(研究方案 §4 的 𝒯 维)。\r\n // 未提供 commitLog ⇒ temporal === null,行为与既有调用逐字节一致。\r\n const sliced = sliceGraphForTemporal(merged, {\r\n ...(a.commitLog !== undefined ? { commitLog: a.commitLog } : {}),\r\n ...(a.days !== undefined ? { days: a.days } : {}),\r\n ...(a.from ? { from: a.from } : {}),\r\n ...(a.to ? { to: a.to } : {}),\r\n });\r\n\r\n // 时序请求但切片不可用 ⇒ 诚实降级,不拿原图冒充切片(研究方案 §4:没有 𝒯\r\n // 就没有时序切片,把它渲染成「整个仓库」比渲染成「空图」更远离事实)。\r\n if (sliced.temporal?.requested && !sliced.temporal.available) {\r\n return toJsonValue({\r\n pattern: a.pattern,\r\n paths: [] as string[][],\r\n truncated: false,\r\n total: 0,\r\n temporal: sliced.temporal,\r\n reason: sliced.temporal.reason ?? 'commit 切片不可用',\r\n });\r\n }\r\n\r\n // 边级时序谓词(cycle-138):在图级切片**之后**对切片数据做边级过滤。\r\n // 请求了但不可用(含挥发度被显式拒绝)⇒ 与图级降级同一形态的诚实空结果。\r\n let data = sliced.data;\r\n const edgeFilter = filterEdgesByTemporalPredicate(data, {\r\n ...(a.commitLog !== undefined ? { commitLog: a.commitLog } : {}),\r\n ...(a.since !== undefined ? { since: a.since } : {}),\r\n ...(a.minVolatility !== undefined ? { minVolatility: a.minVolatility } : {}),\r\n ...(a.maxVolatility !== undefined ? { maxVolatility: a.maxVolatility } : {}),\r\n });\r\n const temporal = sliced.temporal;\r\n let edgeTemporal: ReturnType<typeof filterEdgesByTemporalPredicate>['meta'] = null;\r\n if (edgeFilter.meta) {\r\n if (!edgeFilter.meta.available) {\r\n return toJsonValue({\r\n pattern: a.pattern,\r\n paths: [] as string[][],\r\n truncated: false,\r\n total: 0,\r\n temporal,\r\n edgeTemporal: edgeFilter.meta,\r\n reason: edgeFilter.meta.reason ?? '边级时序谓词不可用',\r\n });\r\n }\r\n data = edgeFilter.data;\r\n edgeTemporal = edgeFilter.meta;\r\n }\r\n\r\n // 切片图是原图的子集;把它 sync 进主池会让下一次不带 commitLog 的 ρ 调用静默少查。\r\n // 因此时序路径用**独立**的时序池(temporalXuanjiPool,见 temporalEnumeration.ts)。\r\n // 边级过滤产生了(可能更小的)子集 ⇒ 同样走时序池,绝不污染主池。\r\n const adapter = temporal || edgeTemporal ? temporalXuanjiPool.get(a.repoId) : xuanjiPool.get(a.repoId);\r\n adapter.syncGraph(data);\r\n\r\n const limit = clampInt(a.limit, 1, 50, 20);\r\n try {\r\n const result = adapter.pathsByPattern(a.pattern, {\r\n ...(a.startNode === undefined ? {} : { startNode: a.startNode }),\r\n ...(a.endNode === undefined ? {} : { endNode: a.endNode }),\r\n minHops: clampInt(a.minHops, 0, 12, 0),\r\n maxHops: clampInt(a.maxHops, 0, 12, 8),\r\n limit,\r\n ...(a.cursor === undefined ? {} : { cursor: clampInt(a.cursor, 0, Number.MAX_SAFE_INTEGER, 0) }),\r\n ...(a.pageSize === undefined ? {} : { pageSize: clampInt(a.pageSize, 1, 50, limit) }),\r\n ...(a.truncation === 'top-k-weight' || a.truncation === 'dfs-order'\r\n ? { truncation: a.truncation }\r\n : {}),\r\n });\r\n return toJsonValue({ ...result, temporal: temporal ?? null, ...(edgeTemporal ? { edgeTemporal } : {}) });\r\n } catch (e) {\r\n // 只有 ρ 语法错误走这里(其余异常照常上抛,保持响亮失败)。\r\n const diagnostic = formatPatternError(a.pattern, e);\r\n if (!(e instanceof PathPatternError)) throw e;\r\n return toJsonValue({\r\n pattern: a.pattern,\r\n paths: [] as string[][],\r\n truncated: false,\r\n total: 0,\r\n temporal: temporal ?? null,\r\n ...(edgeTemporal ? { edgeTemporal } : {}),\r\n error: `Invalid path pattern: ${e instanceof Error ? e.message : String(e)}`,\r\n diagnostic,\r\n });\r\n }\r\n },\r\n });\r\n\r\n const graphCycles = defineTool({\r\n name: 'graph_cycles',\r\n description: 'Enumerate simple dependency cycles (closed paths) in the code graph with a length bound. Each cycle is canonicalised (Booth minimal rotation) so it is reported once regardless of its starting symbol (Xuanji closed-path reader).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n maxLength: { type: 'number', description: 'Maximum cycle length in edges. Default: 6, capped at 12.' },\r\n limit: { type: 'number', description: 'Maximum number of cycles returned. Default: 100, capped at 500.' },\r\n cursor: {\r\n type: 'number',\r\n description: 'Pagination cursor: number of cycles already returned. Use the previous call\\'s nextCursor.',\r\n },\r\n pageSize: { type: 'number', description: 'Cycles per page. Defaults to limit, capped at 500.' },\r\n edgeTypes: {\r\n type: 'string',\r\n description: 'Comma-separated edge types to traverse. Default: call,import,extend,implement,dependency.',\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as {\r\n paths?: string[][];\r\n truncated?: boolean;\r\n total?: number;\r\n nextCursor?: number | null;\r\n } | null;\r\n if (!v || !v.paths || v.paths.length === 0) {\r\n return [{ type: 'text' as const, text: TOOL_MESSAGES.NO_CYCLES?.en ?? 'No dependency cycles detected.' }];\r\n }\r\n const lines = v.paths.slice(0, 10).map((cycle, i) => ` ${i + 1}. ${cycle.join(' → ')}`);\r\n const suffix = v.truncated ? ' (truncated)' : '';\r\n const next = v.nextCursor === null || v.nextCursor === undefined ? '' : ` (nextCursor=${String(v.nextCursor)})`;\r\n return [{ type: 'text' as const, text: `${v.total ?? v.paths.length} cycle(s)${suffix}${next}\\n${lines.join('\\n')}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string };\r\n return { card: 'generic' as const, title: 'Enumerate dependency cycles', kind: 'search' as const, rawInput: { repoId: a.repoId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Cycle enumeration failed' : 'Dependency cycles',\r\n }),\r\n execute: async (args) => {\r\n const a = args as {\r\n repoId: string;\r\n maxLength?: number;\r\n limit?: number;\r\n cursor?: number;\r\n pageSize?: number;\r\n edgeTypes?: string;\r\n };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n if (data.nodes.length === 0) return null;\r\n if (data.nodes.length > PATH_FINDER.MAX_NODES_FOR_BFS) {\r\n return toJsonValue({ paths: [], truncated: true, total: 0 });\r\n }\r\n\r\n const adapter = xuanjiPool.get(a.repoId);\r\n adapter.syncGraph(data);\r\n const parsedTypes = parseEdgeTypes(a.edgeTypes);\r\n const limit = clampInt(a.limit, 1, 500, 100);\r\n const result = adapter.cycles({\r\n ...(parsedTypes ? { edgeTypes: parsedTypes } : {}),\r\n maxLength: clampInt(a.maxLength, 1, 12, 6),\r\n limit,\r\n ...(a.cursor === undefined ? {} : { cursor: clampInt(a.cursor, 0, Number.MAX_SAFE_INTEGER, 0) }),\r\n ...(a.pageSize === undefined ? {} : { pageSize: clampInt(a.pageSize, 1, 500, limit) }),\r\n });\r\n return toJsonValue(result);\r\n },\r\n });\r\n\r\n const graphHealth = defineTool({\r\n name: 'graph_health',\r\n description: 'Calculate architecture health score (coupling, cohesion, cyclic dependency rate, modularity).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as HealthScore | null;\r\n if (!v) return [{ type: 'text' as const, text: TOOL_MESSAGES.INSUFFICIENT_DATA?.en ?? 'Insufficient data' }];\r\n return [{\r\n type: 'text' as const,\r\n text: `Architecture Health: ${v.overall}/100\\n Coupling: ${v.coupling}/100\\n Cohesion: ${v.cohesion}/100\\n Cyclic Dep Rate: ${v.cyclicDependencyRate}/100\\n Modularity: ${v.modularity}/100`,\r\n }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string };\r\n return { card: 'generic' as const, title: 'Calculate health score', kind: 'other' as const, rawInput: { repoId: a.repoId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Health scoring failed' : 'Health score',\r\n }),\r\n execute: async (args) => {\r\n const a = args as { repoId: string };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n if (data.nodes.length === 0) return null;\r\n\r\n const scorer = new HealthScorer();\r\n const score = scorer.calculate(data.nodes, data.edges);\r\n return toJsonValue(score);\r\n },\r\n });\r\n\r\n const graphRulesCheck = defineTool({\r\n name: 'graph_rules_check',\r\n description: 'Check code graph against architecture rules (max indegree, max outdegree, circular dependencies).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n maxIndegree: { type: 'number', description: 'Maximum allowed indegree per node. Default: 10.' },\r\n maxOutdegree: { type: 'number', description: 'Maximum allowed outdegree per node. Default: 15.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as RuleViolation[] | null;\r\n if (!v || v.length === 0) return [{ type: 'text' as const, text: TOOL_MESSAGES.NO_VIOLATIONS?.en ?? 'No violations' }];\r\n const lines = v.slice(0, 20).map((r) => ` [${r.severity}] ${r.message}`);\r\n return [{ type: 'text' as const, text: `${v.length} ${TOOL_MESSAGES.RULE_VIOLATIONS?.en ?? 'rule violations:'}\\n${lines.join('\\n')}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string; maxIndegree?: number; maxOutdegree?: number };\r\n return { card: 'generic' as const, title: 'Check architecture rules', kind: 'other' as const, rawInput: { repoId: a.repoId, maxIndegree: a.maxIndegree ?? 10, maxOutdegree: a.maxOutdegree ?? 15 } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Rules check failed' : 'Rules checked',\r\n }),\r\n execute: async (args) => {\r\n const a = args as { repoId: string; maxIndegree?: number; maxOutdegree?: number };\r\n const data = await fetchMergedGraph(invoke, a.repoId, 'both', deps);\r\n if (data.nodes.length === 0) return null;\r\n\r\n const maxIn = a.maxIndegree ?? 10;\r\n const maxOut = a.maxOutdegree ?? 15;\r\n\r\n const rules: RuleDefinition[] = [\r\n { id: 'max-indegree', name: 'Max Indegree', type: 'max-indegree', params: { threshold: maxIn }, severity: 'warning', enabled: true },\r\n { id: 'max-outdegree', name: 'Max Outdegree', type: 'max-outdegree', params: { threshold: maxOut }, severity: 'warning', enabled: true },\r\n ];\r\n\r\n const engine = new RuleEngine();\r\n const violations = engine.check(rules, data.nodes, data.edges);\r\n\r\n return toJsonValue(violations);\r\n },\r\n });\r\n\r\n const graphFocus = defineTool({\r\n name: 'graph_focus',\r\n description: 'Get the neighborhood (1-hop neighbors) of a symbol in the code graph.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n symbolId: { type: 'string', required: true, description: 'Symbol id to focus on.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as { neighbors?: string[]; symbolId?: string } | null;\r\n if (!v) return [{ type: 'text' as const, text: 'Symbol not found in graph.' }];\r\n return [{\r\n type: 'text' as const,\r\n text: `Focus on ${v.symbolId}: ${v.neighbors?.length ?? 0} direct neighbors\\n ${v.neighbors?.slice(0, 15).join(', ') ?? ''}`,\r\n }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { symbolId: string };\r\n return { card: 'generic' as const, title: 'Focus on symbol', kind: 'search' as const, rawInput: { symbolId: a.symbolId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Focus failed' : 'Symbol neighborhood',\r\n }),\r\n execute: async (args) => {\r\n const a = args as { repoId: string; symbolId: string };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n const nodeIds = new Set(data.nodes.map((n) => n.id));\r\n if (!nodeIds.has(NodeId(a.symbolId))) return null;\r\n\r\n const neighbors = new Set<string>();\r\n for (const edge of data.edges) {\r\n if (edge.source === a.symbolId) neighbors.add(edge.target);\r\n if (edge.target === a.symbolId) neighbors.add(edge.source);\r\n }\r\n return toJsonValue({ symbolId: a.symbolId, neighbors: [...neighbors] });\r\n },\r\n });\r\n\r\n const graphQuery = defineTool({\r\n name: 'graph_query',\r\n description:\r\n 'Keyword/symbol query over the code graph: match seeds (exact > prefix > substring > file path), expand a bounded subgraph around them and render it as a token-budgeted text summary. CJK keywords are supported (whole run + per-character tokens).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n query: {\r\n type: 'string',\r\n required: true,\r\n description: 'Keywords or symbol names (space separated for multiple terms; Chinese supported).',\r\n },\r\n depth: { type: 'number', description: 'Subgraph expansion hops from each seed. Default 1, capped at 3.' },\r\n tokenBudget: { type: 'number', description: 'Token budget for the rendered text. Default 1200, capped at 8000.' },\r\n maxSeeds: { type: 'number', description: 'Maximum seeds. Default 3, capped at 10. Each query term also guarantees one seed.' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as\r\n | {\r\n text?: string;\r\n seeds?: Array<{ id: string; label: string; score: number }>;\r\n tokens?: number;\r\n truncated?: boolean;\r\n reason?: string;\r\n }\r\n | null;\r\n if (!v || !v.text) return [{ type: 'text' as const, text: 'No seed matched the query.' }];\r\n const seedLine =\r\n v.seeds && v.seeds.length > 0\r\n ? `seeds: ${v.seeds.map((seed) => `${seed.label}(${seed.score})`).join(', ')}\\n`\r\n : '';\r\n const meta = `${v.tokens ?? 0} tokens${v.truncated ? ` / truncated (${v.reason})` : ''}`;\r\n return [{ type: 'text' as const, text: `${seedLine}${meta}\\n${v.text}` }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string; query: string };\r\n return { card: 'generic' as const, title: 'Query graph', kind: 'search' as const, rawInput: { repoId: a.repoId, query: a.query } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Graph query failed' : 'Graph query result',\r\n }),\r\n execute: async (args) => {\r\n const a = args as {\r\n repoId: string;\r\n query: string;\r\n depth?: number;\r\n tokenBudget?: number;\r\n maxSeeds?: number;\r\n };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n if (data.nodes.length === 0) return null;\r\n\r\n const terms = tokenizeQuery(a.query);\r\n if (terms.length === 0) {\r\n return toJsonValue({\r\n query: a.query,\r\n terms,\r\n seeds: [],\r\n text: 'No searchable term in query (need at least one letter/digit or CJK character).',\r\n tokens: 0,\r\n truncated: false,\r\n reason: 'fits',\r\n nodeCount: 0,\r\n edgeCount: 0,\r\n });\r\n }\r\n\r\n const seeds = selectSeeds(\r\n data.nodes.map((node) => ({\r\n id: node.id,\r\n label: node.label,\r\n ...(node.filePath === undefined ? {} : { filePath: node.filePath }),\r\n })),\r\n terms,\r\n { maxK: clampInt(a.maxSeeds, 1, 10, 3) },\r\n );\r\n const rendered = renderSubgraphText(\r\n {\r\n nodes: data.nodes.map((node) => ({\r\n id: node.id,\r\n label: node.label,\r\n ...(node.filePath === undefined ? {} : { filePath: node.filePath }),\r\n })),\r\n edges: data.edges.map((edge) => ({ source: edge.source, target: edge.target, type: edge.type })),\r\n },\r\n seeds,\r\n { depth: clampInt(a.depth, 0, 3, 1), tokenBudget: clampInt(a.tokenBudget, 50, 8000, 1200) },\r\n );\r\n\r\n return toJsonValue({\r\n query: a.query,\r\n terms,\r\n seeds: seeds.map((seed) => ({\r\n id: seed.id,\r\n label: seed.label,\r\n score: seed.score,\r\n matchedTerms: [...seed.matchedTerms],\r\n })),\r\n ...rendered,\r\n });\r\n },\r\n });\r\n\r\n // cycle-132 / D-06 + D-10 剩余半边:璇玑图白盒可观测工具。语义契约见\r\n // GraphIntegrityReport 头注三条设计决定(只读水位优先 / 不跑覆盖型不变量 /\r\n // 休眠态显式披露)。未装载时返回诚实空态而不是编造零值健康。\r\n const graphIntegrity = defineTool({\r\n name: 'graph_integrity',\r\n description: 'White-box integrity report of the Xuanji graph layer: change-log watermark (pending/dropped, with optional explicit drain), per-rule index health counters, and whether the incremental pipeline is dormant (loaded but zero registered rules).',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n drainChanges: {\r\n type: 'boolean',\r\n description: \"Explicitly drain the change log in this call (events are consumed, not just counted). Default: false — read-only watermark, because a report that silently empties the log trades a memory issue for a data-loss issue.\",\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as\r\n | { loaded?: boolean; graph?: { nodes: number; edges: number; version: number }; changeLog?: { pending: number; dropped: number; drained: number | null }; rules?: ReadonlyArray<{ name: string; counters: { total: number; stale: number } }>; incrementalIdle?: boolean }\r\n | null;\r\n if (!v || v.loaded !== true) {\r\n return [{ type: 'text' as const, text: TOOL_MESSAGES.INSUFFICIENT_DATA?.en ?? 'Graph layer not loaded for this repo yet' }];\r\n }\r\n const g = v.graph ?? { nodes: 0, edges: 0, version: 0 };\r\n const c = v.changeLog ?? { pending: 0, dropped: 0, drained: null };\r\n const drainNote = c.drained === null ? '' : ` drained=${String(c.drained)}`;\r\n const ruleLines = (v.rules ?? []).map((r) => ` ${r.name}: total=${String(r.counters.total)} stale=${String(r.counters.stale)}`);\r\n const lines = [\r\n `Graph integrity: nodes=${String(g.nodes)} edges=${String(g.edges)} version=${String(g.version)}`,\r\n ` changeLog: pending=${String(c.pending)} dropped=${String(c.dropped)}${drainNote}`,\r\n ...(v.incrementalIdle ? [' incremental pipeline: dormant (loaded, zero registered rules — enumerations use one-shot inline indexes)'] : []),\r\n ...(ruleLines.length > 0 ? [' rules:'] : []),\r\n ...ruleLines,\r\n ];\r\n return [{ type: 'text' as const, text: lines.join('\\n') }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string };\r\n return { card: 'generic' as const, title: 'Check graph integrity', kind: 'other' as const, rawInput: { repoId: a.repoId } };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Integrity check failed' : 'Graph integrity',\r\n }),\r\n execute: async (args) => {\r\n const a = args as { repoId: string; drainChanges?: boolean };\r\n // 与其余读法工具同链路:先同步到该仓库的最新图状态(syncGraph 内部按结构等价\r\n // 判定 'none'/'delta'/'full'),再报白盒水位——否则报告的是陈旧图上的健康度。\r\n // 同步失败(上游不可达)不抛错:integrity 报告的是适配层当前状态,loaded 字段\r\n // 会如实反映「从未装载」。\r\n try {\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n xuanjiPool.get(a.repoId).syncGraph(data);\r\n } catch (err) {\r\n log.warn('graph_integrity: graph sync failed, reporting last known state', { repoId: a.repoId, error: err });\r\n }\r\n const adapter = xuanjiPool.get(a.repoId);\r\n return toJsonValue(adapter.integrity(a.drainChanges === true ? { drainChanges: true } : {}));\r\n },\r\n });\r\n\r\n // cycle-135:多维视图切片(研究方案 §4 的 $\\mathcal{T}$ 与 $\\Pi$ 维 + §7.1 的统一接口)。\r\n // 四个视图(structural / temporal / ownership / domain)共用一条链路,差别只在\r\n // 「标注什么」与「外部事实从哪来」。详见 `src/shared/domain.ts` 的 `graphSlice`。\r\n const graphSlice = defineTool({\r\n name: 'graph_slice',\r\n description:\r\n 'Slice the code graph by view and granularity: structural (AST symbols), temporal (commit slices + edge volatility), ownership (git blame authors + bus factor) or domain (Louvain communities → domain labels). Use when you need a bounded subgraph along one orthogonal dimension instead of the whole hairball. `focus` takes a node id/label (1-hop neighborhood) or a file path (whole file). Temporal/ownership views accept raw git output via `commitLog` (git log --name-only --format=%x1e%H%x1f%ct) and `blameLog` (git blame --porcelain); without them the view degrades explicitly (viewDataAvailable: false + reason) rather than returning a graph missing that dimension.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n view: {\r\n type: 'string',\r\n enum: [...GRAPH_SLICE_VIEWS],\r\n required: true,\r\n description: 'Which orthogonal dimension to slice and label.',\r\n },\r\n granularity: {\r\n type: 'string',\r\n enum: [...GRAPH_SLICE_GRANULARITIES],\r\n description: 'Aggregation level: repo (as-is), file (collapse to file nodes), symbol (default).',\r\n },\r\n focus: {\r\n type: 'string',\r\n description: 'Node id/label (1-hop neighborhood) or file path (whole file). Omit to slice the whole graph.',\r\n },\r\n commitLog: {\r\n type: 'string',\r\n description: 'Raw `git log --name-only --format=%x1e%H%x1f%ct` output (temporal view).',\r\n },\r\n blameLog: {\r\n type: 'string',\r\n description: 'Raw `git blame --porcelain` output (ownership view).',\r\n },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as GraphSliceResult | null;\r\n if (!v || !v.available) {\r\n return [{ type: 'text' as const, text: `Slice unavailable: ${v?.reason ?? 'unknown reason'}` }];\r\n }\r\n const labelSamples = Object.entries(v.labels).slice(0, 10).map(([id, label]) => ` ${id} → ${label}`);\r\n const lines = [\r\n `${v.view} @ ${v.granularity}: ${v.nodes.length} nodes, ${v.edges.length} edges`,\r\n ...(labelSamples.length > 0 ? [`labels:\\n${labelSamples.join('\\n')}`] : []),\r\n ...(v.reason ? [`note: ${v.reason}`] : []),\r\n ];\r\n return [{ type: 'text' as const, text: lines.join('\\n') }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string; view: string; granularity?: string; focus?: string };\r\n return {\r\n card: 'generic' as const,\r\n title: 'Slice graph view',\r\n kind: 'other' as const,\r\n rawInput: { repoId: a.repoId, view: a.view, granularity: a.granularity ?? 'symbol' },\r\n };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'Graph slice failed' : 'Graph slice',\r\n }),\r\n execute: async (args) => {\r\n const slice = await executeGraphSlice(args as {\r\n repoId: string;\r\n view: GraphSliceView;\r\n granularity?: GraphSliceGranularity;\r\n focus?: string;\r\n commitLog?: string;\r\n blameLog?: string;\r\n }, invoke);\r\n return toJsonValue(slice);\r\n },\r\n });\r\n\r\n // cycle-138:Π 视图划分函数(研究方案 §4 的 Π 维)。\r\n //\r\n // 与 `graph_slice` 的区别:`graph_slice` 回答「按某个视图切出子图」,本工具回答\r\n // 「每个节点/边属于**哪些**视图」。后者是 §4 的 Π: V ∪ E → 2^{V_view} 的正式实现,\r\n // 前者只是它的一个投影。\r\n //\r\n // 四个视图的事实来源(缺哪个就缺哪个视图,绝不静默补全):\r\n // - structural:恒属所有节点/边(基图);\r\n // - temporal:`commitLog` 里被触及的文件;\r\n // - ownership:`blameLog` 里有记录的节点;\r\n // - domain:Louvain 社区检测分配到的节点。\r\n const graphViews = defineTool({\r\n name: 'graph_views',\r\n description:\r\n 'Report the view partition Π (研究方案 §4): which orthogonal views each node and edge belongs to. The four views are structural (base graph, always), temporal (files touched by a commit slice), ownership (nodes with a git blame record) and domain (Louvain community assignment). Use this when you need to know \"which views does this symbol belong to\" rather than \"give me the subgraph of one view\" (that is graph_slice). Temporal/ownership views degrade explicitly when their raw git input (commitLog / blameLog) is absent.',\r\n parameters: {\r\n repoId: { type: 'string', required: true, description: 'Repository id.' },\r\n focus: {\r\n type: 'string',\r\n description: 'Node id/label (1-hop neighborhood) or file path (whole file). Omit to partition the whole graph.',\r\n },\r\n commitLog: {\r\n type: 'string',\r\n description: 'Raw `git log --name-only --format=%x1e%H%x1f%ct` output (temporal view).',\r\n },\r\n blameLog: {\r\n type: 'string',\r\n description: 'Raw `git blame --porcelain` output (ownership view).',\r\n },\r\n days: { type: 'number', description: 'Temporal slice: only commits within the last N days.' },\r\n from: { type: 'string', description: 'Temporal slice: keep commits from this hash onward (inclusive).' },\r\n to: { type: 'string', description: 'Temporal slice: keep commits up to this hash (inclusive).' },\r\n },\r\n output: {\r\n schema: { type: 'json' },\r\n render: (_args: unknown, value: unknown) => {\r\n const v = value as {\r\n nodes?: Array<{ id: string; views: string[] }>;\r\n edges?: Array<{ id: string; views: string[] }>;\r\n viewCounts?: Record<string, number>;\r\n edgeViewCounts?: Record<string, number>;\r\n domain?: { domainCount: number; domainNames: string[] };\r\n } | null;\r\n if (!v) return [{ type: 'text' as const, text: 'View partition unavailable' }];\r\n const lines = [\r\n `Π view partition: ${v.nodes?.length ?? 0} nodes, ${v.edges?.length ?? 0} edges`,\r\n `view counts: ${Object.entries(v.viewCounts ?? {}).map(([k, n]) => `${k}=${n}`).join(', ')}`,\r\n ];\r\n if (v.domain) lines.push(`domain: ${v.domain.domainCount} communities (${v.domain.domainNames.join(', ') || 'none'})`);\r\n const samples = (v.nodes ?? []).slice(0, 8).map((n) => ` ${n.id} → [${n.views.join(', ')}]`);\r\n if (samples.length > 0) lines.push(`samples:\\n${samples.join('\\n')}`);\r\n return [{ type: 'text' as const, text: lines.join('\\n') }];\r\n },\r\n },\r\n presentCall: (args) => {\r\n const a = args as { repoId: string; focus?: string; commitLog?: string; blameLog?: string };\r\n return {\r\n card: 'generic' as const,\r\n title: 'Report view partition Π',\r\n kind: 'other' as const,\r\n rawInput: { repoId: a.repoId, ...(a.focus === undefined ? {} : { focus: a.focus }) },\r\n };\r\n },\r\n presentResult: (_args, result) => ({\r\n card: 'generic' as const,\r\n title: result.isError ? 'View partition failed' : 'View partition Π',\r\n }),\r\n execute: async (args) => {\r\n const a = args as {\r\n repoId: string;\r\n focus?: string;\r\n commitLog?: string;\r\n blameLog?: string;\r\n days?: number;\r\n from?: string;\r\n to?: string;\r\n };\r\n const data = await fetchMergedGraph(invoke, a.repoId);\r\n if (data.nodes.length === 0) return null;\r\n\r\n // focus 邻域(与 graph_slice 同一函数、同一口径)\r\n const base = a.focus\r\n ? focusNeighborhood(data.nodes, data.edges, a.focus, DEFAULT_MAX_NEIGHBORS)\r\n : { nodes: data.nodes, edges: data.edges };\r\n\r\n // 时序事实:被 commit 切片触及的文件(归一化路径)\r\n let temporalFiles: Set<string> | undefined;\r\n if (a.commitLog && a.commitLog.trim() !== '') {\r\n const history = { slices: parseCommitLog(a.commitLog), head: null, available: true };\r\n const range: CommitRange = {\r\n ...(a.days !== undefined ? { days: a.days } : {}),\r\n ...(a.from ? { from: a.from } : {}),\r\n ...(a.to ? { to: a.to } : {}),\r\n };\r\n const { slices } = filterSlices(history, range);\r\n temporalFiles = new Set<string>();\r\n for (const slice of slices) for (const file of slice.files) temporalFiles.add(file);\r\n }\r\n\r\n // 所有权事实:有 blame 记录的节点\r\n let attributedNodeIds: ReadonlySet<string> | undefined;\r\n if (a.blameLog && a.blameLog.trim() !== '') {\r\n const ownership = buildOwnershipMap(base.nodes, groupBlameByFile(parseGitBlamePorcelain(a.blameLog)));\r\n attributedNodeIds = new Set(Object.keys(ownership.nodeLabels));\r\n }\r\n\r\n // 领域事实:Louvain 社区\r\n const domain = await computeDomainLabels(base.nodes, base.edges);\r\n const domainByNodeId = new Map<string, string>();\r\n for (const [id, label] of Object.entries(domain.labels)) {\r\n if (label) domainByNodeId.set(id, label);\r\n }\r\n\r\n const partition = partitionByView({\r\n data: base,\r\n temporalFiles,\r\n attributedNodeIds,\r\n domainByNodeId,\r\n });\r\n\r\n return toJsonValue({\r\n ...serializeViewPartition(partition),\r\n domain: { domainCount: domain.domainCount, domainNames: domain.domainNames },\r\n });\r\n },\r\n });\r\n\r\n return {\r\n graphStatus,\r\n graphData,\r\n graphSymbol,\r\n graphImpact,\r\n graphPathFind,\r\n graphPathEnumerate,\r\n graphPathPattern,\r\n graphCycles,\r\n graphHealth,\r\n graphRulesCheck,\r\n graphFocus,\r\n graphQuery,\r\n graphIntegrity,\r\n graphSlice,\r\n graphViews,\r\n dispose: () => {\r\n xuanjiPool.dispose();\r\n graphCache.clear();\r\n },\r\n };\r\n};\r\n\r\n\r\n","export const PLUGIN_VERSION = '0.3.0';\n","import Schema from '@deepseek-ai/schemastery';\r\nimport type { DataSourceType } from '../types/index.ts';\r\nimport { ERROR_CODES } from '../shared/constants.ts';\r\n\r\nconst VALID_DATA_SOURCES = new Set<DataSourceType>(['auto', 'codegraph', 'lens', 'tree-sitter', 'dependency']);\r\nconst ORIGIN_PATTERN = /^https?:\\/\\/[a-zA-Z0-9.-]+(:\\d+)?$/;\r\n\r\nexport interface VisualizerConfig {\r\n dataSource: DataSourceType;\r\n requestTimeout: number;\r\n scanCacheTtl: number;\r\n scanCacheLimit: number;\r\n maxBodyBytes: number;\r\n prerequisiteRetryDelay: number;\r\n watchDebounce: number;\r\n maxNodes: number;\r\n allowedOrigins: string[];\r\n panelMinHeight: number;\r\n panelDefaultHeight: number;\r\n}\r\n\r\nexport const DEFAULT_CONFIG: VisualizerConfig = {\r\n dataSource: 'auto',\r\n requestTimeout: 15000,\r\n scanCacheTtl: 30_000,\r\n scanCacheLimit: 4,\r\n maxBodyBytes: 1024 * 1024,\r\n prerequisiteRetryDelay: 3000,\r\n watchDebounce: 500,\r\n maxNodes: 500_000,\r\n allowedOrigins: ['http://localhost:3080'],\r\n panelMinHeight: 240,\r\n panelDefaultHeight: 600,\r\n};\r\n\r\n/**\r\n * Schemastery schema(cycle-146 T146-1)——与 `VisualizerConfig` 同形状的**加载时**校验器。\r\n *\r\n * ## 为什么必须有它\r\n *\r\n * 旧实现只导出 `resolveConfig`/`DEFAULT_CONFIG`:插件没有 Standard Schema 接口,于是\r\n * ① cordis.yml 的 config 行没有声明式校验(错误要等 apply 内手动校验才报);\r\n * ② 宿主设置 UI 无法渲染行内表单(dsh-context/dsh-ops 双插件的 `Config` 导出模式)。\r\n *\r\n * 官方规范(docs/user/develop/basic/config.md):插件导出 `Config` 类型 + 同名 Schemastery\r\n * schema,默认值直接写在 schema 中;cordis 在插件加载时通过 schema 校验配置并填默认值。\r\n *\r\n * ## 约束:与 resolveConfig 的单一真源关系\r\n *\r\n * `resolveConfig` 的手写校验(正数/origin 模式/高度下界)保留——它是运行时防线,错误\r\n * 信息更具体;schema 是加载时防线。两者必须同形状:`configSchemaContract.test.ts`\r\n * 断言「schema 声明的默认值 === DEFAULT_CONFIG 字段值」,防止两处漂移。\r\n */\r\nexport const Config = Schema.object({\r\n dataSource: Schema.union(['auto', 'codegraph', 'lens', 'tree-sitter', 'dependency'])\r\n .description('Graph data source: auto (degradation chain) / codegraph / lens / tree-sitter / dependency')\r\n .default('auto'),\r\n requestTimeout: Schema.number().min(1000).description('Upstream request timeout (ms)').default(DEFAULT_CONFIG.requestTimeout),\r\n scanCacheTtl: Schema.number().min(1000).description('Scan cache TTL (ms)').default(DEFAULT_CONFIG.scanCacheTtl),\r\n scanCacheLimit: Schema.number().min(1).description('Max concurrent scan cache entries').default(DEFAULT_CONFIG.scanCacheLimit),\r\n maxBodyBytes: Schema.number().min(1024).description('Max HTTP request body size (bytes)').default(DEFAULT_CONFIG.maxBodyBytes),\r\n prerequisiteRetryDelay: Schema.number().min(500).description('Prerequisite re-probe interval (ms)').default(DEFAULT_CONFIG.prerequisiteRetryDelay),\r\n watchDebounce: Schema.number().min(100).description('File watcher debounce (ms)').default(DEFAULT_CONFIG.watchDebounce),\r\n maxNodes: Schema.number().min(100).description('Max nodes rendered in the graph').default(DEFAULT_CONFIG.maxNodes),\r\n allowedOrigins: Schema.array(Schema.string().pattern(/^https?:\\/\\/[a-zA-Z0-9.-]+(:\\d+)?$/))\r\n .description('Allowed CORS origins for cross-origin clients')\r\n .default(DEFAULT_CONFIG.allowedOrigins),\r\n panelMinHeight: Schema.number().min(120).description('Minimum panel height (px)').default(DEFAULT_CONFIG.panelMinHeight),\r\n panelDefaultHeight: Schema.number().min(120).description('Default panel height (px)').default(DEFAULT_CONFIG.panelDefaultHeight),\r\n});\r\n\r\nexport function resolveConfig(userConfig?: Partial<VisualizerConfig>): VisualizerConfig {\r\n const config: VisualizerConfig = { ...DEFAULT_CONFIG, ...userConfig };\r\n const errors: string[] = [];\r\n if (!VALID_DATA_SOURCES.has(config.dataSource)) {\r\n errors.push(`dataSource must be auto|codegraph|lens|tree-sitter, got ${String(config.dataSource)}`);\r\n }\r\n for (const origin of config.allowedOrigins) {\r\n if (!ORIGIN_PATTERN.test(origin)) {\r\n errors.push(`allowedOrigins contains invalid origin: ${origin} (expected http(s)://host[:port])`);\r\n }\r\n }\r\n const positiveFields: Array<[keyof VisualizerConfig, number]> = [\r\n ['requestTimeout', config.requestTimeout],\r\n ['scanCacheTtl', config.scanCacheTtl],\r\n ['scanCacheLimit', config.scanCacheLimit],\r\n ['maxBodyBytes', config.maxBodyBytes],\r\n ['prerequisiteRetryDelay', config.prerequisiteRetryDelay],\r\n ['watchDebounce', config.watchDebounce],\r\n ['maxNodes', config.maxNodes],\r\n ['panelMinHeight', config.panelMinHeight],\r\n ['panelDefaultHeight', config.panelDefaultHeight],\r\n ];\r\n for (const [field, value] of positiveFields) {\r\n if (!Number.isFinite(value) || value <= 0) {\r\n errors.push(`${field} must be a positive finite number, got ${String(value)}`);\r\n }\r\n }\r\n if (config.panelDefaultHeight < config.panelMinHeight) {\r\n errors.push(`panelDefaultHeight (${config.panelDefaultHeight}) must be >= panelMinHeight (${config.panelMinHeight})`);\r\n }\r\n if (errors.length > 0) {\r\n throw new Error(`${ERROR_CODES.INVALID_CONFIG}: ${errors.join('; ')}`);\r\n }\r\n return config;\r\n}","import { normalize, isAbsolute, sep } from 'node:path';\r\nimport { platform } from 'node:os';\r\n\r\nconst isCaseInsensitive = platform() === 'win32';\r\n\r\n/**\r\n * Comparison key for a path: normalized, without a trailing separator (so `C:\\x\\` and `C:\\x` are\r\n * the same root and `root + sep` never becomes a double separator), lower-cased on Windows.\r\n * A bare drive (`C:`) or filesystem root (`/`) keeps its separator.\r\n */\r\nfunction normalizeForComparison(p: string): string {\r\n const n = normalize(p);\r\n const trimmed = n.replace(/[\\\\/]+$/, '');\r\n const stable = trimmed === '' || /^[a-zA-Z]:$/.test(trimmed) ? n : trimmed;\r\n return isCaseInsensitive ? stable.toLowerCase() : stable;\r\n}\r\n\r\n/**\r\n * Which host source produced the allowed roots. Reported by the workspace route so a\r\n * \"everything is 403\" report can be diagnosed from outside the process instead of\r\n * inferred from a silent 403 (see docs/需求分析.md §31.7).\r\n */\r\nexport type WorkspaceRootSource = 'registry' | 'sessions' | 'cwd' | 'unknown';\r\n\r\nexport interface WorkspaceRootsSnapshot {\r\n roots: string[];\r\n source: WorkspaceRootSource;\r\n}\r\n\r\nexport class SecurityManager {\r\n /** Raw paths as the host reported them; normalization happens at comparison time. */\r\n private allowedWorkspaceRoots: string[] = [];\r\n private rootSource: WorkspaceRootSource = 'unknown';\r\n\r\n setAllowedWorkspaceRoots(roots: string[], source: WorkspaceRootSource = 'unknown'): void {\r\n this.allowedWorkspaceRoots = roots.filter((r) => !!r);\r\n this.rootSource = source;\r\n }\r\n\r\n /**\r\n * Grow the allowlist instead of replacing it.\r\n *\r\n * The roots come from host services only (`workspaceRegistry` / `sessions`), never from a\r\n * request, so widening is safe; shrinking is the dangerous direction because a transiently\r\n * empty registry would deny every in-flight request (the §31.7 failure mode). A workspace\r\n * removed at runtime therefore stays readable until the host restarts, which is the\r\n * conservative choice for a local, read-only analysis plugin.\r\n */\r\n addAllowedWorkspaceRoots(roots: string[], source?: WorkspaceRootSource): void {\r\n const seen = new Set(this.allowedWorkspaceRoots.map(normalizeForComparison));\r\n let added = false;\r\n for (const root of roots) {\r\n if (!root) continue;\r\n const key = normalizeForComparison(root);\r\n if (seen.has(key)) continue;\r\n seen.add(key);\r\n this.allowedWorkspaceRoots.push(root);\r\n added = true;\r\n }\r\n if (source && (added || this.rootSource === 'unknown')) {\r\n this.rootSource = source;\r\n }\r\n }\r\n\r\n isPathAllowed(path: string): boolean {\r\n if (!path) return false;\r\n const normalized = normalize(path);\r\n if (normalized.includes('..')) return false;\r\n if (!isAbsolute(normalized)) {\r\n return false;\r\n }\r\n if (this.allowedWorkspaceRoots.length === 0) return false;\r\n const cmpPath = normalizeForComparison(normalized);\r\n return this.allowedWorkspaceRoots.some((root) => {\r\n const cmpRoot = normalizeForComparison(root);\r\n return cmpPath === cmpRoot || cmpPath.startsWith(cmpRoot + sep);\r\n });\r\n }\r\n\r\n /** Observable view of the allowlist (workspace route / tests). Never redacted: it is local-only. */\r\n getSnapshot(): WorkspaceRootsSnapshot {\r\n return { roots: [...this.allowedWorkspaceRoots], source: this.rootSource };\r\n }\r\n\r\n reset(): void {\r\n this.allowedWorkspaceRoots = [];\r\n this.rootSource = 'unknown';\r\n }\r\n}\r\n\r\nexport function createSecurityManager(): SecurityManager {\r\n return new SecurityManager();\r\n}\r\n","import { scoped } from '../shared/Logger.ts';\r\nimport type { GraphNode, GraphEdge } from '../types/index.ts';\r\nimport { NodeId, EdgeId } from '../types/index.ts';\r\n\r\n\r\nconst log = scoped('dep-adapter');\r\n\r\nexport interface DependencyNode {\r\n id: string;\r\n name: string;\r\n version: string;\r\n type: 'npm' | 'cargo' | 'gomod' | 'pip';\r\n}\r\n\r\nexport interface DependencyEdge {\r\n source: string;\r\n target: string;\r\n type: 'depends';\r\n scope: 'prod' | 'dev' | 'peer' | 'optional';\r\n}\r\n\r\nexport interface DependencyGraph {\r\n nodes: DependencyNode[];\r\n edges: DependencyEdge[];\r\n}\r\n\r\nconst SEMVER_RE = /^\\d+\\.\\d+\\.\\d+/;\r\n\r\nfunction sanitizeVersion(raw: unknown): string {\r\n const v = String(raw ?? '');\r\n return SEMVER_RE.test(v) ? v : '0.0.0';\r\n}\r\n\r\nexport function parsePackageJson(content: string): DependencyGraph {\r\n const nodes: DependencyNode[] = [];\r\n const edges: DependencyEdge[] = [];\r\n const seenIds = new Set<string>();\r\n try {\r\n const pkg = JSON.parse(content);\r\n const rootName = pkg.name || 'root';\r\n const rootId = `dep:${rootName}`;\r\n nodes.push({ id: rootId, name: rootName, version: sanitizeVersion(pkg.version), type: 'npm' });\r\n seenIds.add(rootId);\r\n\r\n const depSections: Array<{ key: string; scope: DependencyEdge['scope'] }> = [\r\n { key: 'dependencies', scope: 'prod' },\r\n { key: 'devDependencies', scope: 'dev' },\r\n { key: 'peerDependencies', scope: 'peer' },\r\n { key: 'optionalDependencies', scope: 'optional' },\r\n ];\r\n for (const { key, scope } of depSections) {\r\n const deps = pkg[key];\r\n if (!deps || typeof deps !== 'object') continue;\r\n for (const [name, version] of Object.entries(deps)) {\r\n const depId = `dep:${name}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name, version: sanitizeVersion(version), type: 'npm' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope });\r\n }\r\n }\r\n } catch (e) {\r\n log.warn('parsePackageJson failed', e);\r\n }\r\n return { nodes, edges };\r\n}\r\n\r\nexport function parseCargoToml(content: string): DependencyGraph {\r\n const nodes: DependencyNode[] = [];\r\n const edges: DependencyEdge[] = [];\r\n const seenIds = new Set<string>();\r\n try {\r\n const lines = content.split('\\n');\r\n let rootName = 'root';\r\n for (const line of lines) {\r\n const nameMatch = line.match(/^name\\s*=\\s*\"([^\"]+)\"/);\r\n if (nameMatch) { rootName = nameMatch[1]!; break; }\r\n }\r\n const rootId = `dep:${rootName}`;\r\n nodes.push({ id: rootId, name: rootName, version: '0.0.0', type: 'cargo' });\r\n seenIds.add(rootId);\r\n\r\n let inDepsSection = false;\r\n let pendingTableDep: { name: string; fields: string[] } | null = null;\r\n for (const line of lines) {\r\n const tableDepHeaderMatch = line.match(/^\\[dependencies\\.([a-zA-Z0-9_-]+)\\]/);\r\n if (tableDepHeaderMatch) {\r\n const depName = tableDepHeaderMatch[1]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: '0.0.0', type: 'cargo' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n inDepsSection = false;\r\n continue;\r\n }\r\n if (line.match(/^\\[dependencies\\]/)) { inDepsSection = true; continue; }\r\n if (line.match(/^\\[/)) { inDepsSection = false; continue; }\r\n if (!inDepsSection) continue;\r\n\r\n if (pendingTableDep) {\r\n const trimmed = line.trim();\r\n if (trimmed === '}') {\r\n const versionField = pendingTableDep.fields.find((f) => f.match(/^version\\s*=\\s*\"/));\r\n if (versionField) {\r\n const versionMatch = versionField.match(/^version\\s*=\\s*\"([^\"]+)\"/);\r\n if (versionMatch) {\r\n const depName = pendingTableDep.name;\r\n const depVersion = versionMatch[1]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: depVersion, type: 'cargo' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n }\r\n }\r\n pendingTableDep = null;\r\n continue;\r\n }\r\n pendingTableDep.fields.push(line.trim());\r\n continue;\r\n }\r\n\r\n const simpleDepMatch = line.match(/^([a-zA-Z0-9_-]+)\\s*=\\s*\"([^\"]+)\"/);\r\n if (simpleDepMatch) {\r\n const depName = simpleDepMatch[1]!;\r\n const depVersion = simpleDepMatch[2]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: depVersion, type: 'cargo' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n continue;\r\n }\r\n const tableDepStartMatch = line.match(/^([a-zA-Z0-9_-]+)\\s*=\\s*\\{(.*)$/);\r\n if (tableDepStartMatch) {\r\n const depName = tableDepStartMatch[1]!;\r\n const rest = tableDepStartMatch[2]!;\r\n if (rest.includes('}')) {\r\n const inlineMatch = rest.match(/^([^}]*)\\}/);\r\n if (inlineMatch) {\r\n const tableContent = inlineMatch[1]!;\r\n const versionMatch = tableContent.match(/version\\s*=\\s*\"([^\"]+)\"/);\r\n if (versionMatch) {\r\n const depVersion = versionMatch[1]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: depVersion, type: 'cargo' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n }\r\n continue;\r\n }\r\n }\r\n pendingTableDep = { name: depName, fields: [rest] };\r\n continue;\r\n }\r\n }\r\n } catch (e) {\r\n log.warn('parseCargoToml failed', e);\r\n }\r\n return { nodes, edges };\r\n}\r\n\r\nexport function parseGoMod(content: string): DependencyGraph {\r\n const nodes: DependencyNode[] = [];\r\n const edges: DependencyEdge[] = [];\r\n const seenIds = new Set<string>();\r\n try {\r\n const lines = content.split('\\n');\r\n let moduleName = 'root';\r\n for (const line of lines) {\r\n const moduleMatch = line.match(/^module\\s+(\\S+)/);\r\n if (moduleMatch) { moduleName = moduleMatch[1]!; break; }\r\n }\r\n const rootId = `dep:${moduleName}`;\r\n nodes.push({ id: rootId, name: moduleName, version: '0.0.0', type: 'gomod' });\r\n seenIds.add(rootId);\r\n\r\n let inRequireBlock = false;\r\n for (const rawLine of lines) {\r\n const trimmed = rawLine.trim();\r\n if (trimmed.startsWith('//') || trimmed.startsWith('/*')) continue;\r\n const line = trimmed.replace(/\\s*\\/\\/.*$/, '');\r\n if (line.match(/^require\\s*\\(/)) { inRequireBlock = true; continue; }\r\n if (line.match(/^\\)/)) { inRequireBlock = false; continue; }\r\n if (!inRequireBlock) {\r\n const singleRequireMatch = line.match(/^require\\s+(\\S+)\\s+(\\S+)/);\r\n if (singleRequireMatch) {\r\n const depName = singleRequireMatch[1]!;\r\n const depVersion = singleRequireMatch[2]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: depVersion, type: 'gomod' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n continue;\r\n }\r\n continue;\r\n }\r\n const depMatch = line.match(/^\\s*(\\S+)\\s+(\\S+)/);\r\n if (depMatch) {\r\n const depName = depMatch[1]!;\r\n const depVersion = depMatch[2]!;\r\n const depId = `dep:${depName}`;\r\n if (!seenIds.has(depId)) {\r\n seenIds.add(depId);\r\n nodes.push({ id: depId, name: depName, version: depVersion, type: 'gomod' });\r\n }\r\n edges.push({ source: rootId, target: depId, type: 'depends', scope: 'prod' });\r\n }\r\n }\r\n } catch (e) {\r\n log.warn('parseGoMod failed', e);\r\n }\r\n return { nodes, edges };\r\n}\r\n\r\nexport function dependencyGraphToGraphData(depGraph: DependencyGraph): { nodes: GraphNode[]; edges: GraphEdge[] } {\r\n const sourceIds = new Set(depGraph.edges.map((e) => e.source));\r\n const nodes: GraphNode[] = depGraph.nodes.map((n) => {\r\n let filePath = '';\r\n const isRoot = sourceIds.has(n.id);\r\n if (n.type === 'npm') {\r\n filePath = n.id === `dep:${n.name}` && isRoot ? 'package.json' : `node_modules/${n.name}`;\r\n } else if (n.type === 'cargo') {\r\n filePath = isRoot ? 'Cargo.toml' : `cargo/${n.name}`;\r\n } else if (n.type === 'gomod') {\r\n filePath = isRoot ? 'go.mod' : `go/${n.name}`;\r\n } else {\r\n filePath = n.name;\r\n }\r\n return {\r\n id: NodeId(n.id),\r\n label: n.name,\r\n type: 'module',\r\n filePath,\r\n lineNumber: 0,\r\n properties: { version: n.version, depType: n.type, isExternal: true },\r\n layer: 'dependency',\r\n };\r\n });\r\n const edges: GraphEdge[] = depGraph.edges.map((e) => ({\r\n id: EdgeId(`${e.source}->${e.target}:dependency`),\r\n source: NodeId(e.source),\r\n target: NodeId(e.target),\r\n type: 'dependency',\r\n properties: { scope: e.scope },\r\n }));\r\n return { nodes, edges };\r\n}\r\n","import { execFile } from 'node:child_process';\r\nimport { promisify } from 'node:util';\r\nimport type { Context } from '@deepseek-ai/cordis';\r\nimport { scoped } from '../shared/Logger.ts';\r\nimport { POLLING } from '../shared/constants.ts';\r\nimport type { PrerequisiteStatus } from '../shared/prerequisiteStatus.ts';\r\nimport { parsePackageJson } from '../adapters/DependencyAdapter.ts';\r\nimport type { ApiCompatLayer } from './compat/index.ts';\r\nimport type { WorkspaceRootSource } from './security.ts';\r\n\r\nconst log = scoped('host');\r\nconst execFileAsync = promisify(execFile);\r\n\r\nexport async function detectCodegraphCli(): Promise<boolean> {\r\n try {\r\n const cmd = process.platform === 'win32' ? 'codegraph.cmd' : 'codegraph';\r\n // windowsHide: true —— 与 gitDiff/gitChurn 同一纪律(cycle-105 真机经验):\r\n // Windows 上缺它时每次前置探测都会闪一个控制台窗口(\"命令窗口显示问题\")。\r\n // shell: win32 是刻意保留的:codegraph.cmd 需要 shell 解析(DEP0190 只警告\r\n // shell:true 下拼接含空格/元字符的参数——本调用只有固定字面量 ['--version'],无注入面)。\r\n await execFileAsync(cmd, ['--version'], {\r\n timeout: POLLING.PREREQ_CHECK_TIMEOUT_MS,\r\n shell: process.platform === 'win32',\r\n windowsHide: true,\r\n });\r\n return true;\r\n } catch (e) {\r\n log.warn('detectCodegraphCli failed', e);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Tool names that prove the optional upstream plugin is installed.\r\n *\r\n * `codegraph_status`/`codegraph_graph`/`codegraph_query` are the surface our adapters talk to.\r\n * `mcp__codegraph__codegraph_explore` is the only tool the published plugin actually\r\n * contributes — verified against `dsh-codegraph@0.1.3` in the J0 profile, whose\r\n * `dsh.plugin.json` declares `contributes.tools: [\"mcp__codegraph__codegraph_explore\"]`.\r\n * Detecting presence solely through the `codegraph_status` family reported \"prerequisite\r\n * missing\" on a host that had the plugin installed, which also suppressed the client's\r\n * auto-bootstrap (`src/client/hooks/useAutoBootstrap.ts` gates on `codegraphReady`).\r\n */\r\nconst CODEGRAPH_TOOL_EVIDENCE = [\r\n 'codegraph_status',\r\n 'codegraph_graph',\r\n 'codegraph_query',\r\n 'mcp__codegraph__codegraph_explore',\r\n] as const;\r\n\r\n/** The `dsh-tool-lens` surface (absent from the J0 profile, so only its names are known). */\r\nconst LENS_TOOL_EVIDENCE = ['lens_analyze', 'lens_impact'] as const;\r\n\r\n/**\r\n * 内置 **AST 分析通道**的探针。由宿主在构造共享 `TreeSitterAdapter` 之后登记\r\n * (`src/host/routes.ts`),因为 `checkPrerequisites(ctx)` 是**同步**签名,而解析器初始化\r\n * (`await import('web-tree-sitter')` + `Parser.init()`)是异步的。把它改成 async 会波及\r\n * 20 余处调用点(其中 `src/index.ts` 的同步轮询定时器直接读结果),代价远大于收益。\r\n *\r\n * 没有登记时一律判 `false`:**不知道不等于有**。这条方向是有意的 —— 把缺省写成 `true`\r\n * 正是本轮要修的缺陷(`docs/需求分析.md` §61.10)。\r\n */\r\nlet astSourceProbe: (() => boolean) | null = null;\r\n\r\n/**\r\n * 登记 AST 通道探针。返回注销函数,调用方(`registerRoutes`)必须把它接进 `ctx.effect`\r\n * 的清理链:模块级状态在插件卸载后仍指向已 `destroy()` 的适配器时,下一次 `apply()`\r\n * 会读到上一轮的实例(同一进程内的 HMR / profile 重载场景)。\r\n */\r\nexport function setAstSourceProbe(probe: (() => boolean) | null): () => void {\r\n astSourceProbe = probe;\r\n return () => { if (astSourceProbe === probe) astSourceProbe = null; };\r\n}\r\n\r\n/**\r\n * 内置**图数据通道**的自检。\r\n *\r\n * 内置的图产出只有 manifest 依赖图(`package.json` / `Cargo.toml` / `go.mod`,由\r\n * `DependencyAdapter` 的纯函数解析,经 `/dependencies` 供给客户端「依赖」视图)。\r\n * `fetchMergedGraph` 的 `auto` 分支在外部源全空时**只记日志、不产节点**\r\n * (`src/tools.ts:132-134`)—— 所以「内置自动分析」不是一个图数据源,不能算进这个位。\r\n *\r\n * 判据是**跑一遍解析器**,不是写 `true`:本函数存在的全部理由就是不让第二个恒真式出现。\r\n * 解析器是纯函数,输入是一段固定 JSON,因此结果对同一份代码是常量 —— 但它每次调用都重算:\r\n * 缓存能力快照是本仓反复记录的缺陷放大器(`CLAUDE.md` 的「能力快照会放大缺陷」),而\r\n * 一次 `JSON.parse` 的成本远低于一次快照误判的代价。\r\n *\r\n * 导出是为了让它**可被证伪**:若它只在 `checkPrerequisites` 内部被调用,把实现换成\r\n * `return true` 不会有任何用例变红(2026-10-29 实测:变异 M5 换掉后 36 条用例全绿)。\r\n * 一个不能被证伪的探针与写死 `true` 没有区别,只是多了一层看起来很像检查的代码。\r\n *\r\n * `parser` 参数只为**测试可注入**而存在(默认就是生产用的那个纯函数)。为什么不改成\r\n * `vi.spyOn(DependencyAdapter)`:ESM 静态导入的绑定在调用点已经固化,spy 只能改模块\r\n * 命名空间对象、改不到 `prerequisites.ts` 里的那份引用 —— 那样写出来的\"探针失败\"用例\r\n * 会**永远绿**(测的是一个没被真正调用的替身)。注入点是唯一能被证伪的形态。\r\n */\r\nexport function probeGraphSource(parser: (content: string) => { nodes: unknown[]; edges: unknown[] } = parsePackageJson): boolean {\r\n try {\r\n const graph = parser('{\"name\":\"__capability_probe__\",\"dependencies\":{\"x\":\"1.0.0\"}}');\r\n // 两个条件都要满足:只产节点不产边(或反之)说明解析器只完成了一半,\r\n // 那种状态画不出一张可用的依赖图,不该被算作「图通道存在」。\r\n return graph.nodes.length > 0 && graph.edges.length > 0;\r\n } catch (e) {\r\n log.warn('probeGraphSource failed', e);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 能力位(`docs/需求分析.md` §61.10 的修法)。字段语义见\r\n * `src/shared/prerequisiteStatus.ts` 的模块头——那里是宿主与客户端**共用**的唯一来源。\r\n *\r\n * 历史:修前两处分支都写死 `builtin: true`,`/health` 恒 healthy、客户端空态无条件声称\r\n * 「内置分析可用」。cycle-111 拆位时 `builtin` 曾作为 `astSource` 的派生别名保留一个\r\n * 兼容窗口,cycle-118 按判据(`docs/agents/09` §9.4)移除。\r\n */\r\nexport function checkPrerequisites(ctx: Context, apiCompat?: ApiCompatLayer): PrerequisiteStatus {\r\n // AST 探针自身的失败不能把整条 `checkPrerequisites` 打成异常分支:那样 `codegraph` / `lens`\r\n // 的真实探测结果会一起被丢掉,一个适配器的问题就变成「所有上游都不可用」。\r\n let astSource = false;\r\n try {\r\n astSource = astSourceProbe ? astSourceProbe() === true : false;\r\n } catch (e) {\r\n log.warn('checkPrerequisites: astSource probe failed', e);\r\n astSource = false;\r\n }\r\n const graphSource = probeGraphSource();\r\n\r\n try {\r\n return {\r\n codegraph: isToolPresent(ctx, apiCompat, 'codegraphStatus', CODEGRAPH_TOOL_EVIDENCE),\r\n lens: isToolPresent(ctx, apiCompat, 'lensAnalyze', LENS_TOOL_EVIDENCE),\r\n astSource,\r\n graphSource,\r\n };\r\n } catch (e) {\r\n log.warn('checkPrerequisites failed', e);\r\n // 异常分支同样要如实报告两个内置通道:它们与 `ctx.tools` 无关,不该被上游探测的\r\n // 失败牵连成「内置也不可用」。`graphSource` 的自检在 try 之外已算好。\r\n return { codegraph: false, lens: false, astSource, graphSource };\r\n }\r\n}\r\n\r\nfunction isToolPresent(\r\n ctx: Context,\r\n apiCompat: ApiCompatLayer | undefined,\r\n key: 'codegraphStatus' | 'lensAnalyze',\r\n evidence: readonly string[],\r\n): boolean {\r\n // Prefer the name the compat layer resolves for the detected generation, then fall back to\r\n // the full evidence list: a host we could not date may still expose one of these tools.\r\n if (apiCompat) {\r\n const resolved = apiCompat.getAvailableToolName(ctx, key);\r\n if (resolved && ctx.tools.get(resolved)) return true;\r\n }\r\n for (const name of evidence) {\r\n if (ctx.tools.get(name)) return true;\r\n }\r\n return false;\r\n}\r\n\r\ninterface WorkspaceEntry {\r\n path?: string;\r\n}\r\n\r\ninterface SessionEntry {\r\n header?: { cwd?: string };\r\n}\r\n\r\ntype SessionsLike = { list?(): SessionEntry[] } | undefined;\r\ntype WorkspaceRegistryLike = { list?(): WorkspaceEntry[] } | undefined;\r\n\r\nfunction getSessions(ctx: Context): SessionsLike {\r\n return (ctx as unknown as { sessions?: SessionsLike }).sessions;\r\n}\r\n\r\nfunction getWorkspaceRegistry(ctx: Context): WorkspaceRegistryLike {\r\n // 可选服务(gen2 起):走 `ctx.get()` 探测,让插件在没有该注册表的宿主上仍然激活。\r\n //\r\n // **必须传 `strict = false`(cycle-110 修复)**:`ctx.get(name)` 默认 strict 的含义是\r\n // 「提供者 fiber 当前活跃」而不是「服务存在」(cordis `lib/index.js:763-772`、\r\n // `lib/types/reflect.d.ts:16`)。本函数被 `resolveWorkspaceRoots()` 在**每个请求**里调用,\r\n // 而它第一次运行就发生在 `apply()` 的同一步里 —— 此时 `workspaceRegistry` 的提供者很可能\r\n // 还停在 loading。用默认值探测会把「稍后可用」永久判成「不存在」,于是工作区根只剩\r\n // sessions / cwd 两个回退分支,`addAllowedWorkspaceRoots` 也永远只拿到回退来源。\r\n // 这个缺陷在修之前不会让任何用例变红:回退路径同样能解析出根目录,只有「注册表里有、\r\n // sessions 里没有」的工作区会被静默丢掉。\r\n // 由 `tests/unit/generation/serviceProbeContract.test.ts` 的源码形状守卫兜底。\r\n return ctx.get('workspaceRegistry', false) as WorkspaceRegistryLike;\r\n}\r\n\r\nexport interface WorkspaceRootsResolution {\r\n paths: string[];\r\n source: WorkspaceRootSource;\r\n}\r\n\r\n/**\r\n * Resolve the workspace roots the host currently knows about, **and say which branch produced\r\n * them**. The label is the observability half of docs/需求分析.md §31.7: the plugin used to call\r\n * this once in `apply()` and freeze the result, so a boot-time empty registry silently denied\r\n * every later file route with a bare 403 and no way to see why. Callers re-resolve per request\r\n * (`ensureWorkspaceRootsAllowed` in `host/routes/shared.ts`).\r\n */\r\nexport function resolveWorkspaceRoots(ctx: Context): WorkspaceRootsResolution {\r\n try {\r\n const wsr = getWorkspaceRegistry(ctx);\r\n if (wsr?.list) {\r\n const workspaces = wsr.list();\r\n const paths = workspaces.map((w) => w.path).filter((p): p is string => !!p);\r\n if (paths.length > 0) return { paths, source: 'registry' };\r\n }\r\n } catch (e) { log.warn('resolveWorkspaceRoots: workspaceRegistry failed', e); }\r\n try {\r\n const sessions = getSessions(ctx);\r\n if (sessions?.list) {\r\n const all = sessions.list();\r\n const seen = new Set<string>();\r\n const paths: string[] = [];\r\n for (const session of all) {\r\n const cwd = session?.header?.cwd;\r\n if (cwd && !seen.has(cwd)) {\r\n seen.add(cwd);\r\n paths.push(cwd);\r\n }\r\n }\r\n if (paths.length > 0) return { paths, source: 'sessions' };\r\n }\r\n } catch (e) { log.warn('resolveWorkspaceRoots: sessions failed', e); }\r\n return { paths: [process.cwd()], source: 'cwd' };\r\n}\r\n\r\nexport function extractWorkspacePaths(ctx: Context): string[] {\r\n return resolveWorkspaceRoots(ctx).paths;\r\n}\r\n\r\nexport function findWorkspacePath(ctx: Context): string {\r\n try {\r\n const wsr = getWorkspaceRegistry(ctx);\r\n if (wsr?.list) {\r\n const workspaces = wsr.list();\r\n if (workspaces.length > 0 && workspaces[0]?.path) {\r\n return workspaces[0].path;\r\n }\r\n }\r\n } catch (e) { log.warn('findWorkspacePath: workspaceRegistry failed', e); }\r\n try {\r\n const sessions = getSessions(ctx);\r\n if (sessions?.list) {\r\n const all = sessions.list();\r\n for (const session of all) {\r\n const cwd = session?.header?.cwd;\r\n if (cwd) return cwd;\r\n }\r\n }\r\n } catch (e) { log.warn('findWorkspacePath: sessions failed', e); }\r\n return process.cwd();\r\n}\r\n","import { API } from '../shared/constants.ts';\r\n\r\nconst RATE_LIMIT_WINDOW_MS = API.RATE_LIMIT_WINDOW_MS;\r\nconst RATE_LIMIT_DEFAULT = API.RATE_LIMIT_DEFAULT;\r\nconst RATE_LIMIT_HEAVY = API.RATE_LIMIT_HEAVY;\r\nconst RATE_LIMIT_MAP_MAX = API.RATE_LIMIT_MAP_MAX;\r\nconst HEAVY_ENDPOINTS = new Set([\r\n '/api/v1/codegraph/scan',\r\n '/api/v1/codegraph/init',\r\n '/api/v1/codegraph/diff',\r\n // `/churn` 每次冷调用都会 fork 一个 git 子进程(最大 2000 提交的输出)——按重端点限流,\r\n // 否则热点面板的重复打开会把子进程数量交给客户端决定。\r\n '/api/v1/codegraph/churn',\r\n '/api/v1/codegraph/ast',\r\n '/api/v1/codegraph/tree-sitter',\r\n]);\r\n\r\ninterface RateBucket { count: number; resetTime: number; }\r\n\r\n/** 单桶白盒快照(cycle-146 T146-3):管理面板「限流观测」区的数据面。 */\r\nexport interface RateBucketSnapshot {\r\n /** 桶键(`ip:routePath`)。 */\r\n key: string;\r\n /** 窗口内已放行的请求数。 */\r\n count: number;\r\n /** 本窗口配额(heavy 端点与常规端点不同)。 */\r\n limit: number;\r\n /** 窗口剩余毫秒数(0 = 已过期,等待惰性清理)。 */\r\n resetInMs: number;\r\n}\r\n\r\n/** 限流器白盒快照:additive 字段,只读投影,不影响任何限流行为。 */\r\nexport interface RateLimiterSnapshot {\r\n /** 活跃桶数(窗口未过期的键)。 */\r\n activeBuckets: number;\r\n /** 每桶明细(按键排序,输出确定性)。 */\r\n buckets: RateBucketSnapshot[];\r\n /** 配置上界(heavy/regular 两档配额与窗口长度),面板渲染「配额」行的数据面。 */\r\n limits: { heavy: number; regular: number; windowMs: number };\r\n}\r\n\r\nexport function createRateLimiter() {\r\n const rateLimitMap = new Map<string, RateBucket>();\r\n let lastCleanup = Date.now();\r\n\r\n function check(ip: string, routePath: string): boolean {\r\n const key = `${ip}:${routePath}`;\r\n const limit = HEAVY_ENDPOINTS.has(routePath) ? RATE_LIMIT_HEAVY : RATE_LIMIT_DEFAULT;\r\n const now = Date.now();\r\n if (now - lastCleanup > RATE_LIMIT_WINDOW_MS) {\r\n for (const [k, b] of rateLimitMap) {\r\n if (now > b.resetTime) rateLimitMap.delete(k);\r\n }\r\n lastCleanup = now;\r\n }\r\n if (rateLimitMap.size >= RATE_LIMIT_MAP_MAX) {\r\n const oldestKey = rateLimitMap.keys().next().value;\r\n if (oldestKey !== undefined) rateLimitMap.delete(oldestKey);\r\n }\r\n const bucket = rateLimitMap.get(key);\r\n if (!bucket || now > bucket.resetTime) {\r\n rateLimitMap.set(key, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });\r\n return true;\r\n }\r\n if (bucket.count >= limit) return false;\r\n bucket.count++;\r\n return true;\r\n }\r\n\r\n function reset(): void {\r\n rateLimitMap.clear();\r\n lastCleanup = Date.now();\r\n }\r\n\r\n /**\r\n * 白盒快照(cycle-146 T146-3):活跃桶的只读投影。\r\n * 只反映**未过期**桶(过期桶在下一次 check/cleanup 时惰性删除,快照不虚报);\r\n * resetInMs 按快照时刻计算,输出按键排序保证确定性(面板 diff 友好)。\r\n */\r\n function snapshot(): RateLimiterSnapshot {\r\n const now = Date.now();\r\n const buckets: RateBucketSnapshot[] = [];\r\n for (const [key, bucket] of rateLimitMap) {\r\n if (now > bucket.resetTime) continue; // 过期桶尚未被惰性清理——不虚报\r\n buckets.push({\r\n key,\r\n count: bucket.count,\r\n limit: HEAVY_ENDPOINTS.has(key.slice(key.indexOf(':') + 1)) ? RATE_LIMIT_HEAVY : RATE_LIMIT_DEFAULT,\r\n resetInMs: Math.max(0, bucket.resetTime - now),\r\n });\r\n }\r\n buckets.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\r\n return {\r\n activeBuckets: buckets.length,\r\n buckets,\r\n limits: { heavy: RATE_LIMIT_HEAVY, regular: RATE_LIMIT_DEFAULT, windowMs: RATE_LIMIT_WINDOW_MS },\r\n };\r\n }\r\n\r\n return { check, reset, snapshot };\r\n}","/**\r\n * 判断两个仓库标识是否指向同一个工作区。\r\n *\r\n * `repoId` 在链路里可能来自三个地方:watcher 监听的绝对路径、`/api/v1/codegraph/data` 里\r\n * `lastGraphData.metadata.repoId`、以及客户端 store 的 `repoId`。它们的分隔符与大小写不保证一致\r\n * (Windows 上 `D:\\a\\b` 与 `D:/a/b` 是同一个目录,盘符也可能大小写不同),所以比较前必须归一:\r\n * 反斜杠统一成正斜杠、去掉尾部斜杠;只有两侧都是 Windows 盘符绝对路径时才忽略大小写(POSIX 文件\r\n * 系统区分大小写,无条件转小写会把 `/Home/A` 与 `/home/a` 误判为同源)。任一为空\r\n * (null/undefined/空串)一律视为\"不同源\"——宁可退化成一次全量拉取,也不能让一个空标识误判为同源。\r\n *\r\n * 这里刻意不读 `process.platform`:本文件同时被打进浏览器端 bundle(`tsconfig.client.json` 含\r\n * `src/shared/**`),浏览器里没有 `process`,用盘符形状判断既等效又不会在客户端抛错。\r\n */\r\nexport function sameRepoId(a: string | null | undefined, b: string | null | undefined): boolean {\r\n if (!a || !b) return false;\r\n const normalize = (value: string): string => value.replace(/\\\\/g, '/').replace(/\\/+$/, '');\r\n const left = normalize(a);\r\n const right = normalize(b);\r\n if (left === right) return true;\r\n const isWindowsDrive = /^[a-zA-Z]:\\//;\r\n return isWindowsDrive.test(left) && isWindowsDrive.test(right) && left.toLowerCase() === right.toLowerCase();\r\n}\r\n\r\nexport function getTopLevelDir(filePath: string, options?: { simplified?: boolean }): string {\r\n const normalized = filePath.replace(/\\\\/g, '/');\r\n const parts = normalized.split('/');\r\n\r\n if (options?.simplified) {\r\n if (parts.length <= 2) return '/';\r\n return parts.slice(0, 2).join('/');\r\n }\r\n\r\n if (parts.length <= 2) return filePath;\r\n\r\n let startIdx = 0;\r\n if (/^[A-Za-z]:$/.test(parts[0]!)) startIdx = 1;\r\n\r\n if (parts[0] === 'crates' && parts.length >= 2) {\r\n return `crates/${parts[1]}`;\r\n }\r\n\r\n if (parts[startIdx] === 'src' || parts[startIdx] === 'lib' || parts[startIdx] === 'app') {\r\n return parts.slice(startIdx, startIdx + 2).join('/');\r\n }\r\n\r\n return parts.slice(startIdx, startIdx + 2).join('/');\r\n}","// 「我们正运行在 DSH 哪一代」的唯一真源。\n//\n// 本模块存在的理由:代际窗口曾被写死四处 —— `src/host/compat/HostVersionDetector.ts`、\n// `src/client/compat/ClientVersionDetector.ts`、`tests/unit/manifest.test.ts`(DSH_GENERATIONS)\n// 与 `package.json`(engines.dsh)—— 各自持有一份阈值副本。它们之间没有任何联动,于是\n// 在任一处放宽窗口都会静默地让另外三处失配(宿主半与客户端半可能对同一个宿主得出不同\n// 分类,而两处的 `isGen2()` 式分支会照此行事)。\n//\n// 窗口由清单所声明的 DSH 发布线(`engines.dsh`)推导;`tests/unit/dshGeneration.test.ts`\n// 会在清单与本模块漂移时失败。\n//\n// ## cycle-110 事实更正:`engines.dsh` **不是**纯声明\n//\n// 本文件此前长期写着「兼容性只是声明式的(`dsh-package-manifest/README.md:93`:安装器不强制\n// `engines.dsh`),因此本模块只做分类,从不拦截加载」。**该口径已被证伪**,正确事实是:\n//\n// 1. 宿主 `@deepseek-ai/dsh-app-boot` 的 `unmetRequirements()` **会读** `engines.dsh`,\n// 安装与 profile 启动对不满足范围的包**直接拒绝**;\n// 2. **只要包声明了 `engines.dsh`,`peerDependencies` 就被完全忽略**;peer 规则只在\n// `engines.dsh` 缺席时作为回退生效。源码注释逐字:\n// \"An own `engines.dsh` range decides alone and peerDependencies are ignored\"。\n// 3. 同一条规则也被市场侧复刻(`dshmarket/lib/verify.js:217-235`,由 Lead 提供行号,\n// 本轮未独立核验——它在 asar 内,本机无法直接读取该文件)。\n//\n// 公开文档锚点(本机知识库 `dsh-plugin-dev-kb/kb`,官方 master 投影,commit 687f282e,\n// 抓取于 2026-10-21):\n// - `site/develop/basic/publish.md:69`:「安装和 profile 启动会**拒绝**当前运行的 DSH\n// 不满足该范围的组合包;**没有** `engines.dsh` 时,改为检查其 `@deepseek-ai/dsh*`\n// peer 依赖范围」;\n// - `site/guide/plugin-market.md:138,140`:`engines.dsh` 缺省表示任意版本,DSH 会在安装\n// 时再次检查;\n// - `extra/upgrade-guide/v0.2.1-alpha.3/plugin-engines-dsh/guide.zh.md`:本条变更的\n// 迁移指南原文(\"包自身的 `engines.dsh` 范围单独决定结果,并忽略这些 peer\")。\n//\n// ## 这对本模块的含义\n//\n// 分类器仍然**从不**用版本去拦加载(这是运行期行为,且 gen1 必须仍被如实识别)。真正被\n// 改变的是一条措辞与一处上界:`engines.dsh` 现在是一道**强制门**,所以它的上界必须与\n// peer 一样精确到「排除下一代的预发布」。`>=0.2.0-rc.2 <0.4.0` 在\n// `{ includePrerelease: true }` 下会放行 `0.4.0-rc.1`(semver 实测,见 §上界说明),\n// 于是 gen4 的预发布会被当成受支持宿主。\n//\n// ⚠️ cycle-141 第三轮更正:当时的结论「`<0.4.0-0` 精确排除后者**且仍覆盖\n// `0.3.0-rc.2`**」**只对 `includePrerelease: true` 成立**。在 semver **默认模式**下,\n// 单段 `<0.4.0-0` 因预发布元组规则**拒绝** `0.3.0-rc.2`(`semver@7.8.5` 实测 false)。\n// 现在改为显式 `||` 分支——详见 {@link DSH_SUPPORTED_RANGE} 的双模式对照表。\n\n/** 本插件建模的 DSH 宿主代际。`unknown` 是一等结果,不是错误。 */\nexport type DshGeneration = 'gen1' | 'gen2' | 'gen3' | 'unknown';\n\n/** 真正对应已发布 DSH 线的代际。 */\nexport const DSH_GENERATIONS = ['gen1', 'gen2', 'gen3'] as const;\n\nexport type DshKnownGeneration = (typeof DSH_GENERATIONS)[number];\n\nexport interface DshVersionInfo {\n raw: string;\n major: number;\n minor: number;\n patch: number;\n prerelease: string | null;\n generation: DshGeneration;\n}\n\n/**\n * 每代一个代表版本。这既是 `engines.dsh` 必须覆盖的清单,也是宿主/客户端两侧检测器\n * 必须得出相同分类的清单。\n */\nexport const DSH_GENERATION_PROBES: Record<DshKnownGeneration, string> = {\n gen1: '0.1.7-rc.2',\n gen2: '0.2.0-rc.2',\n gen3: '0.3.0-rc.2',\n};\n\n/**\n * **被承诺支持**的代际:即 {@link DSH_SUPPORTED_RANGE} 实际覆盖的代际。\n *\n * cycle-92 裁定(objective #3):宿主下限由 0.1.7-rc.2 **抬高到 0.2.0-rc.2**。\n * 依据是上游把 dsh-v0.2.0 定为当前公开的插件基线,且本项目 peer 门实测只在\n * 0.2.0-rc.2 上完成过真机验证(J0 旅程)。\n *\n * 注意「被建模」与「被承诺」是两件事,见 {@link DSH_GENERATIONS} 与\n * {@link DSH_LEGACY_GENERATIONS} 的分工。\n */\nexport const DSH_PROMISED_GENERATIONS = ['gen2', 'gen3'] as const;\n\n/**\n * **被容忍但不承诺**的代际:适配器仍然能正确分类并按保守能力集运行,但不在\n * `engines.dsh` / `peerDependencies` 的承诺窗口内,也不作为验收对象。\n *\n * 保留这一档而不是删除 gen1 代码,是因为分类器必须能如实回答「我正跑在一个\n * 0.1.x 宿主上」——把一个可识别的宿主报告成 `unknown` 会让能力探针失去\n * 保守基线的依据。删掉它反而会制造出「本插件以为自己在 gen2」这类静默降级。\n */\nexport const DSH_LEGACY_GENERATIONS = ['gen1'] as const;\n\n/**\n * 受支持的宿主窗口,在 `package.json` 中**逐字**声明为 `engines.dsh`。\n *\n * 下限 `>=0.2.0-rc.2` 是 cycle-92 的用户硬要求,**本轮不变**。\n *\n * ## 为什么必须是显式的 `||` 分支(cycle-141 第三轮修正)\n *\n * ### 缺陷:单段范围在 semver **默认模式**下静默排除 0.3 的全部预发布\n *\n * node-semver 有一条易被忽略的规则(`Range.prototype.test` → `testSet`):\n * **带预发布标签的版本只有在范围里存在一个「同 major.minor.patch 元组且自身带预发布\n * 标签」的比较符时才可能匹配**。单段范围 `>=0.2.0-rc.2 <0.4.0-0` 的两个比较符分别落在\n * 0.2.0 与 0.4.0 元组上,**没有任何一个在 0.3.0 上** ⇒ `0.3.0-rc.1`/`0.3.0-rc.2`\n * 在默认模式下**被静默排除**(`semver@7.8.5` 实测 `false`)。\n *\n * 换句话说:受支持的 gen3 宿主装不上本插件,而**没有任何测试变红**——\n * 因为本仓的守卫用的是一个自制的 `compareWithinRange`,它把预发布当普通分量比较,\n * 与真实 semver 语义不一致(`tests/unit/dshGeneration.test.ts`)。守卫测的是它自己,\n * 不是安装门。这正是「fixture 不得自造外部契约」这条教训的现场。\n *\n * ### 修法:把 0.3 元组显式写进范围(市场方也这么建议)\n *\n * `awesome-dsh-plugin/contributing.md` 对同一个坑给出同一条建议:\n * > 使用显式的 `||` 分支,在匹配的元组上带上预发布标签\n *\n * ### 实测对照(`semver@7.8.5`,默认模式 / `includePrerelease:true` 双跑)\n *\n * | 版本 | 单段 `<0.4.0-0` 默认 | 单段 `<0.4.0-0` incl | **`\\|\\|` 分支 默认** | **`\\|\\|` 分支 incl** |\n * |---|---|---|---|---|\n * | `0.2.0-rc.2` / `0.2.1` | PASS | PASS | **PASS** | **PASS** |\n * | `0.3.0-rc.1` / `0.3.0-rc.2` | **REF ← 缺陷** | PASS | **PASS** | **PASS** |\n * | `0.3.5` | PASS | PASS | **PASS** | **PASS** |\n * | `0.4.0-0` / `0.4.0-rc.1` / `0.4.0` | REF | REF | **REF** | **REF** |\n * | `0.1.7-rc.1`(遗留 gen1) | REF | REF | **REF** | **REF** |\n *\n * `\\|\\|` 分支形式在**两种模式下结果一致**,因此不依赖「宿主一定传了\n * `includePrerelease: true`」这一未经验证的假设(cycle-110 曾用「插件在\n * `0.2.1-rc.3` 上能跑」反推该旗标;那是间接证据,且对 `link:` 安装是否跳过引擎门\n * 没有独立核验)。把正确性建立在范围自身而不是宿主选项上,才是可移植的。\n *\n * **下限与上界必须成对阅读**:只改上界不改下限 = 仍在承诺 gen1;只改下限不改上界 =\n * 把 gen4 预发布放进门内。`tests/unit/manifest.test.ts` 与\n * `tests/unit/dshGeneration.test.ts` 对两侧各有一条探针。\n */\nexport const DSH_SUPPORTED_RANGE = '>=0.2.0-rc.2 <0.3.0 || >=0.3.0-0 <0.4.0-0';\n\n/**\n * {@link DSH_SUPPORTED_RANGE} **最近的**排除探针:gen4 线的第一格。\n *\n * 取 `0.4.0-rc.1` 而不是 `0.4.0` 是刻意的:`0.4.0` 在旧上界 `<0.4.0` 下**本来就**被\n * 拒绝,所以拿它当探针无法发现「上界太宽」这一类缺陷——它只能发现上界被写到 0.4.0 之上。\n * 真正的缺口是**预发布**,探针必须落在预发布上才对那条缺陷敏感。\n *\n * cycle-110 起 `engines.dsh` 是强制门(见文件头),所以这条探针保护的不再只是「适配器\n * 模型边界」,而是**安装门会不会把 gen4 预发布放进来**。\n */\nexport const DSH_UNSUPPORTED_PROBE = '0.4.0-rc.1';\n\n/**\n * 全部排除探针(含最近的 `0.4.0-rc.1`)。用例应遍历本数组,而不是只信\n * {@link DSH_UNSUPPORTED_PROBE} 一个点:一个探针只能证明「这一格被排除」。\n *\n * `0.4.0` 作为第二个探针保留下来,覆盖「上界被写成 `<=0.3.x` 之外的其它形态」这类改动。\n */\nexport const DSH_UNSUPPORTED_PROBES = ['0.4.0-rc.1', '0.4.0', '1.0.0'] as const;\n\nconst VERSION_PATTERN = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.]+))?/;\n\nexport function isDshGeneration(value: unknown): value is DshGeneration {\n return value === 'gen1' || value === 'gen2' || value === 'gen3' || value === 'unknown';\n}\n\n/**\n * 把 semver 的 major/minor 对映射到代际。\n *\n * gen1 = 0.1.x,gen2 = 0.2.x,gen3 = 0.3.x 及其之后的所有线(0.4.x、1.x……)。后续线\n * 刻意落进 gen3:gen3 被建模为 gen2 的前向兼容超集,于是尚未发布的 0.4.x 宿主会拿到\n * 最新适配器,而不是退化成 `unknown`;只有低于受支持下界的宿主才保持未分类。\n *\n * gen1 自 cycle-92 起是 {@link DSH_LEGACY_GENERATIONS}:**仍被分类**(分类器不撒谎),\n * 但已不在 {@link DSH_SUPPORTED_RANGE} 内,故不再被承诺。\n *\n * ## cycle-110:代际**不是**兼容性判据\n *\n * 本机运行宿主实测为 `0.2.1-rc.3`,按本函数归 **gen2**(minor === 2)。但 0.2 线内部\n * 已经发生过破坏性变更:`0.2.1-alpha.3` 把 `engines.dsh` 提升为唯一判据(见文件头)、\n * 工具呈现模式拒绝 `both`(`extra/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode`)。\n * 也就是说**同一代际内可以有破坏性变更**,所以适配器绝不能按 `generation` 分支后就假定\n * API 面固定——不确定项必须落成「探测 + 降级」(`ctx.get(key, false)` /\n * `ctx.inject` / 工具在场性),而不是按代开关。\n *\n * 附带澄清一个容易混淆的说法:把 `0.1.7 → 0.2.0 → 0.2.1` 称为「三代」是**发布线序号**,\n * 与本模型的 `DshGeneration` 不是一回事。混用会让「gen3 已真机验证」这样的错误结论出现\n * ——本机验证过的最新一代是 **gen2**,gen3(0.3.x)至今**未发布、未验证**。\n */\nexport function generationOfVersion(major: number, minor: number): DshGeneration {\n if (major >= 1) return 'gen3';\n if (major !== 0) return 'unknown';\n if (minor >= 3) return 'gen3';\n if (minor === 2) return 'gen2';\n if (minor === 1) return 'gen1';\n return 'unknown'; // 0.0.x 早于本插件建模的窗口\n}\n\n/** 解析原始版本串(可带前导 `v`,预发布段可选)。 */\nexport function parseDshVersion(raw: string): DshVersionInfo {\n const match = raw.match(VERSION_PATTERN);\n if (!match) {\n return { raw, major: 0, minor: 0, patch: 0, prerelease: null, generation: 'unknown' };\n }\n const major = Number.parseInt(match[1]!, 10);\n const minor = Number.parseInt(match[2]!, 10);\n const patch = Number.parseInt(match[3]!, 10);\n return {\n raw,\n major,\n minor,\n patch,\n prerelease: match[4] ?? null,\n generation: generationOfVersion(major, minor),\n };\n}\n\n/**\n * 所有检测器都失败时的版本。共享此函数是为了让宿主半与客户端半对「我判断不出来」\n * 报告完全相同的形状。\n */\nexport function unknownDshVersion(raw = 'unknown'): DshVersionInfo {\n return { raw, major: 0, minor: 0, patch: 0, prerelease: null, generation: 'unknown' };\n}\n\n/** 该代的一个代表版本,供「无版本串、只能按 API 形状判定」的检测使用。 */\nexport function probeReleaseFor(generation: DshGeneration): string | null {\n return generation === 'unknown' ? null : DSH_GENERATION_PROBES[generation];\n}\n","import type { Context } from '@deepseek-ai/cordis';\nimport { scoped } from '../../shared/Logger.ts';\nimport {\n parseDshVersion,\n probeReleaseFor,\n unknownDshVersion,\n type DshGeneration,\n type DshVersionInfo,\n} from '../../shared/dshGeneration.ts';\n\nconst log = scoped('host:compat');\n\n/** @deprecated 仅为既有调用点保留的别名;模型本体在 `src/shared/dshGeneration.ts`。 */\nexport type HostGeneration = DshGeneration;\nexport type HostVersionInfo = DshVersionInfo;\n\n/**\n * 候选一:从 cordis context 上读版本串。\n *\n * **本机实测结论(cycle-83,asar 树全量 grep)**:`ctx.hostVersion`、`ctx.version`、\n * `ctx.app?.version`、`ctx.runtime?.version` 四个键在 `@deepseek-ai/*` 里**零命中**——\n * DSH 从不向插件 context 暴露版本。这一路是「如果将来有,就优先用」的前向兼容探测,\n * 不是当前可依赖的路径。\n */\nfunction detectFromContext(ctx: Context): string | null {\n const candidates = [\n () => ctx.hostVersion,\n () => ctx.version,\n () => ctx.app?.version,\n () => ctx.runtime?.version,\n ];\n for (const fn of candidates) {\n try {\n const v = fn();\n if (typeof v === 'string' && v.length > 0) return v;\n } catch { /* 忽略:探针失败等同于未命中 */ }\n }\n return null;\n}\n\n/**\n * 候选二:环境变量。\n *\n * **本机实测结论**:`DSH_HOST_VERSION` / `DSH_VERSION` / `CODEARTS_HOST_VERSION`\n * 三个名字在全树零命中。DSH 的保留 env 只有 `DSH_HOME`、`DSH_SHELL`、`DSH_SESSION_ID`、\n * `DSH_PROFILE`、`DSH_PROFILE_DIR`(见 `@deepseek-ai/dsh-shell-env` 的 README 与\n * `lib/index.js` 的 `collect()`),且它们只注入到 **shell 工具的子进程环境**里,\n * 不进入宿主插件进程。此路同样只是前向兼容。\n */\nfunction detectFromEnv(): string | null {\n const envKeys = ['DSH_HOST_VERSION', 'DSH_VERSION', 'CODEARTS_HOST_VERSION'];\n for (const key of envKeys) {\n const v = process.env[key];\n if (v && v.length > 0) return v;\n }\n return null;\n}\n\n/**\n * 最后一道:按上游工具面反推代。\n *\n * `codegraph_incremental` / `codegraph_bootstrap` 只在 0.2 线出现过,它们在场即可给宿主\n * 定代。返回的是**代表性发行版**(见 `DSH_GENERATION_PROBES`)而不是实测版本号——\n * 目的只是让下游拿到正确的 `generation`,不是伪造一个精确版本。\n *\n * 诚实披露:本机 `dsh-codegraph@0.1.3` 只贡献 `mcp__codegraph__codegraph_explore` 一个工具,\n * 上述 9 个工具名**一个都不存在**(Windows 上 `codegraph: false` 是准确判定)。因此这一路\n * 在真机上同样命中不到,最终会落到 `unknown` + 运行时能力探针。\n */\nfunction detectFromToolNames(ctx: Context): string | null {\n try {\n const tools = ctx.tools;\n const hasGen2OnlyTool = tools.get('codegraph_incremental') ?? tools.get('codegraph_bootstrap');\n if (hasGen2OnlyTool) return probeReleaseFor('gen2');\n const hasGen1Tool = tools.get('codegraph_status') ?? tools.get('codegraph_graph');\n if (hasGen1Tool) return probeReleaseFor('gen1');\n } catch { /* 忽略:宿主未提供 tools 服务 */ }\n return null;\n}\n\n/**\n * 解析当前运行的 DSH 代。\n *\n * 每一路都是启发式(DSH 在插件 context 上不提供稳定的版本 API,且安装器**从不强制**\n * `engines.dsh`——见 `dsh-package-manifest/README.md:93` 的「Compatibility is declarative」),\n * 所以全部未命中时降级为 `unknown`:适配层用保守能力集 + 运行时探针作答,而不是崩溃,\n * 也不是拒绝加载。\n */\nexport function detectHostVersion(ctx: Context): HostVersionInfo {\n const sources = [\n () => detectFromContext(ctx),\n () => detectFromEnv(),\n () => detectFromToolNames(ctx),\n ];\n for (const fn of sources) {\n try {\n const raw = fn();\n if (raw) {\n const info = parseDshVersion(raw);\n log.info('host version detected', { raw, generation: info.generation });\n return info;\n }\n } catch { /* 忽略:继续尝试下一路 */ }\n }\n log.info('host version unknown, using conservative flags');\n return unknownDshVersion();\n}\n","import type { HostVersionInfo } from './HostVersionDetector.ts';\nimport { scoped } from '../../shared/Logger.ts';\nimport type { DshGeneration } from '../../shared/dshGeneration.ts';\n\nconst log = scoped('host:compat');\n\n/**\n * 宿主能力矩阵。每个条目都必须描述**宿主提供的东西**:标志门控的是「宿主缺少该能力时\n * 会降级」的行为。\n *\n * 刻意缺席 `treeSitterWasm`:解析在本插件自己的宿主进程里、用它自己的依赖\n * (`tree-sitter-wasm`,经 `getWasmPath` 解析)运行,宿主无法授予或扣留它;该能力曾\n * 在每一代都声明为 `true` 且无人读取。Round 7 将其删除,而不是留下一个宣称了宿主能力、\n * 插件却从不消费的标志(见 `docs/需求分析.md` §31.6 / FR-33)。\n *\n * cycle-83 删除的幻影位:`spotlight` 与 `remoteWebUi`。两者对应的服务键\n * (`ctx.get('spotlight')` / `ctx.get('remoteWebUI')`)在 gen1/gen2 两套 DSH 树与本机\n * 全部已装第三方插件中**零 `provide` 命中**,其客户端集成恒早退。`desktopPlatform`\n * 予以保留但**语义已改**:它不再探测 `ctx.get('desktopProfiles')`(该键不是 cordis\n * 服务),而由客户端按 DSH 官方的 `<html>` DOM 标记判定(见\n * `src/client/compat/PlatformProbe.ts`)。\n */\nexport interface FeatureFlags {\n incrementalBootstrap: boolean;\n workspaceRegistry: boolean;\n sessionsApi: boolean;\n betterSidebar: boolean;\n mcpServer: boolean;\n rateLimiting: boolean;\n healthScore: boolean;\n rulesCheck: boolean;\n multiWorkspace: boolean;\n aiAssist: boolean;\n crossRepo: boolean;\n /** 宿主为桌面(Electron)外壳。由客户端 DOM 判定,宿主侧仅作诊断转述。 */\n desktopPlatform: boolean;\n}\n\nconst GEN1_FLAGS: FeatureFlags = {\n incrementalBootstrap: false,\n workspaceRegistry: false,\n sessionsApi: true,\n betterSidebar: false,\n mcpServer: true,\n rateLimiting: true,\n healthScore: true,\n rulesCheck: true,\n multiWorkspace: false,\n aiAssist: false,\n crossRepo: false,\n desktopPlatform: false,\n};\n\nconst GEN2_FLAGS: FeatureFlags = {\n incrementalBootstrap: true,\n workspaceRegistry: true,\n sessionsApi: true,\n betterSidebar: true,\n mcpServer: true,\n rateLimiting: true,\n healthScore: true,\n rulesCheck: true,\n multiWorkspace: true,\n aiAssist: false,\n crossRepo: false,\n desktopPlatform: true,\n};\n\nconst GEN3_FLAGS: FeatureFlags = {\n ...GEN2_FLAGS,\n // gen3(v0.3.0+)保留位 —— 待 v0.3.0 确认这些特性后再打开。\n // 刻意与 gen2 相同:未发布的线不得被授予无人验证的能力。\n // 真实的 gen3 宿主通过下方运行时探针取得它们。\n aiAssist: false,\n crossRepo: false,\n desktopPlatform: true,\n};\n\n/** 「版本无法判定」时的保守基线。 */\nconst UNKNOWN_FLAGS: FeatureFlags = {\n ...GEN2_FLAGS,\n incrementalBootstrap: false,\n};\n\n/**\n * 代际 → 能力矩阵,键覆盖模型能报告的每一代。\n * 导出是为了让穷尽性被断言而非假设(`tests/unit/dshGeneration.test.ts`)。\n */\nexport const FEATURE_FLAGS_BY_GENERATION: Record<DshGeneration, FeatureFlags> = {\n gen1: GEN1_FLAGS,\n gen2: GEN2_FLAGS,\n gen3: GEN3_FLAGS,\n unknown: UNKNOWN_FLAGS,\n};\n\n/**\n * 运行时能力探针。`true` = 在该宿主上观测到该能力,\n * `false`/`null`/`undefined` = 没有可用证据(缺席不等于不存在)。\n */\nexport type CapabilityProbe = (capability: keyof FeatureFlags) => boolean | null | undefined;\n\n/** 探针在代际默认值之上提升的能力,供日志/遥测使用。 */\nexport interface FeatureFlagResolution {\n flags: FeatureFlags;\n source: 'generation' | 'generation+probe';\n probedUpgrades: (keyof FeatureFlags)[];\n}\n\n/**\n * 解析某宿主的能力集。\n *\n * 只用代际默认值会错估两种本插件必须活下来的情形:比模型更新的宿主(`unknown`/未来线,\n * 但它确实有某能力)与比自身所属线更旧的宿主(0.2 的构建但没装上游 codegraph 插件)。\n * 探针能同时修正两者,但**只做加法**:探针可以把能力打开,永不关闭。探测是启发式的,\n * 假阴性会静默移除可用行为,而假阳性只是恢复版本模型在邻近代已经授予过的东西。\n */\nexport function resolveFeatureFlags(info: HostVersionInfo, probe?: CapabilityProbe): FeatureFlagResolution {\n const base = FEATURE_FLAGS_BY_GENERATION[info.generation];\n if (!probe) {\n // 返回**副本**,不返回 `FEATURE_FLAGS_BY_GENERATION` 里的那一份(cycle-110)。\n // 无探针曾直接 `return { flags: base }`,于是调用方手里是**代际默认表的同一个对象**:\n // 任何一处对 flags 的写入都会永久改坏该代的默认值(同一进程内后续所有解析都受污染),\n // 而\"能力快照可以被重算\"这一性质也随之失效。副本让「解析」保持纯:\n // 每次调用得到互相独立的对象,随时可以用最新的探针再算一次。\n return { flags: { ...base }, source: 'generation', probedUpgrades: [] };\n }\n\n const flags: FeatureFlags = { ...base };\n const probedUpgrades: (keyof FeatureFlags)[] = [];\n for (const capability of Object.keys(base) as (keyof FeatureFlags)[]) {\n if (flags[capability]) continue; // 已授予的能力永不被探针关掉\n let observed: boolean | null | undefined;\n try {\n observed = probe(capability);\n } catch {\n observed = undefined;\n }\n if (observed === true) {\n flags[capability] = true;\n probedUpgrades.push(capability);\n }\n }\n return {\n flags,\n source: probedUpgrades.length > 0 ? 'generation+probe' : 'generation',\n probedUpgrades,\n };\n}\n\nexport function createFeatureFlags(info: HostVersionInfo, probe?: CapabilityProbe): FeatureFlags {\n const { flags, source, probedUpgrades } = resolveFeatureFlags(info, probe);\n log.info('feature flags resolved', { generation: info.generation, source, probedUpgrades, flags });\n return flags;\n}\n","import type { Context } from '@deepseek-ai/cordis';\nimport { scoped } from '../../shared/Logger.ts';\nimport type { CapabilityProbe, FeatureFlags } from './FeatureFlags.ts';\n\nconst log = scoped('host:compat:probe');\n\n/**\n * 由 Cordis **服务**支撑的能力:服务在场即证据。这些服务名都是本插件已在别处探测过的,\n * 因此探针不引入任何新的 DSH API 假设:\n * - `workspaceRegistry` —— `src/host/prerequisites.ts` 的 `ctx.get('workspaceRegistry')`\n * - `multiWorkspace` —— 同一个服务;多工作区 API 就是该服务\n * - `sessionsApi` —— 本插件自己的 `inject` 列表必需项(`src/index.ts`)\n *\n * cycle-83 删除了 `remoteWebUi: ['remoteWebUI']` 这一条:`remoteWebUI` 在任何 DSH 代都\n * 不存在(幻影),探测它只会让探针永远返回「无证据」。\n */\nconst SERVICE_EVIDENCE: Partial<Record<keyof FeatureFlags, readonly string[]>> = {\n workspaceRegistry: ['workspaceRegistry'],\n multiWorkspace: ['workspaceRegistry'],\n sessionsApi: ['sessions'],\n};\n\n/**\n * 由**上游工具**支撑的能力:工具已注册即证据。\n * `codegraph_incremental`/`codegraph_bootstrap` 是本插件 `ApiCompatLayer` 已映射的 0.2 线\n * 工具,探测它们不可能凭空造出一个不存在的服务。\n *\n * 注意这一条通常不会触发:`detectHostVersion` 正是用这些工具名给宿主定代的,所以拥有\n * 它们的宿主已被归为 gen2、标志本就是开的。保留它作为**安全网**,用于未来出现版本串与\n * 工具面不一致的线。\n */\nconst TOOL_EVIDENCE: Partial<Record<keyof FeatureFlags, readonly string[]>> = {\n incrementalBootstrap: ['codegraph_incremental', 'codegraph_bootstrap'],\n};\n\n/**\n * 在活跃宿主之上构造一个 {@link CapabilityProbe}。\n *\n * **只报告观测到的 `true`**:服务或工具缺席意味着「无证据」,绝不等于「不存在」,\n * 此时由代际默认值裁决。这使探针在任何宿主上都安全可跑 —— 包括当前这个 gen2 宿主\n * (它什么新东西都确认不了)—— 同时给「模型未覆盖的未来宿主」或「被裁剪过的宿主」\n * 一条自行声明能力的通道。\n */\nexport function createHostCapabilityProbe(ctx: Context): CapabilityProbe {\n return (capability: keyof FeatureFlags): boolean | undefined => {\n const services = SERVICE_EVIDENCE[capability];\n if (services) {\n try {\n for (const service of services) {\n // `strict = false`:本函数是「服务是否存在」探针,不是「提供者 fiber 是否活跃」\n // 探针。cordis 的 `ctx.get(name, strict = true)` 只返回提供者 fiber 当前\n // `state === 2`(active)的实现(cordis/lib/index.js:763-772),而本探针可能在\n // 宿主插件尚未转 active 时被调用 —— 用默认 strict 会把「稍后可用」误判成「不存在」,\n // 进而让能力位永久回落到代际默认值。(cycle-83,同 ClientCompatLayer.hasService 的修复。)\n const resolved = ctx.get(service, false);\n if (resolved !== null && resolved !== undefined) {\n log.info('capability probe: service present', { capability, service });\n return true;\n }\n }\n } catch { /* 服务注册表不可用 —— 视为无证据 */ }\n return undefined;\n }\n\n const toolNames = TOOL_EVIDENCE[capability];\n if (toolNames) {\n try {\n for (const toolName of toolNames) {\n if (ctx.tools.get(toolName)) {\n log.info('capability probe: tool present', { capability, tool: toolName });\n return true;\n }\n }\n } catch { /* 工具注册表不可用 —— 视为无证据 */ }\n return undefined;\n }\n\n return undefined; // 该能力没有建模的证据源\n };\n}\n","import type { Context } from '@deepseek-ai/cordis';\r\nimport type { HostVersionInfo } from './HostVersionDetector.ts';\r\nimport { scoped } from '../../shared/Logger.ts';\r\nimport type { DshGeneration } from '../../shared/dshGeneration.ts';\r\n\r\nconst log = scoped('host:compat');\r\n\r\n/**\r\n * 15 个工具键 → 实际工具名的映射表。\r\n *\r\n * 四个「gen2 才可能提供」的键(bootstrap/incremental/aiAssist/semanticSearch)允许为\r\n * `null`:`null` 表示**该代没有对应工具**,而不是「名字待查」。\r\n */\r\nexport interface ToolNameMapping {\r\n codegraphStatus: string;\r\n codegraphGraph: string;\r\n codegraphQuery: string;\r\n codegraphFiles: string;\r\n codegraphImpact: string;\r\n codegraphInit: string;\r\n codegraphSync: string;\r\n codegraphCallers: string;\r\n codegraphCallees: string;\r\n codegraphBootstrap: string | null;\r\n codegraphIncremental: string | null;\r\n codegraphAiAssist: string | null;\r\n codegraphSemanticSearch: string | null;\r\n lensAnalyze: string;\r\n lensImpact: string;\r\n}\r\n\r\n/** gen1(0.1.x 线)工具面:无 bootstrap / incremental / aiAssist / semanticSearch 四个键。 */\r\nconst GEN1_MAPPING: ToolNameMapping = {\r\n codegraphStatus: 'codegraph_status',\r\n codegraphGraph: 'codegraph_graph',\r\n codegraphQuery: 'codegraph_query',\r\n codegraphFiles: 'codegraph_files',\r\n codegraphImpact: 'codegraph_impact',\r\n codegraphInit: 'codegraph_init',\r\n codegraphSync: 'codegraph_sync',\r\n codegraphCallers: 'codegraph_callers',\r\n codegraphCallees: 'codegraph_callees',\r\n codegraphBootstrap: null,\r\n codegraphIncremental: null,\r\n codegraphAiAssist: null,\r\n codegraphSemanticSearch: null,\r\n lensAnalyze: 'lens_analyze',\r\n lensImpact: 'lens_impact',\r\n};\r\n\r\n/** gen2(0.2.x 线):在 gen1 之上增加 bootstrap 与 incremental 两个工具名。 */\r\nconst GEN2_MAPPING: ToolNameMapping = {\r\n ...GEN1_MAPPING,\r\n codegraphBootstrap: 'codegraph_bootstrap',\r\n codegraphIncremental: 'codegraph_incremental',\r\n};\r\n\r\n/**\r\n * gen3(0.3.x 线,以及被建模为前向兼容超集的 0.4+/1.x):**只继承 gen2 已确证的键**。\r\n * aiAssist / semanticSearch 在本机任何 DSH 版本中都没有对应工具(见\r\n * `docs/research/dsh-upgrade-research-report.md` 对上游 `dsh-codegraph` 的核验:\r\n * 它只贡献 `mcp__codegraph__codegraph_explore`),因此不得凭「更新的版本应该更强」\r\n * 的推测把它们打开——未发布线不授予无人验证的能力。\r\n */\r\nconst GEN3_MAPPING: ToolNameMapping = {\r\n ...GEN2_MAPPING,\r\n codegraphAiAssist: null,\r\n codegraphSemanticSearch: null,\r\n};\r\n\r\n/**\r\n * 未知代(版本串缺失且服务面探针也未命中):退回 gen1 的保守面。\r\n *\r\n * 注意这与 `FeatureFlags` 的 `UNKNOWN_FLAGS` 策略**刻意不同**:能力标志走「保守扣留、\r\n * 探针只加不减」,而工具名映射走「退到最小可用集、由 `getAvailableToolName` 的\r\n * `FALLBACK_CHAINS` 在运行时逐个回退」。两者都不假设未来版本拥有更多能力。\r\n */\r\nconst UNKNOWN_MAPPING: ToolNameMapping = {\r\n ...GEN1_MAPPING,\r\n codegraphBootstrap: null,\r\n codegraphIncremental: null,\r\n codegraphAiAssist: null,\r\n codegraphSemanticSearch: null,\r\n};\r\n\r\n/**\r\n * 代 → 工具名矩阵,以模型能报告的**每一代**为键。\r\n * 特意导出,使穷尽性是被断言的而不是被假设的(见 `tests/unit/dshGeneration.test.ts`);\r\n * 将来在 `dshGeneration.ts` 里新增一代,会在此处类型检查失败,而不是静默落到默认值。\r\n */\r\nexport const TOOL_MAPPING_BY_GENERATION: Record<DshGeneration, ToolNameMapping> = {\r\n gen1: GEN1_MAPPING,\r\n gen2: GEN2_MAPPING,\r\n gen3: GEN3_MAPPING,\r\n unknown: UNKNOWN_MAPPING,\r\n};\r\n\r\n/** 按代取工具名映射。 */\r\nexport function createToolNameMapping(info: HostVersionInfo): ToolNameMapping {\r\n return TOOL_MAPPING_BY_GENERATION[info.generation];\r\n}\r\n\r\n/**\r\n * 宿主半的多代适配层:把「逻辑工具键」翻译成当前代真实存在的工具名,并在主名缺席时\r\n * 沿 {@link FALLBACK_CHAINS} 逐级回退。\r\n */\r\nexport interface ApiCompatLayer {\r\n versionInfo: HostVersionInfo;\r\n toolNames: ToolNameMapping;\r\n /** 该键在当前代的**声明**名字;`null` 表示这一代本就没有这个工具。 */\r\n resolveToolName(key: keyof ToolNameMapping): string | null;\r\n /** 该键的声明名字此刻是否真的注册在 `ctx.tools` 上。 */\r\n isToolAvailable(ctx: Context, key: keyof ToolNameMapping): boolean;\r\n /** 主名不可用时沿回退链找到的第一个可用工具名。 */\r\n getAvailableToolName(ctx: Context, key: keyof ToolNameMapping): string | null;\r\n /** 反查:给定一个真实工具名,返回同一逻辑键下可用的名字。 */\r\n getAvailableToolNameFor(ctx: Context, actualName: string): string | null;\r\n}\r\n\r\nexport function createApiCompatLayer(info: HostVersionInfo): ApiCompatLayer {\r\n const toolNames = createToolNameMapping(info);\r\n // 反向索引:真实工具名 → 逻辑键。用于 `getAvailableToolNameFor` 的反查路径。\r\n const keyByToolName = new Map<string, keyof ToolNameMapping>();\r\n for (const key of Object.keys(toolNames) as (keyof ToolNameMapping)[]) {\r\n const actual = toolNames[key];\r\n if (actual) keyByToolName.set(actual, key);\r\n }\r\n\r\n const resolveToolName = (key: keyof ToolNameMapping): string | null => {\r\n return toolNames[key];\r\n };\r\n\r\n const isToolAvailable = (ctx: Context, key: keyof ToolNameMapping): boolean => {\r\n const name = resolveToolName(key);\r\n if (!name) return false;\r\n try {\r\n return !!ctx.tools.get(name);\r\n } catch {\r\n // 宿主未提供 `tools` 服务(极小 profile)时按不可用处理,而不是让整个插件崩掉。\r\n return false;\r\n }\r\n };\r\n\r\n const getAvailableToolName = (ctx: Context, key: keyof ToolNameMapping): string | null => {\r\n const primary = resolveToolName(key);\r\n if (primary && isToolAvailable(ctx, key)) return primary;\r\n const fallbacks = FALLBACK_CHAINS[key] ?? [];\r\n for (const fb of fallbacks) {\r\n const fbName = resolveToolName(fb);\r\n if (fbName) {\r\n try {\r\n if (ctx.tools.get(fbName)) {\r\n log.info('tool name fallback', { key, primary, fallback: fbName });\r\n return fbName;\r\n }\r\n } catch { /* 忽略:探针失败等同于不可用 */ }\r\n }\r\n }\r\n return null;\r\n };\r\n\r\n const getAvailableToolNameFor = (ctx: Context, actualName: string): string | null => {\r\n try {\r\n if (ctx.tools.get(actualName)) return actualName;\r\n } catch { /* 忽略 */ }\r\n const key = keyByToolName.get(actualName);\r\n if (!key) return null;\r\n return getAvailableToolName(ctx, key);\r\n };\r\n\r\n return { versionInfo: info, toolNames, resolveToolName, isToolAvailable, getAvailableToolName, getAvailableToolNameFor };\r\n}\r\n\r\n/**\r\n * 回退链:主名缺席时按顺序尝试的**同一逻辑能力**的其它键。\r\n *\r\n * 例:`codegraphIncremental` 在 gen1 上为 `null`,于是退到 `codegraphSync`,\r\n * 再退到 `codegraphInit`——三者都是「把图更新到最新」的不同代工具名。\r\n * 链条刻意保持短(≤2 跳),因为每多一跳就多一次「语义可能已变」的风险。\r\n */\r\nconst FALLBACK_CHAINS: Partial<Record<keyof ToolNameMapping, (keyof ToolNameMapping)[]>> = {\r\n codegraphStatus: ['codegraphGraph', 'codegraphQuery'],\r\n codegraphGraph: ['codegraphStatus', 'codegraphQuery'],\r\n codegraphQuery: ['codegraphStatus', 'codegraphGraph'],\r\n codegraphFiles: ['codegraphGraph', 'codegraphQuery'],\r\n codegraphImpact: ['codegraphCallers', 'codegraphCallees'],\r\n codegraphCallers: ['codegraphImpact', 'codegraphCallees'],\r\n codegraphCallees: ['codegraphImpact', 'codegraphCallers'],\r\n codegraphBootstrap: ['codegraphInit'],\r\n codegraphIncremental: ['codegraphSync', 'codegraphInit'],\r\n codegraphAiAssist: ['codegraphQuery', 'codegraphGraph'],\r\n codegraphSemanticSearch: ['codegraphQuery', 'codegraphGraph'],\r\n codegraphInit: ['codegraphBootstrap'],\r\n codegraphSync: ['codegraphIncremental', 'codegraphBootstrap'],\r\n lensAnalyze: ['codegraphQuery', 'codegraphGraph'],\r\n lensImpact: ['codegraphImpact', 'codegraphCallers'],\r\n};","import type { GraphNode, GraphEdge, GraphDelta } from '../types/index.ts';\n\n/**\n * Structural difference between two graph revisions.\n *\n * The shape is declared in `types/index.ts` (where the Cordis event payloads live) and re-exported\n * here so both the host diff engine and the client store can name it without importing each other.\n */\nexport type { GraphDelta };\n\nexport interface GraphSkeleton {\n nodes: GraphNode[];\n edges: GraphEdge[];\n}\n\n/**\n * 写入侧防缩水守卫的阈值与规模下限。\n *\n * **两个调用点(client store 与 host incrementalUpdate)必须传同一个 `MIN_RETAIN_RATIO`**:\n * 一处拦一处放只会把问题推迟到另一条路径上。所以常量放在这里(共享层),而不是每个调用点各写\n * 一个数字字面量。\n */\nexport const GRAPH_DELTA_SHRINK = Object.freeze({\n /**\n * delta 应用后必须保留的最低比例。0 = 关闭(守卫的默认状态)。\n *\n * 取值 0.5:一次\"删掉一半以上\"的增量几乎不可能是正常的单文件改动,而落回全量重取总能拿到\n * 正确结果。代价是**不对称**的,故意偏向拒绝:\n * - 误拒:多一次全量重载 = 一次传输(结果仍然正确,只是慢一拍);\n * - 误受:静默丢图 = 用户看到的图不再对应索引,而且**没有任何错误可见**。\n * 所以这里宁可多拒,也不接受一次错误的缩水。\n *\n * 诚实边界:本守卫只防**断崖**(一次掉到阈值以下),不防渐进缩水(每次掉 40% 连掉三次)。\n * 它也不是\"覆盖率 100%\"的保证,只是把最坏的一种失败从\"静默丢图\"换成\"多一次重载\"。\n */\n MIN_RETAIN_RATIO: 0.5,\n /**\n * 比例判定的规模下限:节点/边少于这个数时**不做**缩水判定。\n *\n * 比例在极小图上没有意义 —— 3 个节点删 2 个是 0.33,删掉一个文件的全部符号是再正常不过的\n * 改动。阈值因此只在图上还有一定规模时生效;本仓库已有的同类决策\n * (`INCREMENTAL_CLUSTER.MIN_PREV_NODES`)用的也是\"先看规模再谈比例\"的形状。\n */\n MIN_GUARDED_NODES: 20,\n MIN_GUARDED_EDGES: 20,\n});\n\n/** `canApplyGraphDelta` 的可选守卫项。全部可选,且默认值等于旧行为。 */\nexport interface GraphDeltaGuardOptions {\n /**\n * delta 应用后必须保留的节点/边比例,取值 (0, 1];0 或省略 = 关闭守卫(向后兼容)。\n *\n * 比较的是「delta 应用**后**的保留比例」,不是「被删掉的比例」:`next / current`。\n * 判定是**严格小于**:恰好等于阈值视为通过(边界被 `graphDeltaShrinkGuard.test.ts` 钉死)。\n */\n minRetainRatio?: number;\n}\n\n/** No structural change at all — callers use this to skip an emit entirely. */\nexport function isEmptyGraphDelta(delta: GraphDelta): boolean {\n return delta.addedNodes.length === 0 && delta.removedNodes.length === 0 &&\n delta.modifiedNodes.length === 0 && delta.addedEdges.length === 0 &&\n delta.removedEdges.length === 0 && delta.modifiedEdges.length === 0;\n}\n\n/** Counts per side, for logging and for the SSE payload's own summary. */\nexport function graphDeltaSize(delta: GraphDelta): { nodes: number; edges: number } {\n return {\n nodes: delta.addedNodes.length + delta.removedNodes.length + delta.modifiedNodes.length,\n edges: delta.addedEdges.length + delta.removedEdges.length + delta.modifiedEdges.length,\n };\n}\n\n/**\n * Apply `delta` to `current`, returning a new skeleton.\n *\n * Removals are processed before upserts: `computeGraphDiff` reports the *before* state for removed\n * entries and the *after* state for added/modified ones, so an id that moved between buckets within\n * one delta would otherwise resurrect a deleted node.\n */\nexport function applyGraphDelta(current: GraphSkeleton, delta: GraphDelta): GraphSkeleton {\n const removedNodeIds = new Set<string>(delta.removedNodes.map((n) => n.id as string));\n const removedEdgeIds = new Set<string>(delta.removedEdges.map((e) => e.id as string));\n\n const nodes = new Map<string, GraphNode>();\n for (const n of current.nodes) {\n if (!removedNodeIds.has(n.id as string)) nodes.set(n.id as string, n);\n }\n for (const n of delta.addedNodes) nodes.set(n.id as string, n);\n for (const n of delta.modifiedNodes) nodes.set(n.id as string, n);\n\n const edges = new Map<string, GraphEdge>();\n for (const e of current.edges) {\n if (!removedEdgeIds.has(e.id as string)) edges.set(e.id as string, e);\n }\n for (const e of delta.addedEdges) edges.set(e.id as string, e);\n for (const e of delta.modifiedEdges) edges.set(e.id as string, e);\n\n return { nodes: [...nodes.values()], edges: [...edges.values()] };\n}\n\n/**\n * 保留比例:delta 应用后的规模 / 当前规模。当前规模为 0 时返回 1(没有可缩水的图)。\n */\nfunction retainedRatio(currentSize: number, nextSize: number): number {\n if (currentSize === 0) return 1;\n return nextSize / currentSize;\n}\n\n/**\n * Whether `delta` can be applied to `current` without corrupting it.\n *\n * A delta is only meaningful against the exact revision it was computed from. When the client's\n * copy has drifted (a full `graph/data` landed mid-flight, a reconnect skipped events, the store\n * was reset) applying it would silently drop or resurrect entries, so the client uses this to\n * decide between `applyGraphDelta` and a full re-bootstrap.\n *\n * Two things are checked: every removed/modified entry must exist in `current` (otherwise the\n * delta describes a revision we never had), and every edge the delta introduces or touches must\n * have both endpoints present once the node changes are folded in (otherwise we would publish\n * dangling edges).\n *\n * P0-3(写入侧防缩水):可选的 `options.minRetainRatio` 增加**规模维度**——上面两条都通过、\n * 但 delta 会把图缩到阈值以下时也返回 false,让调用方落回全量重取(`false` 的既有语义就是\n * \"别应用,重新取一份\")。判定只比较 delta 应用后的保留比例,理由与边界写在\n * `GRAPH_DELTA_SHRINK.MIN_RETAIN_RATIO` 上;默认 0 表示关闭,既有调用方不传选项时行为不变。\n */\nexport function canApplyGraphDelta(\n current: GraphSkeleton,\n delta: GraphDelta,\n options: GraphDeltaGuardOptions = {},\n): boolean {\n const currentNodeIds = new Set<string>(current.nodes.map((n) => n.id as string));\n const currentEdgeIds = new Set<string>(current.edges.map((e) => e.id as string));\n\n for (const n of delta.removedNodes) if (!currentNodeIds.has(n.id as string)) return false;\n for (const n of delta.modifiedNodes) if (!currentNodeIds.has(n.id as string)) return false;\n for (const e of delta.removedEdges) if (!currentEdgeIds.has(e.id as string)) return false;\n for (const e of delta.modifiedEdges) if (!currentEdgeIds.has(e.id as string)) return false;\n\n const nextNodeIds = new Set<string>(currentNodeIds);\n for (const n of delta.removedNodes) nextNodeIds.delete(n.id as string);\n for (const n of delta.addedNodes) nextNodeIds.add(n.id as string);\n for (const n of delta.modifiedNodes) nextNodeIds.add(n.id as string);\n\n for (const e of [...delta.addedEdges, ...delta.modifiedEdges]) {\n if (!nextNodeIds.has(e.source as string) || !nextNodeIds.has(e.target as string)) return false;\n }\n\n const minRetainRatio = options.minRetainRatio ?? 0;\n if (minRetainRatio > 0) {\n const nextEdgeIds = new Set<string>(currentEdgeIds);\n for (const e of delta.removedEdges) nextEdgeIds.delete(e.id as string);\n for (const e of delta.addedEdges) nextEdgeIds.add(e.id as string);\n for (const e of delta.modifiedEdges) nextEdgeIds.add(e.id as string);\n\n if (currentNodeIds.size >= GRAPH_DELTA_SHRINK.MIN_GUARDED_NODES\n && retainedRatio(currentNodeIds.size, nextNodeIds.size) < minRetainRatio) return false;\n if (currentEdgeIds.size >= GRAPH_DELTA_SHRINK.MIN_GUARDED_EDGES\n && retainedRatio(currentEdgeIds.size, nextEdgeIds.size) < minRetainRatio) return false;\n }\n return true;\n}\n","// host 侧快照写入的防缩水守卫(cycle-110 / P0-7,cycle-109 carry-over)。\n//\n// ## 为什么 host 侧也需要它\n//\n// `src/shared/graphDelta.ts` 的 `canApplyGraphDelta` 守卫的是「delta 应用**后**的图」,\n// 两个调用点(client store `graphDataSlice` / host `incrementalUpdate`)都已接线。但\n// **快照落盘**是第三条写路径:`triggerBackgroundBuild` 把一次全量扫描的结果写进\n// SQLite 的 `snapshots` 表(`src/host/routes/shared.ts` 的 `saveSnapshot` 调用点)。\n// 一次半读(上游重建中途、DB 半写、工作区解析错位)会产出一张断崖式变小的图,而\n// **快照一旦落盘就带着\"这是该 repo 的最新结构\"的身份**——后续的指纹比对、恢复与\n// 差分都以它为基线。静默写入一张缩水快照的代价不可见,拒绝写入的代价只是磁盘上的\n// 基线晚一轮更新(当前版本里没有任何渲染路径读 snapshots;见下方\"诚实边界\")。\n//\n// ## 为什么不自己发明阈值\n//\n// 阈值、规模下限、比较方向(`next / current`、严格小于)全部**复用**\n// `GRAPH_DELTA_SHRINK`:两条写路径必须用同一组数字,否则「一处拦一处放」只是把问题\n// 推到另一条路径上(该常量文件里的注释已写明这条约束)。本模块不定义任何新数字。\n//\n// ## 与 `canApplyGraphDelta` 的两点刻意差异\n//\n// 1. **没有 delta 可折叠**:这里比较的是「上一张快照的 node_count/edge_count」与\n// 「本次扫描结果」。`getLatestSnapshot()` 返回的记录自带这两个计数,因此不需要\n// 反序列化 payload(反序列化 100k 节点的图只为数个数是不可接受的代价)。\n// 2. **需要一条\"回退通道\"**:`canApplyGraphDelta` 返回 false 时调用方落回**全量重取**,\n// 重取得到的正确答案仍是那张小图,所以不存在永久拒绝。快照写入没有「重取」这一步:\n// 用户真的删掉了 80% 的代码时,此后**每一次**扫描都比基线小,单纯「拒绝写入」\n// 会让基线被永久锁死在一张过时的图上——那正是「守卫退化成锁」的经典形态\n// (与 `docs/需求分析.md` §59.25 里 `loaded.length >= knownTotal` 的教训同族)。\n// 因此这里采用**两次确认(two-strike)**:同一次缩水的**相同指纹**第一次被拒绝,\n// 第二次出现时接受——内容指纹是图结构的确定性函数,连续两轮扫出完全一致的较小图\n// 说明缩水是真实改动,而不是一次半读。\n//\n// ## 诚实边界\n//\n// - 只防**断崖**,不防渐进缩水(每轮掉 40% 连掉三轮,每轮都在阈值之上)。\n// - 小于 `MIN_GUARDED_NODES/EDGES` 的图**完全不设防**:3 个节点删 2 个是 0.33,\n// 而这在删掉一个文件的全部符号时再正常不过。这个洞是刻意留的且被用例钉住\n// (\"下限以下没有保护\"),不是遗漏。\n// - 当前没有任何生产代码读 `snapshots` 表做恢复;本守卫保护的是**未来的**恢复路径\n// 与指纹基线,请不要据此宣称\"用户永远看不到缩水\"。\n\nimport { GRAPH_DELTA_SHRINK } from '../../shared/graphDelta.ts';\n\n/** 快照的规模视图:`GraphData.metadata` 与 `SnapshotRecord` 都满足它。 */\nexport interface SnapshotSize {\n readonly nodeCount: number;\n readonly edgeCount: number;\n}\n\n/** 判定原因。刻意做成可枚举的字符串,便于日志与用例断言「为什么放/拦」。 */\nexport type SnapshotShrinkReason =\n /** 没有基线(首次写入、或表为空)—— 无从比较,放行。 */\n | 'no-previous'\n /** 守卫关闭(阈值为 0)。 */\n | 'guard-off'\n /** 两个维度都低于规模下限 —— 比例在极小图上没有意义,放行。 */\n | 'below-floor'\n /** 规模维度通过。 */\n | 'within-budget'\n /** 至少一个维度断崖式缩小 —— 拒绝写入。 */\n | 'shrink-refused';\n\nexport interface SnapshotShrinkDecision {\n allowed: boolean;\n reason: SnapshotShrinkReason;\n /** `next / previous`;基线为 0 时记 1(没有可缩水的基数),无基线时为 null。 */\n nodeRatio: number | null;\n edgeRatio: number | null;\n}\n\nexport interface SnapshotShrinkOptions {\n /** 必须保留的最低比例;0 = 关闭(与 `canApplyGraphDelta` 的默认语义一致)。 */\n minRetainRatio?: number;\n}\n\n/** 保留比例:`next / previous`。基线为 0 时返回 1(无基数即无缩水)。 */\nfunction retainedRatio(previousSize: number, nextSize: number): number {\n if (previousSize === 0) return 1;\n return nextSize / previousSize;\n}\n\n/**\n * 纯判定:本次扫描结果能否作为新快照落盘。\n *\n * 比较方向与边界语义逐条对齐 `canApplyGraphDelta`:\n * - 比例是 `next / previous`,不是「删掉了多少」;\n * - 判定是**严格小于**阈值(恰好等于阈值放行,由用例在边界上钉住);\n * - 每个维度各自看自己的规模下限(节点看 `MIN_GUARDED_NODES`、边看 `MIN_GUARDED_EDGES`)。\n *\n * **不含任何状态**:两次确认所需的「上一轮被拒指纹」由调用方持有(见\n * {@link rememberRefusedSnapshot})。\n */\nexport function evaluateSnapshotShrink(\n previous: SnapshotSize | null | undefined,\n next: SnapshotSize,\n options: SnapshotShrinkOptions = {},\n): SnapshotShrinkDecision {\n if (!previous) {\n return { allowed: true, reason: 'no-previous', nodeRatio: null, edgeRatio: null };\n }\n\n const nodeRatio = retainedRatio(previous.nodeCount, next.nodeCount);\n const edgeRatio = retainedRatio(previous.edgeCount, next.edgeCount);\n\n const minRetainRatio = options.minRetainRatio ?? 0;\n if (minRetainRatio <= 0) {\n return { allowed: true, reason: 'guard-off', nodeRatio, edgeRatio };\n }\n\n const nodeGuarded = previous.nodeCount >= GRAPH_DELTA_SHRINK.MIN_GUARDED_NODES;\n const edgeGuarded = previous.edgeCount >= GRAPH_DELTA_SHRINK.MIN_GUARDED_EDGES;\n if (!nodeGuarded && !edgeGuarded) {\n return { allowed: true, reason: 'below-floor', nodeRatio, edgeRatio };\n }\n\n const nodeShrunk = nodeGuarded && nodeRatio < minRetainRatio;\n const edgeShrunk = edgeGuarded && edgeRatio < minRetainRatio;\n if (nodeShrunk || edgeShrunk) {\n return { allowed: false, reason: 'shrink-refused', nodeRatio, edgeRatio };\n }\n\n return { allowed: true, reason: 'within-budget', nodeRatio, edgeRatio };\n}\n\n// ── 两次确认的状态 ────────────────────────────────────────────────────\n//\n// 键 = repoId,值 = **上一次被拒**的内容指纹。调用方在拒绝时登记,在放行(写成功)\n// 时清除。指纹由 `computeGraphFingerprint` 生成(`v1:<16 hex>`),所以「同一个指纹\n// 再出现」严格等价于「扫出了一模一样的较小图」。\n//\n// 这是**模块级单例**:必须能被重置,否则用例之间会互相污染(CLAUDE.md 已记录\n// 「模块级单例必须成对重置」的教训)。重置点有两个:\n// 1. `registerRoutes()` 的 `ctx.effect` 清理(随插件卸载);\n// 2. 用例的 `afterEach`。\nconst refusedFingerprints = new Map<string, string>();\n\n/** 登记一次被拒的缩水写入,供下一轮判定「是否同一张缩水图」。 */\nexport function rememberRefusedSnapshot(repoId: string, fingerprint: string): void {\n refusedFingerprints.set(repoId, fingerprint);\n}\n\n/** 该指纹是否**正是上一轮被拒**的那一个(= 缩水已连续两轮确认)。 */\nexport function wasRefusedBefore(repoId: string, fingerprint: string): boolean {\n return refusedFingerprints.get(repoId) === fingerprint;\n}\n\n/** 写入成功后清除登记:基线已经前移,上一轮的拒绝不再有参考价值。 */\nexport function clearRefusedSnapshot(repoId: string): void {\n refusedFingerprints.delete(repoId);\n}\n\n/** 重置全部状态(插件卸载 / 用例隔离)。 */\nexport function resetSnapshotShrinkState(): void {\n refusedFingerprints.clear();\n}\n","import fs from 'node:fs/promises';\r\nimport type { IncomingMessage, ServerResponse } from '../types/index.ts';\r\nimport type { SecurityManager } from './security.ts';\r\nimport { scoped } from '../shared/Logger.ts';\r\nimport { ERROR_MESSAGES, ERROR_CODES } from '../shared/constants.ts';\r\n\r\nconst log = scoped('host');\r\n\r\nexport function getClientIp(req: IncomingMessage): string {\r\n const socketAddr = req.socket?.remoteAddress;\r\n return socketAddr ?? 'unknown';\r\n}\r\n\r\nexport function readBody(req: IncomingMessage, maxBodyBytes: number): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n let body = '';\r\n let totalBytes = 0;\r\n req.on('data', (chunk?: Buffer) => {\r\n if (!chunk) return;\r\n totalBytes += chunk.length;\r\n if (totalBytes > maxBodyBytes) {\r\n req.destroy();\r\n reject(new Error(ERROR_CODES.BODY_TOO_LARGE));\r\n return;\r\n }\r\n body += chunk.toString();\r\n });\r\n req.on('end', () => resolve(body));\r\n req.on('error', reject);\r\n });\r\n}\r\n\r\nexport function parseJsonBody(body: string): Record<string, unknown> {\r\n const parsed = JSON.parse(body || '{}');\r\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\r\n throw new Error(ERROR_CODES.INVALID_JSON_BODY);\r\n }\r\n return parsed as Record<string, unknown>;\r\n}\r\n\r\nexport function extractStringField(obj: Record<string, unknown>, field: string, maxLength?: number): string | undefined {\r\n const v = obj[field];\r\n if (typeof v !== 'string') return undefined;\r\n if (v.includes('\\0')) return undefined;\r\n if (maxLength !== undefined && v.length > maxLength) return undefined;\r\n return v;\r\n}\r\n\r\nexport function extractBooleanField(obj: Record<string, unknown>, field: string): boolean | undefined {\r\n const v = obj[field];\r\n return typeof v === 'boolean' ? v : undefined;\r\n}\r\n\r\n/**\r\n * 合法的 **git revision 表达式**字符集(P0-C,cycle-105 真机发现)。\r\n *\r\n * 历史:这里原本是 `/^(?!.*\\.\\.)[a-zA-Z0-9._/-]+$/`,即「合法分支名」的字符集。但调用方\r\n * (`POST /diff` 的 `from`/`to`)要的是 **revision 表达式**,不是分支名——两者是不同的语法:\r\n *\r\n * - 分支名只允许 `[A-Za-z0-9._/-]`;\r\n * - revision 还必须允许 `~`(`HEAD~3`)、`^`(`HEAD^`、`HEAD^2`)、`@{...}`(`HEAD@{1}`、\r\n * `main@{upstream}`)以及 `@`(`@` 是 `HEAD` 的别名)。\r\n *\r\n * 真机实测:面板默认值 `HEAD~1` → `HEAD` 被 **100% 拒绝**(`400 INVALID_GIT_REF`),\r\n * 也就是说 cycle-104 交付的「版本对比」在真机上的可用性为零——用户必须先手输完整 SHA。\r\n * 这不是参数校验过严的「小问题」,而是把默认路径整条打死:默认值永远失败,功能等于没有。\r\n *\r\n * 安全性仍必须守住,因此保留三条硬性拒绝:\r\n *\r\n * 1. **禁止以 `-` 开头**。否则 `--upload-pack=...` 这类参数会被 git 当成选项解析\r\n * (参数注入)。这条是新增的:旧正则本身就排除了 `-` 开头的字符串,重写时不能丢掉。\r\n * 2. **禁止 `..`**(`a..b` 是范围语法,不是单个 revision,混进来会让 diff 语义变成\r\n * 「三个点/两个点」的范围而不是「两个端点」)。\r\n * 3. **禁止空白、`\\`、`:`、`?`、`*`、`[`、控制字符**——它们是 git 的 pathspec/通配符语法,\r\n * 也可能被 shell 或 Windows 路径解析改写。\r\n *\r\n * 注意 `@{` 必须成对出现由 git 自己校验;这里只保证字符集安全,不试图重写 git 的语法。\r\n */\r\nconst GIT_REF_PATTERN = /^(?!-)(?!.*\\.\\.)[A-Za-z0-9._/~^@{}+-]+$/;\r\n\r\n/**\r\n * 判断一个字符串是否可以安全地作为 **git revision 表达式**传给子进程。\r\n *\r\n * 只做「字符集 + 结构」的白名单校验,**不做**「这个 revision 是否存在」的判断——\r\n * 后者只有 git 自己知道,且必须由调用方在真实仓库里问(见 `src/host/gitDiff.ts` 的\r\n * `collectGitFileDiff`,它把 git 的失败原样变成 `available: false` + `error`)。\r\n * 把存在性也塞进校验函数,会让一个纯函数变成需要仓库上下文的函数。\r\n */\r\nexport function isSafeGitRef(ref: string): boolean {\r\n if (typeof ref !== 'string') return false;\r\n // 长度上限由调用方(`extractStringField`)保证;这里再兜一次,避免有人直接调用。\r\n if (ref.length === 0 || ref.length > 256) return false;\r\n // `@{` 未闭合会让 git 报出难以理解的错误,提前拒绝给出明确的 400。\r\n const openBraces = (ref.match(/\\{/g) ?? []).length;\r\n const closeBraces = (ref.match(/\\}/g) ?? []).length;\r\n if (openBraces !== closeBraces) return false;\r\n // `@` 后紧跟 `{` 才合法(`@{...}`);裸 `{` 不是 git 语法。\r\n if (ref.includes('{') && !ref.includes('@{')) return false;\r\n return GIT_REF_PATTERN.test(ref);\r\n}\r\n\r\nexport async function validatePathRealpath(scanPath: string, securityManager: SecurityManager): Promise<boolean> {\r\n try {\r\n const realPath = await fs.realpath(scanPath);\r\n return securityManager.isPathAllowed(realPath);\r\n } catch (e) {\r\n log.warn('validatePathRealpath failed', e);\r\n return false;\r\n }\r\n}\r\n\r\nexport interface CorsConfig {\r\n origins: string[];\r\n}\r\n\r\nexport function buildCorsHeaders(req: IncomingMessage | undefined, config: CorsConfig): Record<string, string> {\r\n const headers: Record<string, string> = {\r\n 'access-control-allow-methods': 'GET, POST, OPTIONS',\r\n 'access-control-allow-headers': 'content-type, if-none-match',\r\n // 跨源(桌面端 / dsh-remote-web-ui)时,`etag` 与 `x-truncated` 默认不暴露给 JS:`fetch()` 的\r\n // `res.headers.get('etag')` 会返回 null,客户端于是永远拿不到校验值,\"增量取数\"静默退化成\r\n // 每次都全量重取。这两个头必须显式列入 expose 名单。\r\n 'access-control-expose-headers': 'etag, x-truncated',\r\n 'access-control-max-age': '86400',\r\n 'vary': 'Origin',\r\n };\r\n if (config.origins.length > 0) {\r\n const reqOrigin = req?.headers?.['origin'];\r\n if (typeof reqOrigin === 'string' && config.origins.includes(reqOrigin)) {\r\n headers['access-control-allow-origin'] = reqOrigin;\r\n }\r\n }\r\n return headers;\r\n}\r\n\r\nexport function handleCorsPreflight(req: IncomingMessage, res: ServerResponse, config: CorsConfig): boolean {\r\n if (req.method !== 'OPTIONS') return false;\r\n const headers = buildCorsHeaders(req, config);\r\n res.writeHead(204, headers);\r\n res.end();\r\n return true;\r\n}\r\n\r\n/**\r\n * 回 200 时可选附加响应头。`etag` 必须走这里而不是由调用方自行 `writeHead`:\r\n * `sendJson` 是唯一同时写 `content-type` 与 CORS 头的地方,绕过它就会漏掉 CORS,\r\n * 跨源客户端(桌面端 / dsh-remote-web-ui)读不到响应头。\r\n */\r\nexport type ExtraResponseHeaders = Record<string, string>;\r\n\r\nexport function sendJson(\r\n res: ServerResponse,\r\n code: number,\r\n data: unknown,\r\n req: IncomingMessage | undefined,\r\n config: CorsConfig,\r\n extraHeaders?: ExtraResponseHeaders,\r\n): void {\r\n const headers: Record<string, string> = {\r\n 'content-type': 'application/json',\r\n ...buildCorsHeaders(req, config),\r\n ...extraHeaders,\r\n };\r\n res.writeHead(code, headers);\r\n res.end(JSON.stringify(data));\r\n}\r\n\r\nexport function sendError(res: ServerResponse, httpCode: number, errorCode: string, req: IncomingMessage | undefined, config: CorsConfig, detail?: string): void {\r\n const baseMessage = ERROR_MESSAGES[errorCode]?.en ?? errorCode;\r\n const message = detail ? `${baseMessage}: ${detail}` : baseMessage;\r\n sendJson(res, httpCode, { success: false, error: errorCode, message }, req, config);\r\n}\r\n\r\nexport function requireMethod(req: IncomingMessage, res: ServerResponse, method: string, config: CorsConfig): boolean {\r\n if (req.method === method) return true;\r\n sendError(res, 405, 'METHOD_NOT_ALLOWED', req, config, `${req.method} not allowed; expected ${method}`);\r\n return false;\r\n}\r\n\r\nexport function checkRate(req: IncomingMessage, res: ServerResponse, routePath: string, rateLimiter: ReturnType<typeof import('./RateLimiter.ts')['createRateLimiter']>, config: CorsConfig): boolean {\r\n if (!rateLimiter.check(getClientIp(req), routePath)) {\r\n sendError(res, 429, 'RATE_LIMIT_EXCEEDED', req, config);\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\nexport function logRequest(req: IncomingMessage, routePath: string): void {\r\n log.info('request', { method: req.method, path: routePath, ip: getClientIp(req) });\r\n}","import type { Context } from '@deepseek-ai/cordis';\n\nimport path from 'node:path';\nimport { scoped } from '../../shared/Logger.ts';\nimport { ERROR_CODES } from '../../shared/constants.ts';\nimport { computeGraphEtag, computeGraphFingerprint } from '../../shared/graphFingerprint.ts';\nimport { sameRepoId } from '../../shared/pathUtils.ts';\nimport type { GraphEtagResource } from '../../shared/graphFingerprint.ts';\nimport type { GraphData, GraphNode, GraphEdge, IncomingMessage, ServerResponse, IGraphVisualizerService } from '../../types/index.ts';\nimport { RepoId, NodeId, EdgeId } from '../../types/index.ts';\nimport { fetchMergedGraph } from '../../tools.ts';\nimport { createInvoke } from '../../tools.ts';\nimport type { UpstreamInvoker } from '../../adapters/IAdapter.ts';\nimport { SecurityManager } from '../security.ts';\nimport type { WorkspaceRootSource } from '../security.ts';\nimport { findWorkspacePath, resolveWorkspaceRoots } from '../prerequisites.ts';\nimport type { VisualizerConfig } from '../config.ts';\nimport type { ApiCompatLayer, FeatureFlags } from '../compat/index.ts';\nimport {\n evaluateSnapshotShrink,\n rememberRefusedSnapshot,\n wasRefusedBefore,\n clearRefusedSnapshot,\n} from '../compat/index.ts';\nimport { GRAPH_DELTA_SHRINK } from '../../shared/graphDelta.ts';\nimport { createRateLimiter } from '../RateLimiter.ts';\nimport type { GraphRepository } from '../db/GraphRepository.ts';\nimport {\n validatePathRealpath, buildCorsHeaders, handleCorsPreflight,\n sendError, requireMethod, checkRate, logRequest,\n type CorsConfig,\n} from '../HttpHelpers.ts';\n\nexport const log = scoped('host');\n\n/**\n * Shape check for a caller-supplied *relative* path. Both separators are accepted on every\n * platform: `path.normalize()` rewrites `/` to `\\` on win32, so comparing its output against the\n * raw input rejected perfectly valid forward-slash paths (`src/App.tsx`) while the sibling `/ast`\n * route accepted them. What must stay rejected is traversal and anything absolute.\n */\nexport function isSafeRelativePath(value: string, maxLength: number): boolean {\n if (!value || value.length > maxLength || value.includes('\\0')) return false;\n if (path.isAbsolute(value) || /^[a-zA-Z]:/.test(value) || value.startsWith('\\\\\\\\')) return false;\n const normalized = path.normalize(value);\n return !normalized.startsWith('..') && !path.isAbsolute(normalized);\n}\n\nexport type RouteHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;\n\nexport function withRouteMiddleware(\n method: string,\n routePath: string,\n corsConfig: CorsConfig,\n rateLimiter: ReturnType<typeof createRateLimiter>,\n handler: RouteHandler,\n): RouteHandler {\n return async (req, res) => {\n if (handleCorsPreflight(req, res, corsConfig)) return;\n if (!requireMethod(req, res, method, corsConfig)) return;\n logRequest(req, routePath);\n if (!checkRate(req, res, routePath, rateLimiter, corsConfig)) return;\n try {\n await handler(req, res);\n } catch (e) {\n log.error('route handler error', { routePath, error: e });\n if (!res.headersSent) {\n sendError(res, 500, ERROR_CODES.INTERNAL_ERROR, req, corsConfig);\n }\n }\n };\n}\n\nexport interface WorkspaceRootsRefresh {\n /** Roots the security manager now accepts (raw paths). */\n roots: string[];\n /** Which host source produced the newly resolved roots. */\n source: WorkspaceRootSource;\n /** The roots resolved for this request, regardless of de-duplication. */\n resolved: string[];\n}\n\n/**\n * Re-resolve and widen the allowlist before validating a path.\n *\n * The allowlist used to be computed once in `apply()` (`src/index.ts`) and frozen, so a workspace\n * the host registered later — or a registry that was still empty at boot — denied every file route\n * with a bare 403 and no diagnostic (`docs/需求分析.md` §31.7). Roots only ever come from host\n * services (`workspaceRegistry` / `sessions` / the host's own cwd), never from the request, which is\n * why growing the list here cannot be used to read an arbitrary path.\n */\nexport function ensureWorkspaceRootsAllowed(ctx: Context, deps: RouteDeps): WorkspaceRootsRefresh {\n let resolved: string[] = [];\n let source: WorkspaceRootSource = 'unknown';\n try {\n const resolution = resolveWorkspaceRoots(ctx);\n resolved = resolution.paths;\n source = resolution.source;\n deps.securityManager.addAllowedWorkspaceRoots(resolved, source);\n } catch (e) {\n log.warn('ensureWorkspaceRootsAllowed failed', e);\n }\n const roots = deps.securityManager.getSnapshot().roots;\n return { roots, source, resolved };\n}\n\n/**\n * 从请求里解析工作区路径。\n *\n * ## 为什么必须同时看 query 与 body(P0-E,cycle-105 真机发现)\n *\n * 历史实现**只读 `url.searchParams.get('workspace')`**。而 `POST /diff` 的客户端\n * (`src/client/api/index.ts` 的 `fetchGraphDiff`)把 workspace 放在 **JSON body** 里——\n * 全仓唯一一处这么做的按需路由。于是:\n *\n * - 客户端明确声明「对比 A 工作区」,宿主完全看不见这个声明;\n * - 回落到 `findWorkspacePath(ctx)`,即宿主工作区列表里的**第一个**;\n * - 真机实测:请求 `workspace: 'D:\\...\\dsh-codegraph-visualizer'`,响应的\n * `metadata.repoId` 却是 `D:\\...\\dsh-anic`——**另一个项目**。\n *\n * 这与 cycle-105 修的 P0-B 同族但方向相反:P0-B 是「回落读错图槽」,P0-E 是\n * 「解析错工作区」。两者叠加的效果是「用户以为在对比自己的仓库,实际在对比别人的」。\n *\n * ## 安全性不变\n *\n * body 里的 workspace **不是**一条新的信任通道:它和 query 参数走完全相同的两道校验\n * (`isPathAllowed` 允许列表 + `validatePathRealpath` 符号链接解析)。请求方无法通过\n * 换一个位置传参来读到允许列表之外的路径——这正是本函数把校验放在「取到候选值之后」\n * 而不是「取 query 之时」的原因。\n *\n * ## 优先级\n *\n * query > body > 宿主默认。query 优先是刻意的向后兼容:既有 GET 路由的行为一位不变,\n * 而同时带两处参数的请求(不应存在)也不会因为实现细节改变语义。\n *\n * @param bodyWorkspace POST 请求体里已解析出的 `workspace` 字段(GET 路由传 `undefined`)。\n */\nexport async function resolveWorkspaceFromRequest(\n req: IncomingMessage,\n ctx: Context,\n deps: RouteDeps,\n bodyWorkspace?: string | undefined,\n): Promise<string> {\n ensureWorkspaceRootsAllowed(ctx, deps);\n\n // 候选值按优先级排列,**校验只做一次**:把「取值」与「验值」分开,避免两个来源\n // 各自复制一遍校验逻辑(复制出来的第二份迟早会漏掉 `validatePathRealpath`)。\n let candidate: string | null = null;\n try {\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\n candidate = url.searchParams.get('workspace');\n } catch { /* ignore */ }\n if (!candidate || candidate === '.') {\n candidate = typeof bodyWorkspace === 'string' ? bodyWorkspace : null;\n }\n\n if (candidate && candidate !== '.') {\n // 与历史实现同一条判据:允许列表 + 真实路径(防符号链接逃逸)。\n if (deps.securityManager.isPathAllowed(candidate)) {\n if (await validatePathRealpath(candidate, deps.securityManager)) {\n return candidate;\n }\n }\n // 候选值存在但**未通过校验**时不能静默回落:那会把「请求了一个无权工作区」\n // 变成「悄悄用了另一个工作区」,正是 P0-E 的危害形态。这里保持既有语义\n // (回落到默认工作区,由调用方的 403 分支负责拒绝),但把它记下来——\n // 真机排查时这条日志是唯一能看出「参数被丢弃了」的线索。\n log.warn('requested workspace rejected, falling back to default', { candidate });\n }\n\n return findWorkspacePath(ctx);\n}\n\n/**\n * 取 host 的 `lastGraphData` 单槽,**但只在它确实属于请求方工作区时**才返回(P0-B)。\n *\n * 背景:host 只有一个图槽(`deps.getLastGraphData()`),而客户端可以在同一次会话里切换多个\n * 工作区。此前 `/bootstrap`、`/children`、`/files/:id/outline`、`/symbols/:id/ast`、`/edges`\n * 五条路由的**内存回落分支**全都无条件读这个槽,于是:\n *\n * - 请求工作区 B,槽里是 A 的图 ⇒ `/bootstrap` 把 **A 的顶层节点**连同 `metadata.repoId: A`\n * 一起发给 B 的客户端。客户端 `setBootstrapData(..., data.metadata.repoId)` 会把这个外来\n * repoId 写进 store,等于**用 A 的身份覆盖 B 的画布身份**;\n * - 更隐蔽的一半:`/children`、`/outline`、`/ast`、`/edges` 用 `nodeId` 在 A 的图里查表,\n * 大多数情况查不到(返回空),于是 B 表现为「索引已就绪但一个子节点都没有」——\n * 一个**静默的空画布**,没有任何错误可看。\n *\n * 修法刻意与 `incrementalUpdate.ts` / `watcher.ts` 的既有防护共用 `sameRepoId` 判据:\n * 跨工作区防护在三个地方出现过,判据必须只有一份实现,否则三处会各自漂移。\n *\n * `workspacePath` 为空(`findWorkspacePath` 也没解析出工作区)时返回 `null`:没有身份就没有\n * 「同源」可言,宁可退化成空响应,也不能让一个无身份的请求拿到任意工作区的图。\n */\nexport function getWorkspaceScopedGraphData(\n deps: RouteDeps,\n workspacePath: string | null | undefined,\n): GraphData | null {\n const slot = deps.getLastGraphData();\n if (!slot || !workspacePath) return null;\n if (!sameRepoId(slot.metadata.repoId, workspacePath)) {\n log.info('in-memory graph slot belongs to another workspace, ignoring fallback', {\n requested: workspacePath,\n slotRepoId: slot.metadata.repoId,\n });\n return null;\n }\n return slot;\n}\n\nexport async function resolveAndValidatePath(\n pathInput: string | undefined,\n ctx: Context,\n deps: RouteDeps,\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: CorsConfig,\n): Promise<string | null> {\n ensureWorkspaceRootsAllowed(ctx, deps);\n if (pathInput && pathInput !== '.' && !deps.securityManager.isPathAllowed(pathInput)) {\n sendError(res, 403, ERROR_CODES.PATH_NOT_ALLOWED, req, corsConfig);\n return null;\n }\n const resolved = pathInput && pathInput !== '.' ? pathInput : findWorkspacePath(ctx);\n if (resolved && resolved !== '.' && !deps.securityManager.isPathAllowed(resolved)) {\n sendError(res, 403, ERROR_CODES.WORKSPACE_PATH_NOT_ALLOWED, req, corsConfig);\n return null;\n }\n if (resolved && resolved !== '.' && !await validatePathRealpath(resolved, deps.securityManager)) {\n sendError(res, 403, ERROR_CODES.SYMLINK_ESCAPE, req, corsConfig);\n return null;\n }\n return resolved;\n}\n\nexport async function resolveAndValidateRelativePath(\n relativePath: string,\n ctx: Context,\n deps: RouteDeps,\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: CorsConfig,\n): Promise<string | null> {\n // Request-scoped: honours `?workspace=<path>` so a caller can address the workspace it is\n // actually looking at instead of whichever workspace the host happens to list first.\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\n if (!workspacePath) {\n sendError(res, 400, ERROR_CODES.NO_WORKSPACE_PATH, req, corsConfig);\n return null;\n }\n const fullPath = path.resolve(workspacePath, relativePath);\n const normalizedWorkspace = path.normalize(workspacePath);\n if (!fullPath.startsWith(normalizedWorkspace + path.sep) && fullPath !== normalizedWorkspace) {\n sendError(res, 403, ERROR_CODES.PATH_OUTSIDE_WORKSPACE, req, corsConfig);\n return null;\n }\n if (!await validatePathRealpath(fullPath, deps.securityManager)) {\n sendError(res, 403, ERROR_CODES.SYMLINK_ESCAPE, req, corsConfig);\n return null;\n }\n return fullPath;\n}\n\nexport async function validateWorkspacePath(\n ctx: Context,\n deps: RouteDeps,\n req?: IncomingMessage,\n bodyWorkspace?: string | undefined,\n): Promise<string | null> {\n let workspacePath: string;\n if (req) {\n workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps, bodyWorkspace);\n } else {\n ensureWorkspaceRootsAllowed(ctx, deps);\n workspacePath = findWorkspacePath(ctx);\n }\n if (!workspacePath) {\n return null;\n }\n if (!deps.securityManager.isPathAllowed(workspacePath)) {\n return '__denied__';\n }\n if (!await validatePathRealpath(workspacePath, deps.securityManager)) {\n return '__denied__';\n }\n return workspacePath;\n}\n\n/**\n * 304 短路:客户端带来的校验值与 `serverEtag` 完全一致时回 304 并返回 `true`。\n *\n * 本函数只负责\"比较并回包\",**不负责决定校验值是什么**——校验值的构造必须由调用方通过\n * `returnIfEtagMatch` 传入身份与资源维度,否则就会退化成\"校验器与被校验资源无关\"。\n */\nexport function checkEtag(\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: CorsConfig,\n serverEtag: string,\n): boolean {\n const ifNoneMatch = req.headers['if-none-match'];\n if (!ifNoneMatch) return false;\n const clientEtag = String(ifNoneMatch);\n if (clientEtag !== serverEtag) return false;\n const corsHeaders = buildCorsHeaders(req, corsConfig);\n corsHeaders['etag'] = serverEtag;\n res.writeHead(304, corsHeaders);\n res.end();\n return true;\n}\n\n/**\n * 算出本次请求的校验值;缓存槽为空时返回 `null`(此时既不能校验、也不能回写 etag)。\n *\n * 这是校验值的**唯一构造点**:304 分支与 200 响应回写都必须调用本函数,否则两侧各算一遍,\n * 一旦 `requestKey` 的拼法漂移,客户端拿到 200 里的 etag 再发回来就永远匹配不上——\n * \"增量取数\"静默退化成每次都全量重取(`/data` 分支 `dataRoutes.ts:71-73` 的注释记录了同一约束)。\n */\nexport function resourceEtag(\n deps: RouteDeps,\n resource: GraphEtagResource,\n repoId?: string | null | undefined,\n requestKey?: string,\n): string | null {\n const cachedData = deps.getLastGraphData();\n if (!cachedData) return null;\n return computeGraphEtag(\n repoId ?? cachedData.metadata.repoId,\n cachedData.metadata.timestamp,\n resource,\n requestKey,\n );\n}\n\n/**\n * 校验值的唯一构造点 + 304 判定,**一次调用同时给出两件东西**:\n * - `handled === true`:已回 304,调用方必须立刻 `return`;\n * - `etag`:非 null 时调用方**必须**把它写进 200 响应头。\n *\n * 为什么把两件事合成一个返回值(P3-R18-02):分开写就会出现\"校验器能回 304、但 200 从不带校验值\"\n * 的状态——客户端手里永远没有可回传的值,`returnIfEtagMatch` 于是成了**可达但无用**的死代码。\n * 合并后\"算了校验值\"与\"回传校验值\"在结构上不可能只做一半。\n */\nexport interface EtagDecision {\n /** 已回 304,调用方应立即返回。 */\n handled: boolean;\n /** 本次请求的校验值;槽为空时为 null(既不校验、也无值可回)。 */\n etag: string | null;\n}\n\n/**\n * 校验值 + 304 判定。`requestKey` 见 `computeGraphEtag`:参数化路由必须传入请求键,\n * 否则同一资源下不同参数的请求会共用一个校验值,客户端拿 A 的校验值问 B 会收到 304。\n */\nexport function etagGuard(\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: CorsConfig,\n deps: RouteDeps,\n resource: GraphEtagResource,\n repoId?: string | null | undefined,\n requestKey?: string,\n): EtagDecision {\n const etag = resourceEtag(deps, resource, repoId, requestKey);\n if (etag === null) return { handled: false, etag: null };\n return { handled: checkEtag(req, res, corsConfig, etag), etag };\n}\n\n/**\n * 把校验值转成 `sendJson` 的附加响应头;无校验值时返回 `undefined`(不写该头)。\n *\n * 存在的意义是让调用点**没有机会忘记回写**:`sendJson(..., etagHeaders(etag))` 是全仓统一写法,\n * 新增分支时若漏掉它,`etagGuard` 的返回类型不会报错——所以这条约束由\n * `tests/unit/etagContract.test.ts` 的源码扫描兜底。\n */\nexport function etagHeaders(etag: string | null): Record<string, string> | undefined {\n return etag === null ? undefined : { etag };\n}\n\n/**\n * 用**资源身份**(repoId + revision)与**资源维度**构造校验值,再交给 `checkEtag` 比较。\n *\n * `resource` 是必填参数,理由见 `computeGraphEtag`:缺少资源维度,\"拿 `/data` 的校验值让 `/children`\n * 回 304\"就重新成立——同一条\"校验器与被校验资源无关\"的缺陷换了个形状。\n *\n * `requestKey` 同样是必填语义(P3-R18-02):四条按需路由是参数化的,同一资源下 `children(nodeA)`\n * 与 `children(nodeB)` 必须算出不同校验值,否则客户端拿 A 的校验值问 B 会收到 304,\n * B 的子节点永远加载不出来。传 `undefined` 表示该资源确实无请求参数(`/data`、`/bootstrap`)。\n *\n * `repoId` 传**请求方声明的工作区**(`resolveWorkspaceFromRequest()` 的结果)。这样校验值对\n * `(工作区, 版本, 资源, 请求键)` 四元组单射:两个不同的请求永远不会算出同一个校验值。调用方必须\n * 先解析工作区、再调本函数(顺序反了就等于把身份维度丢掉一半)。\n *\n * `repoId` 省略时回落到槽自身的 `metadata.repoId`——`/data` 返回的就是这个槽,没有请求方工作区可言。\n *\n * 为什么身份维度可以取\"请求方声明的工作区\",而 revision 必须取槽的:这两类错误的代价不对称。校验值\n * 多算一次不匹配,只是多回一次 200、少省一次传输;而假 304 会让客户端保留过期数据并**静默丢弃一次\n * 真实更新**(P0-R16-01 的同类机理)。取请求方工作区只会让校验值更难撞上,方向是安全的。\n *\n * 本函数是 `etagGuard` 的布尔便捷形式,保留给 `/data` 这类\"校验值另有一处手工构造\"的调用点。\n */\nexport function returnIfEtagMatch(\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: CorsConfig,\n deps: RouteDeps,\n resource: GraphEtagResource,\n repoId?: string | null | undefined,\n requestKey?: string,\n): boolean {\n return etagGuard(req, res, corsConfig, deps, resource, repoId, requestKey).handled;\n}\n\nexport interface RouteDeps {\n ctx: Context;\n config: VisualizerConfig;\n securityManager: SecurityManager;\n invokeUpstream?: UpstreamInvoker;\n apiCompat?: ApiCompatLayer;\n featureFlags?: FeatureFlags;\n getLastGraphData: () => GraphData | null;\n setLastGraphData: (data: GraphData | null) => void;\n getLastInitResult: () => { success: boolean; path: string; message: string; timestamp: number } | null;\n setLastInitResult: (data: { success: boolean; path: string; message: string; timestamp: number } | null) => void;\n getScanCache: () => Map<string, { data: GraphData; timestamp: number }>;\n getScanInFlight: (path: string) => Promise<GraphData> | null;\n setScanInFlight: (path: string, p: Promise<GraphData> | null) => void;\n graphRepo?: GraphRepository | undefined;\n isDestroyed: () => boolean;\n}\n\nexport function slimGraphData(data: GraphData, maxNodes: number): GraphData {\n const cap = maxNodes;\n const truncated = data.nodes.length > cap;\n let selectedNodes: typeof data.nodes;\n\n if (truncated) {\n const degreeMap = new Map<string, number>();\n for (const n of data.nodes) degreeMap.set(n.id, 0);\n for (const e of data.edges) {\n if (e.source === e.target) continue;\n const s = degreeMap.get(e.source);\n const t = degreeMap.get(e.target);\n if (s !== undefined) degreeMap.set(e.source, s + 1);\n if (t !== undefined) degreeMap.set(e.target, t + 1);\n }\n const sorted = [...data.nodes].sort((a, b) => (degreeMap.get(b.id) ?? 0) - (degreeMap.get(a.id) ?? 0));\n selectedNodes = sorted.slice(0, cap);\n } else {\n selectedNodes = data.nodes;\n }\n\n const slimNodes = selectedNodes.map((n) => ({\n id: NodeId(n.id),\n label: n.label,\n type: n.type,\n filePath: n.filePath,\n lineNumber: n.lineNumber,\n properties: n.properties ?? {},\n ...(n.parentId ? { parentId: NodeId(n.parentId) } : {}),\n ...(n.layer ? { layer: n.layer } : {}),\n }));\n const allowedIds = new Set(slimNodes.map((n) => n.id));\n const slimEdges = data.edges\n .filter((e) => allowedIds.has(e.source) && allowedIds.has(e.target) && e.source !== e.target)\n .map((e) => ({\n id: EdgeId(e.id),\n source: NodeId(e.source),\n target: NodeId(e.target),\n type: e.type,\n properties: e.properties ?? {},\n }));\n return {\n nodes: slimNodes as GraphNode[],\n edges: slimEdges as GraphEdge[],\n metadata: {\n ...data.metadata,\n truncated,\n nodeCount: slimNodes.length,\n edgeCount: slimEdges.length,\n totalNodeCount: data.metadata.totalNodeCount ?? data.nodes.length,\n totalEdgeCount: data.metadata.totalEdgeCount ?? data.edges.length,\n },\n };\n}\n\nexport function createInvokeUpstream(ctx: Context, config: VisualizerConfig, apiCompat?: ApiCompatLayer) {\n return createInvoke(ctx, config.requestTimeout, apiCompat);\n}\n\nexport function registerService(deps: RouteDeps): void {\n const { ctx, config } = deps;\n const invokeUpstream = deps.invokeUpstream ?? createInvokeUpstream(ctx, config);\n\n ctx.effect(() => {\n const service: IGraphVisualizerService = {\n getCurrentGraph: () => deps.getLastGraphData(),\n getGraphData: async (repoId: string) => {\n try {\n const raw = await fetchMergedGraph(invokeUpstream, repoId, 'both');\n return slimGraphData(raw, config.maxNodes);\n } catch (e) { log.warn('getGraphData failed', e); return null; }\n },\n getSymbolDetail: async (symbolId: string) => {\n const result = await invokeUpstream('codegraph_query', { search: symbolId, limit: 1 });\n if (result && typeof result === 'object') return result as Record<string, unknown>;\n return null;\n },\n getImpactAnalysis: async (symbolId: string) => {\n const result = await invokeUpstream('codegraph_impact', { symbol: symbolId, depth: 2 });\n if (result && typeof result === 'object' && 'affected' in result) {\n return result as { affected: string[]; depth: number };\n }\n return null;\n },\n onGraphUpdate: (callback: (data: GraphData) => void) => {\n return ctx.on('codegraph-visualizer/graph/data', (event) => {\n callback({\n nodes: event.nodes as GraphNode[],\n edges: event.edges as GraphEdge[],\n metadata: {\n repoId: RepoId(event.repoId),\n timestamp: event.timestamp,\n nodeCount: event.nodes.length,\n edgeCount: event.edges.length,\n },\n });\n });\n },\n };\n return ctx.provide('graphVisualizer', service);\n }, 'codegraph: service registration');\n}\n\nexport interface RouteContext {\n deps: RouteDeps;\n corsConfig: CorsConfig;\n rateLimiter: ReturnType<typeof createRateLimiter>;\n invokeUpstream: ReturnType<typeof createInvokeUpstream>;\n}\n\nexport function triggerBackgroundBuild(\n deps: RouteDeps,\n invokeUpstream: ReturnType<typeof createInvokeUpstream>,\n workspacePath: string,\n): void {\n const existing = deps.getScanInFlight(workspacePath);\n if (existing) return;\n\n const cached = deps.getScanCache().get(workspacePath);\n if (cached && Date.now() - cached.timestamp < deps.config.scanCacheTtl) return;\n\n const repoId = workspacePath || `workspace-${Date.now()}`;\n\n // Prefer a bootstrap-capable upstream tool when the host actually exposes one. This used to be\n // gated on the generation-derived `incrementalBootstrap` flag, which can only produce a false\n // negative: DSH exposes no host-version API (see docs/research/dsh-0.2.0-插件API权威基线.md),\n // sibling tools mount after our own apply() runs, and the live 0.2.0-rc.2 host resolves to\n // `unknown` (J0). `getAvailableToolName` already applies the per-generation mapping plus the\n // `codegraph_init` fallback chain, so tool availability is the honest signal here.\n const bootstrapTool = deps.apiCompat\n ? deps.apiCompat.getAvailableToolName(deps.ctx, 'codegraphBootstrap')\n : null;\n\n let scanPromise: Promise<GraphData>;\n if (bootstrapTool) {\n log.info('background build: upstream bootstrap tool available', { tool: bootstrapTool, workspace: workspacePath });\n scanPromise = invokeUpstream(bootstrapTool, { path: workspacePath })\n .then(() => fetchMergedGraph(invokeUpstream, repoId, 'both'))\n .then(raw => slimGraphData(raw, deps.config.maxNodes));\n } else {\n scanPromise = fetchMergedGraph(invokeUpstream, repoId, 'both')\n .then(raw => slimGraphData(raw, deps.config.maxNodes));\n }\n\n deps.setScanInFlight(workspacePath, scanPromise);\n\n scanPromise.then(data => {\n if (deps.isDestroyed()) return;\n deps.setLastGraphData(data);\n deps.getScanCache().set(workspacePath, { data, timestamp: Date.now() });\n // P0-6: key snapshots by structural fingerprint, never by timestamp. The old\n // `${nodeCount}:${edgeCount}:${timestamp}` hash changed on every scan, so the\n // idx_snapshots_repo_hash UNIQUE constraint could never fire and pruneSnapshots was the only\n // bound on table growth. An unchanged graph now short-circuits the write entirely.\n const fingerprint = computeGraphFingerprint(data);\n const graphRepo = deps.graphRepo;\n if (graphRepo?.isReady()) {\n try {\n const previous = graphRepo.getLatestSnapshot(repoId);\n // 先把基线规模取出来(`previous` 为 null 时留 null)。这样下面的日志与判定都不必\n // 对可空对象做属性访问,也就不需要非空断言。\n const previousNodes = previous?.nodeCount ?? null;\n const previousEdges = previous?.edgeCount ?? null;\n if (previous?.snapshotHash !== fingerprint) {\n // P0-7(cycle-110):写入前的规模维度守卫。阈值/规模下限全部来自\n // `GRAPH_DELTA_SHRINK`(与 client store、host incrementalUpdate 共用同一组数字),\n // 理由与边界写在 `src/host/compat/snapshotShrinkGuard.ts` 的模块头。\n //\n // **关键约束(\"快照不决定永远\")**:拒绝写入**只**影响落盘。`setLastGraphData()`\n // 已经在上面执行、`ctx.emit()` 在下面照常发出——内存里的图、扫描缓存、SSE/事件\n // 观众全都拿到这次扫描的结果。把这里的 `if` 写成 `return` 就会让一次守卫命中\n // 连整轮构建一起掐断,那正是本仓反复记录的\"快照决定永远\"缺陷形态。\n const decision = evaluateSnapshotShrink(previous, {\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n }, { minRetainRatio: GRAPH_DELTA_SHRINK.MIN_RETAIN_RATIO });\n\n // 先读\"上一轮是否已经拒过同一个指纹\",再决定要不要登记——顺序反了会让第一次\n // 拒绝立刻看见自己刚登记的记录,从而把两次确认退化成一次(写入照旧发生)。\n const reproduced = wasRefusedBefore(repoId, fingerprint);\n const writeAllowed = decision.allowed || reproduced;\n\n if (!decision.allowed) {\n if (reproduced) {\n // 两次确认:连续两轮扫出**完全相同**的较小图 —— 缩水是真实改动(用户确实删了代码),\n // 不是一次半读。放行写入,否则基线会被永久锁死在一张过时的图上。\n log.warn('snapshot write proceeding: the shrunken graph was reproduced identically', {\n repoId, fingerprint, nodeRatio: decision.nodeRatio, edgeRatio: decision.edgeRatio,\n previousNodes, previousEdges,\n nextNodes: data.metadata.nodeCount, nextEdges: data.metadata.edgeCount,\n });\n } else {\n rememberRefusedSnapshot(repoId, fingerprint);\n log.warn('snapshot write refused: graph would shrink beyond the retain ratio', {\n repoId, fingerprint, reason: decision.reason,\n nodeRatio: decision.nodeRatio, edgeRatio: decision.edgeRatio,\n previousNodes, previousEdges,\n nextNodes: data.metadata.nodeCount, nextEdges: data.metadata.edgeCount,\n minRetainRatio: GRAPH_DELTA_SHRINK.MIN_RETAIN_RATIO,\n });\n }\n }\n\n if (writeAllowed) {\n const serialized = Buffer.from(JSON.stringify({ nodes: data.nodes, edges: data.edges, metadata: data.metadata }), 'utf-8');\n graphRepo.saveSnapshot(repoId, fingerprint, data.metadata.nodeCount, data.metadata.edgeCount, serialized);\n clearRefusedSnapshot(repoId);\n }\n } else {\n clearRefusedSnapshot(repoId);\n log.debug('snapshot unchanged, skipping write', { repoId, fingerprint });\n }\n } catch (e) { log.warn('snapshot save failed', e); }\n }\n const { ctx } = deps;\n ctx.emit('codegraph-visualizer/graph/updated', {\n repoId: RepoId(repoId),\n nodeCount: data.metadata.nodeCount,\n edgeCount: data.metadata.edgeCount,\n timestamp: data.metadata.timestamp,\n fingerprint,\n });\n ctx.emit('codegraph-visualizer/graph/data', {\n repoId: RepoId(repoId),\n nodes: data.nodes,\n edges: data.edges,\n timestamp: data.metadata.timestamp,\n fingerprint,\n });\n log.info('background build completed', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount, fingerprint });\n }).catch(e => {\n log.error('background build failed', e);\n }).finally(() => {\n deps.setScanInFlight(workspacePath, null);\n });\n}","/**\r\n * 能力位(capability bits)的**单一来源**:宿主 `/status`、`/health` 与客户端 store / 空态\r\n * 显示都必须经过这里,不允许任何一侧自己拼一遍字段或自己判一遍健康。\r\n *\r\n * ## 为什么必须拆成两个位(docs/需求分析.md §61.10,cycle-110 登记 → cycle-111 修)\r\n *\r\n * 旧实现只有一个 `builtin` 位,并在 `src/host/prerequisites.ts` 的两处分支里**硬编码 `true`**。\r\n * 它同时被用来回答两个**不同**的问题:\r\n *\r\n * 1. 「内置 AST 分析(`/ast`、`/outline`、`/parse`)能不能用?」\r\n * 2. 「一个上游插件都没装,也能产出一张图吗?」\r\n *\r\n * 第 1 问在 TreeSitter 真的能用时成立;第 2 问**从来不成立** —— 内置的图产出通道只有\r\n * manifest 依赖图(`/dependencies`,`DependencyAdapter` 的三个纯函数解析器),而\r\n * `fetchMergedGraph` 的 `auto` 分支在外部数据源全空时**只记日志、不产节点**\r\n * (`src/tools.ts:132-134`)。两件事被一个恒真位压在一起,后果是可观测的:\r\n *\r\n * · `/health` 的 `healthy = codegraph || lens || builtin` 恒真,「degraded」永不出现\r\n * (`src/host/routes/statusRoutes.ts:42` 修前);\r\n * · 客户端空态无条件显示「降级模式 — 使用内置 TreeSitter 和依赖分析」\r\n * (`src/client/components/EmptyState.tsx:58` 修前),而它承诺的那张图并不存在。\r\n *\r\n * 拆分后:\r\n * · `astSource` —— **AST 分析通道**:由 `TreeSitterAdapter` 的真实初始化状态决定\r\n * (见 `src/host/prerequisites.ts` 的探针登记);\r\n * · `graphSource` —— **图数据通道**:由内置 manifest 依赖图解析器的**自检**决定\r\n * (解析一段固定 JSON,能产出节点才算这条通道存在)。\r\n *\r\n * `/health` 的判定只看「有没有**图**数据通道」:`codegraph || lens || graphSource`。\r\n * `astSource` **单独上报、不参与判定** —— AST 可用不代表能画图,这正是旧实现的错误等式。\r\n *\r\n * ## 能力位回答的是「通道存在」,不是「本工作区有数据」\r\n *\r\n * 「`/dependencies` 这次真的返回了节点吗」取决于该工作区有没有 `package.json` / `Cargo.toml` /\r\n * `go.mod`,那属于**请求期**事实,随工作区、随磁盘变化。把它塞进能力位会让 `/status`\r\n * 变成一个按请求漂移的值,而客户端把它当作「通道开关」缓存在 store 里 —— 那种设计早就被本仓\r\n * 反复记为缺陷(能力快照会放大缺陷)。能力位只说「这条分析通道在这台宿主上存在」。\r\n *\r\n * ## `builtin` 别名(cycle-111 引入 → cycle-118 移除)\r\n *\r\n * 历史背景:拆位(§61.10)时已安装客户端读的是 `builtin`(`builtin ?? true` 兜底),\r\n * 直接删字段会让那些客户端拿到 `undefined` 再按 `true` 兜底——等于没修但多一次崩溃面。\r\n * 因此别名「`builtin === astSource`」保留了一个兼容窗口(原定 cycle-112,实际 cycle-118)。\r\n *\r\n * **cycle-118 移除判据已满足**(`docs/agents/09` §9.4):兼容窗口经过 6 个循环 +\r\n * 真机多轮 fresh load 验证(cycle-112…117 的 J0 均为新 bundle,无旧缓存客户端存活证据);\r\n * 客户端消费方(`EmptyState` / `hostRequests` / store)自 cycle-111 起已全部改读\r\n * `astSource` / `graphSource`。移除方向的安全性:旧客户端拿 `undefined` 会走它自己的\r\n * `?? true` 兜底——症状回到「多显示一次降级提示」之前的旧世界,是**少一次提示**而不是\r\n * 崩溃;真机旧 bundle 已无存活证据,接受该残余。\r\n *\r\n * 注意:**归一化的输入容错不保留 `builtin` 回读**。`graphSource` 缺省方向是 `false`\r\n * (「不知道」≠「有」),同一诚实原则适用于 `astSource`:旧宿主载荷里只认 `astSource`,\r\n * 不再从 `builtin` 推导——两个来源迟早分叉(本仓反复的教训)。\r\n */\r\n\r\n/** 能力位。字段全部必填:任何一侧省略一个位都会在编译期暴露,而不是在运行时变成 `undefined`。 */\r\nexport interface PrerequisiteStatus {\r\n /** 上游 `dsh-codegraph` 的工具面是否存在(`CODEGRAPH_TOOL_EVIDENCE`)。 */\r\n codegraph: boolean;\r\n /** 上游 `dsh-tool-lens` 的工具面是否存在(`LENS_TOOL_EVIDENCE`)。 */\r\n lens: boolean;\r\n /** 内置 AST 分析通道是否可用(TreeSitter 解析器已初始化)。 */\r\n astSource: boolean;\r\n /** 内置图数据通道是否存在(manifest 依赖图解析器自检通过)。 */\r\n graphSource: boolean;\r\n}\r\n\r\n/** 线上形状(旧宿主可能缺 `astSource` / `graphSource`,所以解析入口必须宽容)。 */\r\ntype WireStatus = Partial<Record<keyof PrerequisiteStatus, unknown>> & Record<string, unknown>;\r\n\r\n/**\r\n * **输入**形状:任何可能来自旧宿主、旧调用点、mock 的载荷。\r\n *\r\n * 为什么输入与状态用两个类型而不是一个:状态必须是「已归一化的真相」(每个位都有确定值),\r\n * 否则每个消费方都要自己再兜一次 `?? false`,而它们迟早会兜成不同的方向 —— 这正是\r\n * `builtin ?? true` 那类缺陷的来源。输入则必须宽容(旧宿主没有新字段、测试只关心其中两位),\r\n * 宽容只允许存在于**边界**,归一化之后一律严格。\r\n */\r\nexport interface PrerequisiteStatusInput {\r\n codegraph?: boolean | undefined;\r\n lens?: boolean | undefined;\r\n astSource?: boolean | undefined;\r\n graphSource?: boolean | undefined;\r\n}\r\n\r\nfunction asBoolean(value: unknown): boolean | undefined {\r\n return typeof value === 'boolean' ? value : undefined;\r\n}\r\n\r\n/**\r\n * 把一份线上载荷归一成 `PrerequisiteStatus`。\r\n *\r\n * 两个方向都要能读,否则总有一个方向会崩:\r\n * · 新宿主(四个位齐全)⇒ 原样采纳 `astSource` / `graphSource`;\r\n * · 缺字段 / 类型不对(手写响应、代理截断、cycle-118 前只发 `builtin` 的旧宿主)⇒ `false`。\r\n *\r\n * `graphSource` 的缺省方向是 `false` 而不是 `true`:**不知道**不等于**有**。反过来兜底会让\r\n * 「降级模式」在根本没有图通道的宿主上重新出现,而那正是本轮要修的假叙事。\r\n * `astSource` 同理(cycle-118 起不再从 `builtin` 回读推导——见模块头)。\r\n */\r\nexport function normalizePrerequisiteStatus(raw: unknown): PrerequisiteStatus {\r\n const wire: WireStatus = (raw && typeof raw === 'object' ? raw : {}) as WireStatus;\r\n return {\r\n codegraph: asBoolean(wire.codegraph) ?? false,\r\n lens: asBoolean(wire.lens) ?? false,\r\n astSource: asBoolean(wire.astSource) ?? false,\r\n graphSource: asBoolean(wire.graphSource) ?? false,\r\n };\r\n}\r\n\r\n/**\r\n * 服务是否「有图可看」。**唯一**的健康判据,宿主 `/health` 与任何将来需要它的调用方都走这里。\r\n *\r\n * `astSource` 刻意不在其中:AST 分析可用 ≠ 画得出图(旧的 `healthy = codegraph || lens || builtin`\r\n * 恒真就是因为把这两件事当成了一件)。`hasGraphData`(内存里当前有没有图)同样不在其中 ——\r\n * 它是「这一次的结果」,不是「这条通道存在」,混进来会让健康检查随请求漂移。\r\n */\r\nexport function isGraphServiceHealthy(\r\n status: Pick<PrerequisiteStatus, 'codegraph' | 'lens' | 'graphSource'>,\r\n): boolean {\r\n return status.codegraph || status.lens || status.graphSource;\r\n}\r\n\r\n/** 从 `ctx` 之外的地方(测试、日志、事件)构造一份全 false 的能力位。 */\r\nexport function emptyPrerequisiteStatus(): PrerequisiteStatus {\r\n return { codegraph: false, lens: false, astSource: false, graphSource: false };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 诊断四态 + 问题码分类学(cycle-146 T146-2,对齐 dsh-ops diagnose 模式)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * 诊断四态。**状态机**语义(不是布尔):\r\n *\r\n * - `ready` —— 至少一条图数据通道在场且至少一条 AST 通道在场,全功能;\r\n * - `degraded` —— 有图可看但 AST 分析(outline/ast/children 符号树)缺席,核心旅程可用;\r\n * - `disabled` —— 没有任何图数据通道(codegraph/lens/graphSource 全 false),\r\n * 插件加载了但本宿主画不出图——用户需要装上游插件或检查工具面;\r\n * - `not-selected`—— 预留:会话未绑定工作区时的「无从谈起」态(dsh-ops 的语义),\r\n * 当前 `checkPrerequisites` 不产出它,由调用方在 ctx 提取失败时映射。\r\n *\r\n * 为什么不是复用 `/health` 的两态(healthy/degraded):两态把「装没装上游」和「内置\r\n * 通道自检」压成一个布尔,管理面板无法给出**分状态的修复指引**(装哪个插件、为什么\r\n * AST 缺席)。四态 + 稳定 code 是给前端按 code 渲染指引的数据面。\r\n */\r\nexport type PrerequisiteDiagnosisState = 'ready' | 'degraded' | 'disabled' | 'not-selected';\r\n\r\n/** 稳定问题码。**对外契约**:客户端/管理面板按 code 匹配渲染修复指引,改名即破坏性变更。 */\r\nexport type PrerequisiteIssueCode =\r\n | 'CODEGRAPH_UPSTREAM_ABSENT'\r\n | 'LENS_UPSTREAM_ABSENT'\r\n | 'AST_SOURCE_UNAVAILABLE'\r\n | 'GRAPH_SOURCE_UNAVAILABLE'\r\n | 'ALL_GRAPH_CHANNELS_ABSENT';\r\n\r\n/** 一条诊断问题:稳定 code + 面向用户的解释(i18n 由客户端按 code 渲染)。 */\r\nexport interface PrerequisiteIssue {\r\n code: PrerequisiteIssueCode;\r\n /** 机器可读的问题通道(哪个能力位引发的)。 */\r\n channel: 'codegraph' | 'lens' | 'astSource' | 'graphSource';\r\n}\r\n\r\n/** 诊断结果:四态 + 问题码列表 + 就绪通道清单。 */\r\nexport interface PrerequisiteDiagnosis {\r\n state: PrerequisiteDiagnosisState;\r\n issues: PrerequisiteIssue[];\r\n /** 就绪的图数据通道(管理面板「数据来源」徽章的数据面)。 */\r\n readyChannels: Array<'codegraph' | 'lens' | 'graphSource'>;\r\n /** AST 通道是否就绪(与 readyChannels 正交,见能力位拆分注释)。 */\r\n astReady: boolean;\r\n}\r\n\r\n/** 四态分类的**唯一**实现:宿主 /status、/health、客户端空态、管理面板都必须走这里。 */\r\nexport function diagnosePrerequisiteStatus(status: PrerequisiteStatus): PrerequisiteDiagnosis {\r\n const readyChannels: PrerequisiteDiagnosis['readyChannels'] = [];\r\n if (status.codegraph) readyChannels.push('codegraph');\r\n if (status.lens) readyChannels.push('lens');\r\n if (status.graphSource) readyChannels.push('graphSource');\r\n\r\n const issues: PrerequisiteIssue[] = [];\r\n if (!status.codegraph) issues.push({ code: 'CODEGRAPH_UPSTREAM_ABSENT', channel: 'codegraph' });\r\n if (!status.lens) issues.push({ code: 'LENS_UPSTREAM_ABSENT', channel: 'lens' });\r\n if (!status.astSource) issues.push({ code: 'AST_SOURCE_UNAVAILABLE', channel: 'astSource' });\r\n if (!status.graphSource) issues.push({ code: 'GRAPH_SOURCE_UNAVAILABLE', channel: 'graphSource' });\r\n\r\n // 有图可看 = readyChannels 非空;AST 缺席把 ready 降为 degraded。\r\n const hasGraph = readyChannels.length > 0;\r\n const state: PrerequisiteDiagnosisState =\r\n !hasGraph ? 'disabled'\r\n : !status.astSource ? 'degraded'\r\n : 'ready';\r\n\r\n if (!hasGraph) {\r\n // 问题码按确定性顺序插到最前:面板指引的第一行永远是「为什么画不出图」。\r\n issues.unshift({ code: 'ALL_GRAPH_CHANNELS_ABSENT', channel: 'graphSource' });\r\n }\r\n\r\n return { state, issues, readyChannels, astReady: status.astSource };\r\n}\r\n","/**\r\n * 业务注册-生产-消费矩阵(cycle-116 可观测性主轴)。\r\n *\r\n * 本模块是**唯一事实源**:四面(tools / routes / entries / skill)的每一项业务都有一行,\r\n * 标明它的注册点(宿主 API 调用)、生产面(数据从哪来)、消费面(谁在读)、守卫测试(谁保证它不静默失效)。\r\n *\r\n * 设计约束:\r\n * - 纯静态数据 + 可选的运行时探针(`probe()`),无副作用、无 React、无网络请求——\r\n * 同一份清单同时服务宿主路由(/api/v1/codegraph/registry)、dev-server 与文档生成。\r\n * - 每一项的 `guardTest` 指向**真实存在的测试文件**;清单与测试不同步时由\r\n * `registryContract.test.ts` 抓住(守卫的守卫)。\r\n * - 「断链」判定是声明式的:`consumers.length === 0` 即断链,看板直接标红。\r\n */\r\n\r\nexport interface RegistryEntry {\r\n /** 业务 id(全局唯一,跨四面不重复)。 */\r\n id: string;\r\n /** 面:tools=LLM 工具 / routes=HTTP 路由 / entries=UI 入口 / skill=技能贡献。 */\r\n face: 'tools' | 'routes' | 'entries' | 'skill';\r\n /** 注册点:宿主 API 调用的 file:line(重构后会漂移,契约测试按 id 对齐)。 */\r\n registeredAt: string;\r\n /** 生产面:数据/行为的来源描述。 */\r\n produces: string;\r\n /** 消费面:谁在读/调。空数组 = 断链(看板标红)。 */\r\n consumers: string[];\r\n /** 守卫测试文件(不含路径,契约测试核对存在性)。 */\r\n guardTest?: string;\r\n /** 生命周期状态(五态):registered → producing → consuming → degraded → disposed。 */\r\n lifecycle: 'registered' | 'producing' | 'consuming' | 'degraded' | 'disposed';\r\n}\r\n\r\n/** LLM 工具(14 个)——factory 返回与 entry 注册的集合相等性由 tools.test.ts 守卫。 */\r\nexport const TOOL_ENTRIES: RegistryEntry[] = [\r\n { id: 'graph_status', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '能力位 + 工作区索引状态', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_data', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '合并图数据(codegraph+lens,增量 delta)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_symbol', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '节点符号详情 + AST 摘要', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_impact', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '变更影响面(三级扩散)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_path_find', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '两节点间路径', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_path_enumerate', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '路径枚举(截断语义:序/top-k-weight)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_cycles', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '环检测(Booth 规范化)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_health', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '架构健康评分', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_rules_check', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '规则引擎违规清单', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_focus', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '邻域聚焦子图', consumers: ['LLM 会话', 'tests/unit/tools.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_query', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '关键词图检索(璇玑图 top-k)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts', 'tests/unit/keywordQueryApi.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_integrity', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '璇玑图白盒体检(变更日志水位 / 逐规则索引计数 / 休眠态披露)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts', 'tests/unit/xuanjiAdapter.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n // cycle-134:路径模式 ρ(边类型**序列**正则)。这一条曾经漏登记——真机 `/status`\r\n // 的 registry 摘要在注册了 13 个工具后仍报 `byFace.tools = 12`,而 brokenLinks 为空,\r\n // 也就是说「少登记一个」在业务注册矩阵里是**静默的**(不产生断链,只让计数说谎)。\r\n // 这正是本仓「注册-生产-消费」三面可观测性要覆盖的形态:注册了但矩阵不知道,\r\n // 运维与守卫都会以为它不存在。\r\n { id: 'graph_path_pattern', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '路径模式 ρ 枚举(边类型序列正则 → PathRule → enumerate)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts', 'tests/unit/patternEnumerationAdapter.test.ts'], guardTest: 'tools.test.ts', lifecycle: 'consuming' },\r\n // cycle-135:多维视图切片(研究方案 §4 的 $\\mathcal{T}$ / $\\Pi$ 维 + §7.1 统一接口)。\r\n // 一个入口收敛四个正交视图:structural / temporal(commit 切片 + 边挥发度)/\r\n // ownership(blame 作者 + bus factor)/ domain(Louvain 社区 → 领域标签)。\r\n { id: 'graph_slice', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: '按视图×粒度的图切片 + 视图专属标注(时序/作者/领域)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts', 'tests/unit/graphSlice.test.ts'], guardTest: 'graphSlice.test.ts', lifecycle: 'consuming' },\r\n { id: 'graph_views', face: 'tools', registeredAt: 'src/index.ts (ctx.tools.register)', produces: 'Π 视图划分函数:每个节点/边属于哪些正交视图(结构恒属,时序/所有权/领域按事实叠加)', consumers: ['LLM 会话', 'tests/unit/tools.test.ts', 'tests/unit/views.test.ts'], guardTest: 'views.test.ts', lifecycle: 'consuming' },\r\n];\r\n\r\n/** HTTP 路由(19 条)——tree-sitter/health 的消费面是测试与 E2E(外部消费)。 */\r\nexport const ROUTE_ENTRIES: RegistryEntry[] = [\r\n { id: '/data', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:65', produces: '合并图数据 JSON', consumers: ['client bootstrap/dataChannels', 'graph_data tool'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/bootstrap', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:107', produces: '首屏引导数据', consumers: ['client bootstrap'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/children', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:196', produces: '展开子图(目录/文件/符号)', consumers: ['client expandNode'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/files', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:271', produces: '文件清单', consumers: ['client file-scope'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/symbols', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:332', produces: '符号表', consumers: ['client symbol panel', 'graph_symbol tool'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/edges', face: 'routes', registeredAt: 'src/host/routes/dataRoutes.ts:442', produces: '边集(聚合分页)', consumers: ['client edge panel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/dependencies', face: 'routes', registeredAt: 'src/host/routes/analysisRoutes.ts:39', produces: '依赖关系(多代兼容)', consumers: ['client dependencies view'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/churn', face: 'routes', registeredAt: 'src/host/routes/analysisRoutes.ts:132', produces: 'git 变更频率', consumers: ['client hotspot panel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/diff', face: 'routes', registeredAt: 'src/host/routes/analysisRoutes.ts:201', produces: '版本对比', consumers: ['client diff panel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/ast', face: 'routes', registeredAt: 'src/host/routes/analysisRoutes.ts:281', produces: '符号 AST(分页+预算)', consumers: ['client AST panel', 'graph_symbol tool'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/manage/delete', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:262', produces: '删除原生索引', consumers: ['client GraphManagePanel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/manage/metrics', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:369', produces: '图指标(密度/聚类/Brandes 介数/PageRank + 连通性/关节点/直径/模块度/圈复杂度/k-core)', consumers: ['client GraphManagePanel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n // cycle-141:/manage/update 曾只注册不登记——正是本矩阵要抓住的「注册了但矩阵不知道」\r\n // 形态(cycle-134 真机 /status 报 byFace.tools=12 而实际 13 的同族缺陷)。\r\n { id: '/manage/update', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:519', produces: '增量更新(codegraph_sync → 重扫)', consumers: ['client GraphManagePanel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n // cycle-141 workstream E:Π 视图划分的用户侧出口——「每个节点/边属于哪些正交视图」。\r\n { id: '/views', face: 'routes', registeredAt: 'src/host/routes/viewsRoute.ts', produces: 'Π 视图划分(结构/时序/所有权/领域 + 节点视图矩阵)', consumers: ['client ViewsPanel', 'graph_views tool'], guardTest: 'viewsRoute.test.ts', lifecycle: 'consuming' },\r\n { id: '/tree-sitter', face: 'routes', registeredAt: 'src/host/routes/analysisRoutes.ts:320', produces: 'tree-sitter 解析对照', consumers: ['tests/unit/pathParityAndRevealContract.test.ts(外部消费)'], guardTest: 'pathParityAndRevealContract.test.ts', lifecycle: 'consuming' },\r\n { id: '/status', face: 'routes', registeredAt: 'src/host/routes/statusRoutes.ts:10', produces: '能力位 + 索引状态', consumers: ['client 空态判定', '本 registry 路由'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/health', face: 'routes', registeredAt: 'src/host/routes/statusRoutes.ts:37', produces: '健康度(503 语义)', consumers: ['E2E diff-and-integration.spec', 'dev-server.mjs(外部消费)'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/workspace', face: 'routes', registeredAt: 'src/host/routes/statusRoutes.ts:83', produces: '工作区清单', consumers: ['client workspace selector'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/scan', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:17', produces: '工作区扫描(建索引)', consumers: ['client 扫描按钮'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/init', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:99', produces: '索引初始化', consumers: ['client init'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/watch', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:168', produces: '文件监听开关', consumers: ['client watcher'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/events', face: 'routes', registeredAt: 'src/host/routes/controlRoutes.ts:194', produces: 'SSE 增量事件流', consumers: ['client SSE channel'], guardTest: 'routes.test.ts', lifecycle: 'consuming' },\r\n { id: '/query', face: 'routes', registeredAt: 'src/host/routes/queryRoute.ts:93', produces: '关键词检索(五维 etag 键)', consumers: ['client keyword panel', 'graph_query tool'], guardTest: 'xuanjiIntegrationContract.test.ts', lifecycle: 'consuming' },\r\n { id: '/worker', face: 'routes', registeredAt: 'src/host/routes/workerRoutes.ts:69', produces: '布局 worker 脚本(fcose/dagre 布局计算,IIFE 静态资源)', consumers: ['client LayoutController', 'tests/unit/LayoutController.test.ts'], guardTest: 'LayoutController.test.ts', lifecycle: 'consuming' },\r\n { id: '/paths', face: 'routes', registeredAt: 'src/host/routes/pathsRoute.ts:56', produces: '路径枚举(璇玑图路径规则读法的用户侧出口,所见即所查)', consumers: ['client PathBar(下一轮接线)', 'graph_path_enumerate tool(同算法)'], guardTest: 'pathsRoute.test.ts', lifecycle: 'consuming' },\r\n];\r\n\r\n/** UI 入口席位(4 通道优先级链)。状态由 topTabObservability 写 DOM 标记。 */\r\nexport const ENTRY_ITEMS: RegistryEntry[] = [\r\n { id: 'conversation.view', face: 'entries', registeredAt: 'src/client/integrations/ConversationViewIntegration.ts', produces: '主会话顶部「代码图谱」标签', consumers: ['会话面板用户'], guardTest: 'topTabRegistration.test.tsx', lifecycle: 'consuming' },\r\n { id: 'sidebar-right-tab', face: 'entries', registeredAt: 'src/client/integrations/SidebarRightTabsIntegration.ts', produces: '右侧栏 tab(回退通道)', consumers: ['右侧栏用户'], guardTest: 'SidebarRightTabsIntegration.test.ts', lifecycle: 'consuming' },\r\n { id: 'better-sidebar', face: 'entries', registeredAt: 'src/client/integrations/BetterSidebarIntegration.ts', produces: 'dsh-better-sidebar tab(回退通道)', consumers: ['better-sidebar 用户'], guardTest: 'BetterSidebarIntegration.test.ts', lifecycle: 'consuming' },\r\n { id: 'floating-panel', face: 'entries', registeredAt: 'src/client/integrations/FloatingPanelIntegration.ts', produces: '浮动面板(最后回退)', consumers: ['浮动面板用户'], guardTest: 'FloatingPanelIntegration.test.ts', lifecycle: 'consuming' },\r\n];\r\n\r\n/** 技能贡献(runtime skill,/commands 面板 + 模型侧) */\r\nexport const SKILL_ENTRIES: RegistryEntry[] = [\r\n { id: 'graph-skill', face: 'skill', registeredAt: 'src/host/skills.ts (contributeGraphSkill)', produces: '图检索技能说明(/ 面板与模型提示)', consumers: ['LLM 会话', '用户 / 面板'], guardTest: 'hostSkillContribution.test.ts', lifecycle: 'consuming' },\r\n];\r\n\r\n/** 全量清单(四面合一)。 */\r\nexport function allEntries(): RegistryEntry[] {\r\n return [...TOOL_ENTRIES, ...ROUTE_ENTRIES, ...ENTRY_ITEMS, ...SKILL_ENTRIES];\r\n}\r\n\r\n/** 断链项(consumers 为空——当前应为 0,非 0 即看板红)。 */\r\nexport function brokenLinks(): RegistryEntry[] {\r\n return allEntries().filter((e) => e.consumers.length === 0);\r\n}\r\n\r\n/** 看板摘要(宿主路由与 dev-server 共用的返回形状)。 */\r\nexport function registrySummary() {\r\n const entries = allEntries();\r\n const byFace = new Map<string, number>();\r\n for (const e of entries) byFace.set(e.face, (byFace.get(e.face) ?? 0) + 1);\r\n return {\r\n version: 1,\r\n generatedAt: Date.now(),\r\n total: entries.length,\r\n byFace: Object.fromEntries(byFace),\r\n brokenLinks: brokenLinks().map((e) => e.id),\r\n lifecycleCounts: {\r\n registered: entries.filter((e) => e.lifecycle === 'registered').length,\r\n producing: entries.filter((e) => e.lifecycle === 'producing').length,\r\n consuming: entries.filter((e) => e.lifecycle === 'consuming').length,\r\n degraded: entries.filter((e) => e.lifecycle === 'degraded').length,\r\n disposed: entries.filter((e) => e.lifecycle === 'disposed').length,\r\n },\r\n entries,\r\n };\r\n}\r\n\r\n\r\n/** 宿主运行时依赖的最小形状(避免拉入完整 Deps 类型造成循环依赖)。 */\r\nexport interface RegistryProbeDeps {\r\n getLastGraphData: () => unknown | null;\r\n isWatcherEnabled?: () => boolean;\r\n apiCompat?: unknown;\r\n featureFlags?: { readonly [key: string]: unknown } | object | null;\r\n}\r\n\r\n/** 单业务运行时探针结果。 */\r\nexport interface LiveHint {\r\n id: string;\r\n face: RegistryEntry['face'];\r\n /** 实测生命周期:由宿主状态推导,与声明式 lifecycle 独立。 */\r\n live: 'live' | 'idle' | 'degraded' | 'unknown';\r\n /** 人类可读的实测依据。 */\r\n basis: string;\r\n}\r\n\r\n/**\r\n * 运行时探针(cycle-117:lifecycle 从声明式转实测式)。\r\n *\r\n * 声明式 `lifecycle` 回答「设计上该业务处于什么阶段」;本函数回答「此刻宿主进程里\r\n * 它到底活不活」。两者可能合法地不同:`/scan` 声明 consuming,但从未扫描时实测 idle。\r\n *\r\n * 探测原理(全部零副作用):\r\n * - routes:图数据有无(多数数据/分析路由的工作前提)+ watcher 开关(/watch、/events);\r\n * - tools:与 routes 同源(工具经 invoke 调路由 handler),图数据在即 live;\r\n * - entries:宿主侧不可测(DOM 在客户端)→ `unknown` + basis 说明,客户端看板用 DOM 标记补齐;\r\n * - skill:featureFlags 里 commandUi 类位存在即 live。\r\n */\r\nexport function probeRegistry(deps: RegistryProbeDeps): LiveHint[] {\r\n const hasGraph = deps.getLastGraphData() !== null;\r\n const watcherOn = deps.isWatcherEnabled?.() ?? false;\r\n const flags = (deps.featureFlags ?? {}) as Record<string, unknown>;\r\n\r\n const routeHint = (id: string, basis: string, live: LiveHint['live']): LiveHint => ({ id, face: 'routes', live, basis });\r\n\r\n const routeLive = (id: string): LiveHint => {\r\n if (id === '/watch' || id === '/events') {\r\n return routeHint(id, watcherOn ? 'watcher 已启用' : 'watcher 未启用(路由注册但无事件流)', watcherOn ? 'live' : 'idle');\r\n }\r\n if (id === '/status' || id === '/health' || id === '/workspace' || id === '/registry') {\r\n return routeHint(id, '状态/元路由:无图数据也响应', 'live');\r\n }\r\n return routeHint(id, hasGraph ? '图数据在内存中' : '图数据未加载(先扫描/导入)', hasGraph ? 'live' : 'idle');\r\n };\r\n\r\n return [\r\n ...allEntries().filter((e) => e.face === 'routes').map((e) => routeLive(e.id)),\r\n ...allEntries().filter((e) => e.face === 'tools').map((e): LiveHint => ({\r\n id: e.id, face: 'tools',\r\n live: hasGraph ? 'live' : 'idle',\r\n basis: hasGraph ? 'invoke 链路可用(图数据在)' : 'invoke 链路可用但数据为空',\r\n })),\r\n ...allEntries().filter((e) => e.face === 'entries').map((e): LiveHint => ({\r\n id: e.id, face: 'entries', live: 'unknown',\r\n basis: '入口状态在客户端 DOM(data-cg-top-tab / 集成状态标记),宿主侧不可测',\r\n })),\r\n ...allEntries().filter((e) => e.face === 'skill').map((e): LiveHint => ({\r\n id: e.id, face: 'skill',\r\n live: Object.keys(flags).length > 0 ? 'live' : 'unknown',\r\n basis: Object.keys(flags).length > 0 ? 'featureFlags 已解析' : 'featureFlags 为空',\r\n })),\r\n ];\r\n}\r\n\r\n/** live 探针结果计数(/status 摘要用轻量形态)。 */\r\nexport function countLive(hints: LiveHint[]): Record<string, number> {\r\n const out: Record<string, number> = { live: 0, idle: 0, degraded: 0, unknown: 0 };\r\n for (const h of hints) out[h.live] = (out[h.live] ?? 0) + 1;\r\n return out;\r\n}\r\n","import { findWorkspacePath, extractWorkspacePaths, checkPrerequisites } from '../prerequisites.ts';\r\nimport { sendJson } from '../HttpHelpers.ts';\r\nimport { computeGraphFingerprint } from '../../shared/graphFingerprint.ts';\r\nimport { isGraphServiceHealthy, diagnosePrerequisiteStatus } from '../../shared/prerequisiteStatus.ts';\r\nimport { withRouteMiddleware, ensureWorkspaceRootsAllowed, type RouteContext } from './shared.ts';\r\nimport { registrySummary, probeRegistry, countLive } from '../registry/businessRegistry.ts';\r\n\r\nexport function registerStatusRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/status';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, (req, res) => {\r\n const status = checkPrerequisites(ctx, deps.apiCompat);\r\n sendJson(res, 200, {\r\n codegraph: status.codegraph,\r\n lens: status.lens,\r\n // 「AST 分析通道」与「图数据通道」是两个不同的能力位(docs/需求分析.md §61.10)。\r\n // 客户端据此决定空态是否显示降级提示,宿主据此决定 `/health` 的 healthy。\r\n astSource: status.astSource,\r\n graphSource: status.graphSource,\r\n // Diagnostics only: the generation-dated capability record resolved at boot. Runtime\r\n // behaviour is decided by which upstream tools are actually available, not by these flags.\r\n compat: deps.featureFlags ?? null,\r\n // cycle-116 可观测性:注册-生产-消费矩阵摘要(四面计数 + 断链清单)。\r\n // additive 字段:旧客户端不读无影响;运维/测试用它白盒检查业务连通性。\r\n registry: { ...registrySummary(), liveCounts: countLive(probeRegistry(deps)) },\r\n // cycle-146 T146-2:诊断四态 + 稳定问题码(ready/degraded/disabled/not-selected)。\r\n // additive 字段:管理面板按 code 渲染修复指引;旧客户端不读无影响。\r\n diagnosis: diagnosePrerequisiteStatus(status),\r\n }, req, corsConfig);\r\n }),\r\n }), 'codegraph: status route');\r\n}\r\n\r\nexport function registerHealthRoute(rc: RouteContext): void {\r\n const { deps, corsConfig } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/health';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rc.rateLimiter, (req, res) => {\r\n try {\r\n const status = checkPrerequisites(ctx, deps.apiCompat);\r\n const mem = process.memoryUsage();\r\n const lastGraph = deps.getLastGraphData();\r\n const hasGraphData = lastGraph !== null;\r\n // 修前是 `codegraph || lens || builtin`,而 `builtin` 被硬编码为 `true` ⇒ 恒 healthy,\r\n // 「degraded」这条分支从未在任何宿主上出现过。`astSource` 刻意不参与判定:\r\n // 「能做 AST 分析」不等于「能画出一张图」,把两者当成一件事正是旧实现恒真的原因。\r\n const healthy = isGraphServiceHealthy(status);\r\n sendJson(res, healthy ? 200 : 503, {\r\n status: healthy ? 'healthy' : 'degraded',\r\n codegraph: status.codegraph,\r\n lens: status.lens,\r\n // 两个内置能力位都上报(判定只用了 graphSource),这样 503 时能一眼看出\r\n // 「是 AST 有、图没有」还是「两个都没有」。\r\n astSource: status.astSource,\r\n graphSource: status.graphSource,\r\n hasGraphData,\r\n // P0-6: the current structural fingerprint, so an operator can tell whether the graph\r\n // in memory actually changed without diffing nodes/edges by hand.\r\n fingerprint: lastGraph ? computeGraphFingerprint(lastGraph) : null,\r\n uptime: process.uptime(),\r\n memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal },\r\n timestamp: Date.now(),\r\n }, req, corsConfig);\r\n } catch (e) {\r\n sendJson(res, 503, {\r\n status: 'unhealthy',\r\n error: e instanceof Error ? e.message : String(e),\r\n timestamp: Date.now(),\r\n }, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: health route');\r\n}\r\n\r\nexport function registerWorkspaceRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/workspace';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, (req, res) => {\r\n const current = findWorkspacePath(ctx);\r\n const list = extractWorkspacePaths(ctx);\r\n // Observability for docs/需求分析.md §31.7: a rejected file route used to be diagnosable only\r\n // by reading the process's memory. The allowlist is local-only (host-provided roots).\r\n const refresh = ensureWorkspaceRootsAllowed(ctx, deps);\r\n sendJson(res, 200, {\r\n path: current,\r\n list,\r\n allowedRoots: refresh.roots,\r\n rootSource: refresh.source,\r\n resolvedFrom: refresh.resolved,\r\n }, req, corsConfig);\r\n }),\r\n }), 'codegraph: workspace route');\r\n}","import type { GraphNode, GraphEdge } from '../types/index.ts';\r\nimport { NodeId, EdgeId } from '../types/index.ts';\r\nimport { mapNodeKind, mapEdgeKind, isNoisePath } from '../adapters/CodeGraphAdapter.ts';\r\nimport { inferLayerFromPath } from '../shared/layerUtils.ts';\r\n\r\nexport interface DbNodeRow {\r\n id: string;\r\n kind: string;\r\n name: string;\r\n file_path: string;\r\n start_line: number;\r\n signature: string | null;\r\n docstring: string | null;\r\n is_exported: number;\r\n}\r\n\r\nexport interface DbEdgeRow {\r\n id: number;\r\n source: string;\r\n target: string;\r\n kind: string;\r\n line: number | null;\r\n}\r\n\r\nexport function mapDbNodesToGraphNodes(\r\n dbNodes: DbNodeRow[],\r\n parentId?: string,\r\n): GraphNode[] {\r\n return dbNodes\r\n .filter((n) => n.name && n.file_path && !isNoisePath(n.file_path))\r\n .map((n) => ({\r\n id: NodeId(n.id),\r\n label: n.name,\r\n type: mapNodeKind(n.kind),\r\n filePath: n.file_path,\r\n lineNumber: n.start_line ?? 1,\r\n properties: {\r\n kind: n.kind,\r\n ...(n.signature ? { signature: n.signature } : {}),\r\n ...(n.docstring ? { docstring: n.docstring } : {}),\r\n exported: n.is_exported === 1,\r\n },\r\n ...(parentId ? { parentId: NodeId(parentId) } : {}),\r\n layer: inferLayerFromPath(n.file_path),\r\n }));\r\n}\r\n\r\nexport function mapDbEdgesToGraphEdges(dbEdges: DbEdgeRow[]): GraphEdge[] {\r\n return dbEdges.map((e) => ({\r\n id: EdgeId(String(e.id)),\r\n source: NodeId(e.source),\r\n target: NodeId(e.target),\r\n type: mapEdgeKind(e.kind),\r\n properties: e.line !== null && e.line !== undefined ? { line: e.line } : {},\r\n }));\r\n}","import { VALIDATION, BOOTSTRAP, ERROR_CODES, API, EDGES_AGGREGATION, FILE_OUTLINE } from '../../shared/constants.ts';\r\nimport { existsSync } from 'node:fs';\r\nimport type { GraphEdge, GraphNode, GraphData } from '../../types/index.ts';\r\nimport { RepoId, NodeId, EdgeId } from '../../types/index.ts';\r\nimport {\r\n resolveDbPath, readTopLevelNodesFromDb, readNodeChildrenFromDb, readFileOutlineFromDb, readSymbolASTFromDb, readEdgesFromDb, readIncidentEdgesWithFilePathsFromDb, readEdgesForNodesFromDb, countEdgesInDb, FILTERED_EDGE_KINDS, sortEdgesByKindGroup,\r\n} from '../../adapters/CodeGraphAdapter.ts';\r\nimport { mapDbNodesToGraphNodes, mapDbEdgesToGraphEdges } from '../DbMapper.ts';\r\nimport { computeGraphEtag } from '../../shared/graphFingerprint.ts';\r\nimport {\r\n etagKeyForChildren, etagKeyForOutline, etagKeyForAst, etagKeyForEdges, resolveAstBudget,\r\n} from '../../shared/graphFingerprint.ts';\r\nimport { buildCorsHeaders, sendJson, sendError } from '../HttpHelpers.ts';\r\nimport {\r\n withRouteMiddleware, resolveWorkspaceFromRequest, returnIfEtagMatch, etagGuard, etagHeaders, slimGraphData,\r\n triggerBackgroundBuild, getWorkspaceScopedGraphData, type RouteContext,\r\n} from './shared.ts';\r\n\r\ninterface GraphIndex {\r\n parentIdToChildren: Map<string, GraphNode[]>;\r\n filePathToNodeIds: Map<string, Set<string>>;\r\n nodeIdToFilePath: Map<string, string>;\r\n rootNodes: GraphNode[];\r\n}\r\n\r\nconst _indexCache = new WeakMap<GraphData, GraphIndex>();\r\n\r\nfunction getGraphIndex(data: GraphData): GraphIndex {\r\n const cached = _indexCache.get(data);\r\n if (cached) return cached;\r\n\r\n const parentIdToChildren = new Map<string, GraphNode[]>();\r\n const filePathToNodeIds = new Map<string, Set<string>>();\r\n const nodeIdToFilePath = new Map<string, string>();\r\n const rootNodes: GraphNode[] = [];\r\n\r\n for (const n of data.nodes) {\r\n if (n.parentId) {\r\n const siblings = parentIdToChildren.get(n.parentId);\r\n if (siblings) siblings.push(n);\r\n else parentIdToChildren.set(n.parentId, [n]);\r\n } else {\r\n rootNodes.push(n);\r\n }\r\n const fp = n.filePath;\r\n if (fp) {\r\n const ids = filePathToNodeIds.get(fp);\r\n if (ids) ids.add(n.id);\r\n else filePathToNodeIds.set(fp, new Set([n.id]));\r\n }\r\n nodeIdToFilePath.set(n.id, n.filePath);\r\n }\r\n\r\n for (const children of parentIdToChildren.values()) {\r\n children.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\r\n }\r\n\r\n const index: GraphIndex = { parentIdToChildren, filePathToNodeIds, nodeIdToFilePath, rootNodes };\r\n _indexCache.set(data, index);\r\n return index;\r\n}\r\n\r\n// cycle-131:索引域抽象——上游库不可读时回落原生索引(schema 兼容,读侧零改动)。\r\n// 每个按需路由(/bootstrap /children /outline /symbols /edges)的 dbPath 都经此\r\n// 解析;上游在场行为逐字节不变,原生工作区的按需路由从「内存 500 节点回落」\r\n// 升级为「全量索引直读」。\r\n//\r\n// cycle-141 第五轮:返回值带上**实际解析出的库来源**。此前 /bootstrap 的 DB 分支\r\n// 把 graphLayer 写死 'upstream'(:156),而本函数会回落 native-index——dsh-anic\r\n// 这类原生工作区被标成 upstream,来源声明撒谎(真机 3085 实测:bootstrap 返回\r\n// graphLayer=upstream 而读的是 native-index.db)。修复后调用方按返回值如实标注。\r\nconst resolveIndexDbPath = async (workspacePath: string): Promise<{ dbPath: string; layer: 'upstream' | 'native' }> => {\r\n const upstream = resolveDbPath(workspacePath);\r\n if (existsSync(upstream)) return { dbPath: upstream, layer: 'upstream' };\r\n try {\r\n const { resolveNativeIndexPath } = await import('../../adapters/nativeIndex.ts');\r\n return { dbPath: resolveNativeIndexPath(workspacePath), layer: 'native' };\r\n } catch {\r\n return { dbPath: upstream, layer: 'upstream' };\r\n }\r\n};\r\n\r\nexport function registerDataRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/data';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, (req, res) => {\r\n if (returnIfEtagMatch(req, res, corsConfig, deps, 'graph')) return;\r\n const lastGraphData = deps.getLastGraphData();\r\n if (lastGraphData) {\r\n // 校验值必须与 returnIfEtagMatch 算出的完全一致,否则客户端拿到 200 里的 etag 再发回来时永远\r\n // 匹配不上,\"增量取数\"退化成每次都全量重取。\r\n const etag = computeGraphEtag(lastGraphData.metadata.repoId, lastGraphData.metadata.timestamp, 'graph');\r\n const payload = JSON.stringify(lastGraphData);\r\n if (payload.length > API.DATA_RESPONSE_MAX_BYTES) {\r\n const slimmed = slimGraphData(lastGraphData, API.DATA_RESPONSE_SLIM_NODES);\r\n const slimPayload = JSON.stringify(slimmed);\r\n const dataHeaders = buildCorsHeaders(req, corsConfig);\r\n dataHeaders['content-type'] = 'application/json';\r\n dataHeaders['etag'] = etag;\r\n dataHeaders['x-truncated'] = '1';\r\n res.writeHead(200, dataHeaders);\r\n res.end(slimPayload);\r\n } else {\r\n const dataHeaders = buildCorsHeaders(req, corsConfig);\r\n dataHeaders['content-type'] = 'application/json';\r\n dataHeaders['etag'] = etag;\r\n res.writeHead(200, dataHeaders);\r\n res.end(payload);\r\n }\r\n } else {\r\n // 槽为空时**没有**校验值可算(`resourceEtag` 返回 null),所以这里显式传 `etagHeaders(null)`\r\n // 表示\"本响应刻意不带校验器\",而不是忘了写。全仓 `sendJson(res, 200, ...)` 一律带 etag 参数,\r\n // 这条统一形状由 `tests/unit/etagContract.test.ts` 的源码扫描兜底。\r\n sendJson(res, 200, { nodes: [], edges: [], metadata: { repoId: RepoId('empty'), timestamp: 0, nodeCount: 0, edgeCount: 0 } }, req, corsConfig, etagHeaders(null));\r\n }\r\n }),\r\n }), 'codegraph: data route');\r\n}\r\n\r\nexport function registerBootstrapRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter, invokeUpstream } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/bootstrap';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n\r\n // `/bootstrap` 没有请求参数(工作区已在 repoId 维度里),故不传请求键。\r\n const etagDecision = etagGuard(req, res, corsConfig, deps, 'bootstrap', workspacePath);\r\n if (etagDecision.handled) return;\r\n const etagHeader = etagHeaders(etagDecision.etag);\r\n\r\n if (workspacePath) {\r\n const { dbPath, layer } = await resolveIndexDbPath(workspacePath);\r\n const dbResult = readTopLevelNodesFromDb(dbPath, BOOTSTRAP.TOP_LEVEL_NODE_LIMIT);\r\n if (dbResult && dbResult.nodes.length > 0) {\r\n const topLevelNodes = mapDbNodesToGraphNodes(dbResult.nodes);\r\n const topLevelIds = new Set(topLevelNodes.map((n) => n.id));\r\n const dbEdges = readEdgesForNodesFromDb(dbPath, [...topLevelIds]);\r\n const rootEdges = mapDbEdgesToGraphEdges(dbEdges);\r\n sendJson(res, 200, {\r\n rootNodes: topLevelNodes,\r\n topLevelNodes,\r\n topLevelTotal: dbResult.total,\r\n edges: rootEdges,\r\n indexStatus: 'ready',\r\n metadata: {\r\n repoId: RepoId(workspacePath),\r\n timestamp: Date.now(),\r\n totalNodeCount: dbResult.total,\r\n totalEdgeCount: countEdgesInDb(dbPath),\r\n // cycle-131:DB 可读分支的来源声明;cycle-141 第五轮:按实际解析出的\r\n // 库来源标注(upstream=.codegraph/codegraph.db,native=native-index.db)——\r\n // 此前写死 'upstream',原生工作区的来源徽章撒谎(真机 3085 实测)。\r\n graphLayer: layer,\r\n },\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n // P0-B:内存回落必须按请求方工作区限定。host 只有一个图槽,无条件下落会把\r\n // 另一个工作区的顶层节点连同它的 repoId 一起发给本客户端,覆盖画布身份。\r\n const lastGraphData = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (lastGraphData && lastGraphData.nodes.length > 0) {\r\n const allRootNodes = getGraphIndex(lastGraphData).rootNodes;\r\n const topLevelNodes = allRootNodes.slice(0, BOOTSTRAP.TOP_LEVEL_NODE_LIMIT);\r\n const topLevelIds = new Set(topLevelNodes.map((n) => n.id));\r\n const rootEdges = lastGraphData.edges.filter(\r\n (e) => topLevelIds.has(e.source) && topLevelIds.has(e.target),\r\n );\r\n\r\n sendJson(res, 200, {\r\n rootNodes: topLevelNodes,\r\n topLevelNodes,\r\n topLevelTotal: allRootNodes.length,\r\n edges: rootEdges,\r\n indexStatus: 'ready',\r\n metadata: {\r\n repoId: lastGraphData.metadata.repoId,\r\n timestamp: lastGraphData.metadata.timestamp,\r\n totalNodeCount: lastGraphData.metadata.totalNodeCount ?? lastGraphData.nodes.length,\r\n totalEdgeCount: lastGraphData.metadata.totalEdgeCount ?? lastGraphData.edges.length,\r\n // cycle-130 · T2:图层来源透传(客户端 StatusBar 来源徽章的数据面)。\r\n ...(lastGraphData.metadata.graphLayer ? { graphLayer: lastGraphData.metadata.graphLayer } : {}),\r\n },\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n\r\n if (workspacePath) {\r\n triggerBackgroundBuild(deps, invokeUpstream, workspacePath);\r\n }\r\n\r\n sendJson(res, 200, {\r\n rootNodes: [],\r\n topLevelNodes: [],\r\n topLevelTotal: 0,\r\n edges: [],\r\n indexStatus: 'building',\r\n metadata: {\r\n repoId: RepoId(workspacePath ?? 'empty'),\r\n timestamp: Date.now(),\r\n totalNodeCount: 0,\r\n totalEdgeCount: 0,\r\n },\r\n }, req, corsConfig, etagHeader);\r\n }),\r\n }), 'codegraph: bootstrap route');\r\n}\r\n\r\nexport function registerNodeChildrenRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/children';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const nodeId = url.searchParams.get('nodeId');\r\n if (!nodeId || nodeId.length > VALIDATION.MAX_PATH_LENGTH) {\r\n sendError(res, 400, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n\r\n const cursor = url.searchParams.get('cursor') ?? null;\r\n const limit = parseInt(url.searchParams.get('limit') ?? String(BOOTSTRAP.CHILDREN_PAGE_SIZE), 10) || BOOTSTRAP.CHILDREN_PAGE_SIZE;\r\n\r\n // 先解析工作区、再校验 ETag:反序会让校验值缺少身份维度(`?workspace=` 还没读),\r\n // 客户端切到别的工作区时会因槽里还留着旧图而收到 304,静默保留错误画布。\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n\r\n // 请求键含 nodeId、cursor 与 limit:三者任一不同都会产生不同载荷,\r\n // 少一个就会让客户端拿 A 的校验值问 B 时收到假 304(P3-R18-02)。\r\n const etagKey = etagKeyForChildren(nodeId, cursor, limit);\r\n const etagDecision = etagGuard(req, res, corsConfig, deps, 'children', workspacePath, etagKey);\r\n if (etagDecision.handled) return;\r\n const etagHeader = etagHeaders(etagDecision.etag);\r\n\r\n if (workspacePath) {\r\n const { dbPath } = await resolveIndexDbPath(workspacePath);\r\n const dbResult = readNodeChildrenFromDb(dbPath, nodeId, cursor, limit);\r\n if (dbResult && dbResult.nodes.length > 0) {\r\n const page = mapDbNodesToGraphNodes(dbResult.nodes, nodeId);\r\n const childIds = page.map((n) => n.id);\r\n const dbEdges = readEdgesForNodesFromDb(dbPath, childIds);\r\n const childEdges = mapDbEdgesToGraphEdges(dbEdges);\r\n\r\n const lastNode = dbResult.nodes[dbResult.nodes.length - 1];\r\n sendJson(res, 200, {\r\n nodes: page,\r\n edges: childEdges,\r\n cursor: dbResult.nodes.length < limit || !lastNode ? null : lastNode.id,\r\n total: dbResult.total,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n const lastGraphData = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (lastGraphData && lastGraphData.nodes.length > 0) {\r\n const children = getGraphIndex(lastGraphData).parentIdToChildren.get(nodeId) ?? [];\r\n const startIdx = cursor ? Math.max(0, children.findIndex((n) => n.id > cursor)) : 0;\r\n const page = children.slice(startIdx, startIdx + limit);\r\n const childIds = new Set(page.map((n) => n.id));\r\n const childEdges = lastGraphData.edges.filter(\r\n (e) => childIds.has(e.source) && childIds.has(e.target),\r\n );\r\n\r\n const lastChild = page[page.length - 1];\r\n sendJson(res, 200, {\r\n nodes: page,\r\n edges: childEdges,\r\n cursor: page.length < limit || !lastChild ? null : lastChild.id,\r\n total: children.length,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n\r\n sendJson(res, 200, { nodes: [], edges: [], cursor: null, total: 0 }, req, corsConfig, etagHeader);\r\n }),\r\n }), 'codegraph: node children route');\r\n}\r\n\r\nexport function registerFileOutlineRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/files';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'prefix',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const match = url.pathname.match(/^\\/api\\/v1\\/codegraph\\/files\\/([^/]+)\\/outline$/);\r\n if (!match) {\r\n sendError(res, 404, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n const nodeId = decodeURIComponent(match[1] ?? '');\r\n if (!nodeId || nodeId.length > VALIDATION.MAX_PATH_LENGTH) {\r\n sendError(res, 400, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 先解析工作区、再校验 ETag(理由同 /children)。\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n\r\n // 请求键含 nodeId:`outline(fileA)` 与 `outline(fileB)` 必须算出不同校验值。\r\n const etagDecision = etagGuard(\r\n req, res, corsConfig, deps, 'outline', workspacePath, etagKeyForOutline(nodeId),\r\n );\r\n if (etagDecision.handled) return;\r\n const etagHeader = etagHeaders(etagDecision.etag);\r\n\r\n if (workspacePath) {\r\n const { dbPath } = await resolveIndexDbPath(workspacePath);\r\n const dbResult = readFileOutlineFromDb(dbPath, nodeId);\r\n if (dbResult && dbResult.nodes.length > 0) {\r\n const symbols = mapDbNodesToGraphNodes(dbResult.nodes, nodeId).slice(0, FILE_OUTLINE.MAX_SYMBOLS);\r\n const symbolIds = new Set(symbols.map((n) => n.id));\r\n const dbEdges = readEdgesForNodesFromDb(dbPath, [...symbolIds]);\r\n const symbolEdges = mapDbEdgesToGraphEdges(dbEdges);\r\n sendJson(res, 200, { symbols, edges: symbolEdges }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n const lastGraphData = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (!lastGraphData) {\r\n sendJson(res, 200, { symbols: [], edges: [] }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n\r\n const symbols = (getGraphIndex(lastGraphData).parentIdToChildren.get(nodeId) ?? []).slice(0, FILE_OUTLINE.MAX_SYMBOLS);\r\n const symbolIds = new Set(symbols.map((n) => n.id));\r\n const symbolEdges = lastGraphData.edges.filter(\r\n (e) => symbolIds.has(e.source) && symbolIds.has(e.target),\r\n );\r\n\r\n sendJson(res, 200, { symbols, edges: symbolEdges }, req, corsConfig, etagHeader);\r\n }),\r\n }), 'codegraph: file outline route');\r\n}\r\n\r\nexport function registerSymbolASTRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/symbols';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'prefix',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const match = url.pathname.match(/^\\/api\\/v1\\/codegraph\\/symbols\\/([^/]+)\\/ast$/);\r\n if (!match) {\r\n sendError(res, 404, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n const nodeId = decodeURIComponent(match[1] ?? '');\r\n if (!nodeId || nodeId.length > VALIDATION.MAX_PATH_LENGTH) {\r\n sendError(res, 400, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 先解析工作区、再校验 ETag(理由同 /children)。\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n\r\n // 单符号预算:`budget` 是每页节点上限,`cursor` 是分段加载的起点。\r\n // 两者共同实现 docs/图谱按需加载.md 的「超限时折叠为摘要 / 分段加载」:\r\n // 只截断而不给 cursor,客户端就无法取回被折叠的部分,只能永久丢失。\r\n const budget = resolveAstBudget(url.searchParams.get('budget'));\r\n const cursor = Math.max(parseInt(url.searchParams.get('cursor') ?? '0', 10) || 0, 0);\r\n\r\n // 请求键含 nodeId、cursor 与 budget:budget 直接决定这一页切多少节点,\r\n // 不同预算的响应绝不能共用校验值。\r\n const etagDecision = etagGuard(\r\n req, res, corsConfig, deps, 'ast', workspacePath, etagKeyForAst(nodeId, cursor, budget),\r\n );\r\n if (etagDecision.handled) return;\r\n const etagHeader = etagHeaders(etagDecision.etag);\r\n\r\n if (workspacePath) {\r\n const { dbPath } = await resolveIndexDbPath(workspacePath);\r\n const dbResult = readSymbolASTFromDb(dbPath, nodeId);\r\n if (dbResult && dbResult.nodes.length > 0) {\r\n const allAstNodes = mapDbNodesToGraphNodes(dbResult.nodes, nodeId);\r\n const astNodes = allAstNodes.slice(cursor, cursor + budget);\r\n const nextCursor = cursor + astNodes.length;\r\n const hasMore = nextCursor < allAstNodes.length;\r\n const astNodeIds = new Set(astNodes.map((n) => n.id));\r\n const dbEdges = readEdgesForNodesFromDb(dbPath, [...astNodeIds]);\r\n const astEdges = mapDbEdgesToGraphEdges(dbEdges);\r\n sendJson(res, 200, { astNodes, astEdges, sourceLines: [], total: allAstNodes.length, cursor: nextCursor, hasMore }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n // P0-B:`/ast` 的 `nodeId` 是**另一个工作区**的节点 id 时,在 A 的图里查表只会得到空数组,\r\n // 客户端表现为「展开成功但一个符号都没有」。必须按请求方工作区限定回落槽。\r\n const lastGraphData = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (!lastGraphData) {\r\n sendJson(res, 200, { astNodes: [], astEdges: [], sourceLines: [], total: 0, cursor: 0, hasMore: false }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n\r\n const allAstNodes = getGraphIndex(lastGraphData).parentIdToChildren.get(nodeId) ?? [];\r\n const astNodes = allAstNodes.slice(cursor, cursor + budget);\r\n const nextCursor = cursor + astNodes.length;\r\n const hasMore = nextCursor < allAstNodes.length;\r\n const astNodeIds = new Set(astNodes.map((n) => n.id));\r\n const astEdges = lastGraphData.edges.filter(\r\n (e) => astNodeIds.has(e.source) && astNodeIds.has(e.target),\r\n );\r\n\r\n sendJson(res, 200, { astNodes, astEdges, sourceLines: [], total: allAstNodes.length, cursor: nextCursor, hasMore }, req, corsConfig, etagHeader);\r\n }),\r\n }), 'codegraph: symbol AST route');\r\n}\r\n\r\n/**\r\n * 把同一文件对之间的多条边折叠成一条合成边(`properties.aggregated === true`)。\r\n *\r\n * DB 路径与内存路径共用本函数:两条路径此前各有一套(DB 路径干脆没有),\r\n * 而聚合语义一旦分叉就会静默漂移——`total`、`hasMore`、`filePair` 三处任一不一致,\r\n * 前端「展开明细」都会拿不到正确游标。\r\n *\r\n * `filePathOf` 是注入的:DB 路径用一次 `IN` 查询得到的映射,内存路径用图索引,\r\n * 两者都不保证覆盖全部端点,因此缺失时退化为 `'?'` 而不是丢弃该边。\r\n */\r\nfunction buildFilePairAggregate(\r\n fromId: string,\r\n edges: ReadonlyArray<{ source: string; target: string }>,\r\n filePathOf: (nodeId: string) => string | undefined,\r\n): GraphEdge[] {\r\n const filePairCounts = new Map<string, number>();\r\n for (const e of edges) {\r\n const srcFile = filePathOf(e.source) ?? '?';\r\n const tgtFile = filePathOf(e.target) ?? '?';\r\n const pairKey = srcFile < tgtFile ? `${srcFile}\\0${tgtFile}` : `${tgtFile}\\0${srcFile}`;\r\n filePairCounts.set(pairKey, (filePairCounts.get(pairKey) ?? 0) + 1);\r\n }\r\n return [...filePairCounts.entries()].map(([pairKey, count]) => {\r\n const [fileA, fileB] = pairKey.split('\\0');\r\n return {\r\n id: EdgeId(`agg:${pairKey}`),\r\n source: NodeId(fromId),\r\n target: NodeId(fileA === fromId ? (fileB ?? fromId) : (fileA ?? fromId)),\r\n type: 'dependency' as const,\r\n properties: { aggregated: true, count, filePair: pairKey },\r\n };\r\n });\r\n}\r\n\r\nexport function registerEdgesRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/edges';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'prefix',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const fromId = url.searchParams.get('from') ?? '';\r\n const scope = url.searchParams.get('scope') ?? 'file';\r\n const cursor = parseInt(url.searchParams.get('cursor') ?? '0', 10);\r\n const limit = Math.min(parseInt(url.searchParams.get('limit') ?? String(EDGES_AGGREGATION.PAGE_SIZE), 10), EDGES_AGGREGATION.PAGE_SIZE);\r\n // `detail=1` 请求未聚合的原始边(前端「展开聚合边」用它拉明细)。\r\n // `aggregated=1` 是历史写法,语义相同(1 = 不要聚合),保留以免破坏既有调用方;\r\n // 两者极性都反直觉,新代码一律用 `detail`。\r\n const detailRequested = url.searchParams.get('detail') === '1'\r\n || url.searchParams.get('aggregated') === '1';\r\n // 只有文件作用域才有「跨文件边聚合」的概念:符号作用域的邻接边本来就不按文件折叠。\r\n const mayAggregate = scope !== 'symbol' && !detailRequested;\r\n\r\n if (!fromId || fromId.length > VALIDATION.MAX_PATH_LENGTH) {\r\n sendError(res, 400, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 先解析工作区、再校验 ETag(理由同 /children)。\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n\r\n // 请求键覆盖全部改变载荷的参数:fromId / scope / cursor / limit / detail。\r\n // `detail` 尤其关键——它决定这一页是聚合摘要还是原始边,两者绝不能共用校验值。\r\n // 键多算几个维度只会多回一次 200(安全方向),少算一个就会产生假 304。\r\n const etagDecision = etagGuard(\r\n req, res, corsConfig, deps, 'edges', workspacePath,\r\n etagKeyForEdges(fromId, scope, cursor, limit, detailRequested),\r\n );\r\n if (etagDecision.handled) return;\r\n const etagHeader = etagHeaders(etagDecision.etag);\r\n\r\n if (workspacePath) {\r\n const { dbPath } = await resolveIndexDbPath(workspacePath);\r\n // DB-first: when DB is readable, its answer is authoritative — empty edges means\r\n // no edges exist, NOT \"fall back to memory\". This intentionally differs from\r\n // other routes (bootstrap/children/outline/ast) which use `dbResult.nodes.length > 0`\r\n // to fall back to memory when DB has no nodes. See CLAUDE.md for rationale.\r\n if (mayAggregate) {\r\n // 聚合必须在分页之前完成:同一个文件对会被拆到多页里,逐页聚合必然算错。\r\n // 因此聚合路径一次性取回全集(含端点文件映射),再决定聚合还是切片。\r\n // cycle-139:未切片全集先分组排序——聚合摘要与翻页明细共享同一确定序\r\n //(聚合与排序都发生在分页之前,「聚合是序契约」纪律的延伸)。\r\n const full = readIncidentEdgesWithFilePathsFromDb(dbPath, fromId, scope as 'file' | 'symbol');\r\n if (full) {\r\n const allEdges = sortEdgesByKindGroup(full.edges);\r\n if (allEdges.length > EDGES_AGGREGATION.THRESHOLD) {\r\n const aggregatedEdges = buildFilePairAggregate(\r\n fromId, allEdges, (id) => full.filePathByNodeId.get(id),\r\n );\r\n sendJson(res, 200, {\r\n edges: aggregatedEdges,\r\n total: aggregatedEdges.length,\r\n cursor: 0,\r\n hasMore: false,\r\n aggregated: true,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n const pagedEdges = mapDbEdgesToGraphEdges(allEdges.slice(cursor, cursor + limit));\r\n sendJson(res, 200, {\r\n edges: pagedEdges,\r\n total: allEdges.length,\r\n cursor: cursor + pagedEdges.length,\r\n hasMore: cursor + limit < allEdges.length,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n const dbResult = readEdgesFromDb(dbPath, fromId, scope as 'file' | 'symbol', cursor, limit);\r\n if (dbResult) {\r\n const pagedEdges = mapDbEdgesToGraphEdges(\r\n dbResult.edges.filter((e) => e.source !== e.target && !FILTERED_EDGE_KINDS.has(e.kind)),\r\n );\r\n sendJson(res, 200, {\r\n edges: pagedEdges,\r\n total: dbResult.total,\r\n cursor: cursor + pagedEdges.length,\r\n hasMore: cursor + limit < dbResult.total,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n // P0-B:`/edges` 的内存回落同样按请求方工作区限定。`fromId` 属于别的工作区时,\r\n // 在 A 的边集里过滤只会得到空列表,客户端表现为「这个文件没有任何边」——\r\n // 与「DB 确实没有边」不可区分。\r\n const lastGraphData = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (lastGraphData && lastGraphData.nodes.length > 0) {\r\n let filteredEdges: GraphEdge[];\r\n if (scope === 'symbol') {\r\n filteredEdges = lastGraphData.edges.filter((e) => e.source === fromId || e.target === fromId);\r\n } else {\r\n const index = getGraphIndex(lastGraphData);\r\n const nodeIds = new Set<string>();\r\n const nodeFilePaths = new Map<string, string>();\r\n const fpNodeIds = index.filePathToNodeIds.get(fromId);\r\n if (fpNodeIds) {\r\n for (const id of fpNodeIds) {\r\n nodeIds.add(id);\r\n nodeFilePaths.set(id, fromId);\r\n }\r\n }\r\n const selfFp = index.nodeIdToFilePath.get(fromId);\r\n if (selfFp !== undefined && !nodeIds.has(fromId)) {\r\n nodeIds.add(fromId);\r\n nodeFilePaths.set(fromId, selfFp);\r\n }\r\n filteredEdges = lastGraphData.edges.filter(\r\n (e) => nodeIds.has(e.source) || nodeIds.has(e.target),\r\n );\r\n\r\n if (mayAggregate && filteredEdges.length > EDGES_AGGREGATION.THRESHOLD) {\r\n const nodeIdToFilePath = index.nodeIdToFilePath;\r\n const aggregatedEdges = buildFilePairAggregate(\r\n fromId,\r\n filteredEdges,\r\n (id) => nodeFilePaths.get(id) ?? nodeIdToFilePath.get(id),\r\n );\r\n sendJson(res, 200, {\r\n edges: aggregatedEdges,\r\n total: aggregatedEdges.length,\r\n cursor: 0,\r\n hasMore: false,\r\n aggregated: true,\r\n }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n }\r\n\r\n const total = filteredEdges.length;\r\n // cycle-139:内存回落与 DB-first 共享同一确定序(file 域分组排序;symbol 域\r\n // 刻意保持原序)。两条路径各写一份必然静默漂移(buildFilePairAggregate 先例)。\r\n const orderedEdges = scope === 'file' ? sortEdgesByKindGroup(filteredEdges) : filteredEdges;\r\n const pagedEdges = orderedEdges.slice(cursor, cursor + limit);\r\n const hasMore = cursor + limit < total;\r\n\r\n sendJson(res, 200, { edges: pagedEdges, total, cursor: cursor + limit, hasMore }, req, corsConfig, etagHeader);\r\n return;\r\n }\r\n\r\n sendJson(res, 200, { edges: [], total: 0, cursor: 0, hasMore: false }, req, corsConfig, etagHeader);\r\n }),\r\n }), 'codegraph: edges route');\r\n}","/**\n * 白盒可观测性计数器(cycle-141 第二轮)。\n *\n * ## 为什么需要这个模块\n *\n * PerformanceMonitor 只度量**耗时**(cluster/layout/render/diagram/ast-fetch),\n * 但「白盒透明化」还需要回答**计数**问题:\n * - 缓存命中率是多少?(scanCache / IndexedDB / diagramCache / clusterCache)\n * - ETag 决策分布如何?(200 多还是 304 多?校验值命中/未命中/被拒绝?)\n * - AST 预算用量如何?(请求了多少节点、返回了多少、截断了多少、hasMore?)\n * - 管线各阶段耗时如何?(scan/init/import/DB 读取/三源合并/璇玑增量)\n *\n * 这些是**计数**而非**耗时**,不能用 measureSync 度量。本模块提供轻量级计数器:\n * - `incr(key, delta)` — 原子递增(Map + 计数)\n * - `snapshot()` — 一次性取全部计数(供面板渲染)\n * - `reset()` — 清零(测试用)\n *\n * ## 设计约束\n *\n * - **纯计数,无副作用**:不写文件、不发事件、不触发重渲染——只更新内部 Map。\n * - **线程安全**:JavaScript 单线程,Map 操作天然原子。\n * - **零第三方依赖**:只依赖 Node/浏览器原生 Map。\n * - **可测试**:reset() 后所有计数归零,snapshot() 返回确定性结构。\n *\n * @module shared/observability\n */\n\n/** 计数器条目:累计值 + 采样时间。 */\nexport interface CounterEntry {\n value: number;\n /** 首次记录的时间戳(用于「最近一次活动」显示)。 */\n firstSeen: number;\n /** 最后一次递增的时间戳。 */\n lastSeen: number;\n}\n\nconst counters = new Map<string, CounterEntry>();\n\n/**\n * 递增计数器。delta 缺省为 1。\n *\n * 幂等:对不存在的 key 首次调用会创建条目。\n */\nexport function incr(key: string, delta: number = 1): void {\n const now = Date.now();\n const existing = counters.get(key);\n if (existing) {\n existing.value += delta;\n existing.lastSeen = now;\n } else {\n counters.set(key, { value: delta, firstSeen: now, lastSeen: now });\n }\n}\n\n/**\n * 递减计数器。不会低于 0(负值无意义)。\n */\nexport function decr(key: string, delta: number = 1): void {\n const existing = counters.get(key);\n if (!existing) return;\n const newValue = existing.value - delta;\n if (newValue > 0) {\n existing.value = newValue;\n existing.lastSeen = Date.now();\n } else {\n counters.delete(key);\n }\n}\n\n/**\n * 结束该阶段所有**未配对**的计时窗口(reset 用)。\n *\n * 「未配对」= `pipelinePendingMarks` 里还挂着起始时间、从未等到对应 `end` 的窗口。\n * 只清理挂起窗口、**不产生样本**:没有发生的测量不能伪造一次 0ms 样本。\n */\nfunction clearPendingMarks(): void {\n pipelinePendingMarks.clear();\n}\n\n/**\n * 读取单个计数器值(不存在返回 0)。 */\nexport function getCount(key: string): number {\n return counters.get(key)?.value ?? 0;\n}\n\n/**\n * 读取全部计数器快照。\n *\n * 返回 `{ key: value }` 的扁平对象,方便面板渲染与测试断言。\n */\nexport function snapshot(): Record<string, number> {\n const out: Record<string, number> = {};\n for (const [key, entry] of counters) {\n out[key] = entry.value;\n }\n return out;\n}\n\n/**\n * 清零全部计数器与管线计时状态(测试用)。\n *\n * 注意:这**不**重置 PerformanceMonitor 的阈值表(那是另一个模块的职责)。\n * 调用方如果需要完整重置,必须同时调用 `clearMeasurements()` 与 `resetThresholds()`。\n *\n * cycle-141 第四轮修复(模块级单例成对重置):此前本函数只清 `counters`,\n * 不清 `pipelineDurations` / `pipelinePendingMarks`——一个测试里留下的计时样本\n * 会泄入下一个测试(CLAUDE.md「模块级单例必须成对重置」教训的原样复发:\n * `clearMeasurements()` 不重置阈值表是同一家族缺陷)。挂起窗口只清理、\n * 不产生样本——没有发生的测量不能伪造一次 0ms 样本。\n */\nexport function reset(): void {\n counters.clear();\n pipelineDurations.clear();\n clearPendingMarks();\n}\n\n/** 计数器键名常量——避免散落的字符串字面量漂移。 */\nexport const OBS_KEYS = {\n // 缓存命中/未命中\n cacheScanHit: 'cache.scan.hit',\n cacheScanMiss: 'cache.scan.miss',\n cacheIndexedDbHit: 'cache.indexeddb.hit',\n cacheIndexedDbMiss: 'cache.indexeddb.miss',\n cacheDiagramHit: 'cache.diagram.hit',\n cacheDiagramMiss: 'cache.diagram.miss',\n cacheClusterHit: 'cache.cluster.hit',\n cacheClusterMiss: 'cache.cluster.miss',\n\n // ETag 决策\n etag200: 'etag.200',\n etag304: 'etag.304',\n etagRejected: 'etag.rejected',\n\n // AST 预算用量\n astRequested: 'ast.requested',\n astReturned: 'ast.returned',\n astTruncated: 'ast.truncated',\n astHasMore: 'ast.hasMore',\n\n // 管线阶段计数\n pipelineScan: 'pipeline.scan',\n pipelineInit: 'pipeline.init',\n pipelineImport: 'pipeline.import',\n pipelineDbRead: 'pipeline.dbRead',\n pipelineMerge: 'pipeline.merge',\n pipelineXuanjiDelta: 'pipeline.xuanjiDelta',\n pipelineXuanjiDegraded: 'pipeline.xuanjiDegraded',\n} as const;\n\n/**\n * 缓存决策记录器——统一记录命中/未命中。\n *\n * 调用方不需要自己判断 key 是否存在,只需调用 `recordCacheHit(cache)` 或\n * `recordCacheMiss(cache)`,由本函数映射到正确的计数器键。\n */\nexport function recordCacheHit(cache: 'scan' | 'indexeddb' | 'diagram' | 'cluster'): void {\n incr(`cache.${cache}.hit`);\n}\n\nexport function recordCacheMiss(cache: 'scan' | 'indexeddb' | 'diagram' | 'cluster'): void {\n incr(`cache.${cache}.miss`);\n}\n\n/**\n * ETag 决策记录器。\n *\n * 只记录「决策」而非「传输」——304 是服务端说「没变」,200 是服务端说「变了」。\n * `rejected` 是校验值被拒绝(格式错误/维度不匹配)——这是最该被看见的决策。\n */\nexport function recordEtagDecision(decision: '200' | '304' | 'rejected'): void {\n incr(`etag.${decision}`);\n}\n\n/**\n * AST 预算用量记录器。\n *\n * 每次 AST 请求记录请求的节点数、返回的节点数、是否截断、是否 hasMore。\n * 这些数字在 UI 从未呈现(CLAUDE.md 明确点名过这个「层层丢弃」缺陷)。\n */\nexport function recordAstBudget(stats: { requested: number; returned: number; truncated: boolean; hasMore: boolean }): void {\n incr(OBS_KEYS.astRequested, stats.requested);\n incr(OBS_KEYS.astReturned, stats.returned);\n if (stats.truncated) incr(OBS_KEYS.astTruncated);\n if (stats.hasMore) incr(OBS_KEYS.astHasMore);\n}\n\n/**\n * 管线阶段计数器。\n *\n * 每次进入某个管线阶段递增对应计数器,用于「白盒透明化」的管线流动可视化。\n */\nexport function recordPipelineStage(stage: 'scan' | 'init' | 'import' | 'dbRead' | 'merge' | 'xuanjiDelta' | 'xuanjiDegraded'): void {\n incr(`pipeline.${stage}`);\n}\n\n/**\n * 管线阶段耗时追踪(cycle-141 第二轮)。\n *\n * 计数器只回答「多少次」,不回答「多久」。管线流动可视化需要**两者**:\n * - 计数:数据流过这个阶段多少次\n * - 耗时:每次流过花了多少时间\n *\n * 本模块用两个 Map 分别追踪:\n * - `pipelineDurations`:每个阶段的耗时样本(最近 100 条)\n * - `pipelineTimingKeys`:每个阶段的计时键名\n *\n * 与 `recordPipelineStage` 配合使用:调用方在进入阶段时调用\n * `startPipelineTiming(stage)`,离开时调用 `endPipelineTiming(stage)`。\n * 这两个函数**必须配对**——漏调 end 会让计时器永久挂起(stale timer)。\n */\n\n/** 每个阶段保留的最近耗时样本数。 */\nconst PIPELINE_SAMPLE_LIMIT = 100;\n\n/** 阶段 → 耗时样本数组(最近 100 条)。 */\nconst pipelineDurations = new Map<string, number[]>();\n\n/** 阶段 → 正在计时的起始时间栈(支持同阶段嵌套)。 */\nconst pipelinePendingMarks = new Map<string, number[]>();\n\n/**\n * 开始计时某个管线阶段。\n *\n * 返回一个 disposer:调用它**只终结这一次调用开启的窗口**,等同于对该窗口调用\n * `endPipelineTiming`。这样调用方可以用 `try/finally` 或 `using` 语法确保计时器\n * 一定被清理。\n *\n * cycle-141 第四轮修复(计时窗口所有权):此前 disposer 绑定的是**阶段名**——\n * 同阶段嵌套(scan 内再起 scan,例如子图重扫)时,内层先结束、外层的 disposer\n * 会把**内层的起始时间**当成自己的终点(Map.get 取到的是后写者的标记),或者\n * 内层结束把外层要用的标记删掉。现在每对 start/disposer 走独立的栈帧,\n * disposer 只消费自己压入的那一帧。\n */\nexport function startPipelineTiming(stage: string): () => void {\n const now = Date.now();\n const stack = pipelinePendingMarks.get(stage);\n if (stack) stack.push(now);\n else pipelinePendingMarks.set(stage, [now]);\n return () => endPipelineTiming(stage);\n}\n\n/**\n * 结束计时某个管线阶段**最近一次开启**的窗口,记录耗时。\n *\n * 返回耗时(毫秒);该阶段没有任何挂起窗口时返回 null。\n * 与 `startPipelineTiming` 是 LIFO 配对:后开始的先结束(栈语义)。\n */\nexport function endPipelineTiming(stage: string): number | null {\n const stack = pipelinePendingMarks.get(stage);\n if (!stack || stack.length === 0) return null;\n const start = stack.pop()!;\n if (stack.length === 0) pipelinePendingMarks.delete(stage);\n const durationMs = Date.now() - start;\n let samples = pipelineDurations.get(stage);\n if (!samples) {\n samples = [];\n pipelineDurations.set(stage, samples);\n }\n samples.push(durationMs);\n if (samples.length > PIPELINE_SAMPLE_LIMIT) samples.shift();\n return durationMs;\n}\n\n/**\n * 获取某个管线阶段的耗时统计。\n *\n * 返回 `{ count, avgMs, maxMs, minMs, lastMs, samples }`——\n * `samples` 是最近 100 条样本的数组(用于可视化)。\n */\nexport function getPipelineTiming(stage: string): {\n count: number;\n avgMs: number;\n maxMs: number;\n minMs: number;\n lastMs: number;\n samples: number[];\n} {\n const samples = pipelineDurations.get(stage) ?? [];\n if (samples.length === 0) {\n return { count: 0, avgMs: 0, maxMs: 0, minMs: 0, lastMs: 0, samples: [] };\n }\n const sum = samples.reduce((a, b) => a + b, 0);\n const avgMs = sum / samples.length;\n let maxMs = 0;\n let minMs = Infinity;\n for (const s of samples) {\n if (s > maxMs) maxMs = s;\n if (s < minMs) minMs = s;\n }\n return {\n count: samples.length,\n avgMs,\n maxMs,\n minMs,\n lastMs: samples[samples.length - 1]!,\n samples: [...samples],\n };\n}\n\n/**\n * 获取所有管线阶段的耗时统计。\n *\n * 返回 `Record<stage, timing>`——供面板一次性渲染全部阶段。\n */\nexport function getAllPipelineTiming(): Record<string, ReturnType<typeof getPipelineTiming>> {\n const out: Record<string, ReturnType<typeof getPipelineTiming>> = {};\n for (const stage of pipelineDurations.keys()) {\n out[stage] = getPipelineTiming(stage);\n }\n return out;\n}\n\n/**\n * 管线阶段名的有序列表(用于面板渲染顺序)。\n *\n * 按数据流方向排列:scan → import → dbRead → merge → xuanjiDelta。\n */\nexport const PIPELINE_ORDER = [\n 'scan',\n 'init',\n 'import',\n 'dbRead',\n 'merge',\n 'xuanjiDelta',\n 'xuanjiDegraded',\n] as const;\n\n/**\n * 指标计算耗时记录器。\n *\n * `manage/metrics` 路由已经计算 `computeMs` 并返回给客户端,但这只出现在\n * 单次响应里。把耗时也记入 observability 模块,面板就能展示「最近 N 次\n * 指标计算的平均耗时」——这才是白盒透明化该有的样子。\n *\n * 用 `pipeline.metricsCompute` 键记录累计耗时(毫秒),面板可据此算平均。\n */\nexport function recordMetricsCompute(durationMs: number): void {\n incr('pipeline.metricsCompute', durationMs);\n // 也递增一次计数,让面板知道「计算了多少次」\n incr('pipeline.metricsCompute.count');\n}\n\n/**\n * 管线阶段中文名(用于面板显示)。\n *\n * 放在 observability 模块而不是 i18n,因为这是**技术术语**而非用户界面文本——\n * 管线阶段名是开发者/运维看的内部指标,不需要国际化。\n */\nexport const PIPELINE_STAGE_LABELS: Record<string, string> = {\n scan: '扫描',\n init: '初始化',\n import: '导入',\n dbRead: '数据库读取',\n merge: '三源合并',\n xuanjiDelta: '璇玑增量',\n xuanjiDegraded: '璇玑降级',\n};\n","import { existsSync } from 'node:fs';\r\nimport { VALIDATION, ERROR_CODES, ERROR_MESSAGES, API, METRICS } from '../../shared/constants.ts';\r\nimport { recordCacheHit, recordCacheMiss, recordPipelineStage, startPipelineTiming, endPipelineTiming, recordMetricsCompute } from '../../shared/observability.ts';\r\nimport { RepoId } from '../../types/index.ts';\r\nimport { fetchMergedGraph } from '../../tools.ts';\r\nimport { resolveDbPath } from '../../adapters/CodeGraphAdapter.ts';\r\nimport {\r\n readBody, parseJsonBody, extractStringField, extractBooleanField,\r\n buildCorsHeaders, sendJson, sendError, requireMethod, logRequest,\r\n handleCorsPreflight,\r\n} from '../HttpHelpers.ts';\r\nimport {\r\n withRouteMiddleware, resolveAndValidatePath, slimGraphData, resolveWorkspaceFromRequest,\r\n triggerBackgroundBuild, type RouteContext, log,\r\n} from './shared.ts';\r\n\r\nexport function registerScanRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter, invokeUpstream } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/scan';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const scanPathInput = extractStringField(parsed, 'path', VALIDATION.MAX_PATH_LENGTH);\r\n const scanPath = await resolveAndValidatePath(scanPathInput, ctx, deps, req, res, corsConfig);\r\n if (scanPath === null) return;\r\n const repoId = scanPath || `workspace-${Date.now()}`;\r\n const scanCache = deps.getScanCache();\r\n\r\n const cached = scanCache.get(scanPath);\r\n if (cached && Date.now() - cached.timestamp < config.scanCacheTtl) {\r\n recordCacheHit('scan');\r\n deps.setLastGraphData(cached.data);\r\n const scanPayload = JSON.stringify({ success: true, ...cached.data });\r\n if (scanPayload.length > API.DATA_RESPONSE_MAX_BYTES) {\r\n const slimmed = slimGraphData(cached.data, API.DATA_RESPONSE_SLIM_NODES);\r\n const slimPayload = JSON.stringify({ success: true, ...slimmed });\r\n const scanHeaders = buildCorsHeaders(req, corsConfig);\r\n scanHeaders['content-type'] = 'application/json';\r\n scanHeaders['x-truncated'] = '1';\r\n res.writeHead(200, scanHeaders);\r\n res.end(slimPayload);\r\n } else {\r\n sendJson(res, 200, { success: true, ...cached.data }, req, corsConfig);\r\n }\r\n return;\r\n }\r\n\r\n // 缓存未命中:记录可观测性计数(白盒透明化:缓存命中率是核心指标)\r\n recordCacheMiss('scan');\r\n recordPipelineStage('scan');\r\n\r\n const inFlight = deps.getScanInFlight(scanPath);\r\n if (inFlight) {\r\n sendJson(res, 200, { success: true, status: 'scanning', message: 'Scan already in progress' }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 管线阶段计时:scan 开始 → fetchMergedGraph 完成 → merge 完成\r\n startPipelineTiming('scan');\r\n\r\n const scanPromise = fetchMergedGraph(invokeUpstream, repoId, 'both')\r\n .then(raw => slimGraphData(raw, config.maxNodes));\r\n deps.setScanInFlight(scanPath, scanPromise);\r\n\r\n sendJson(res, 200, { success: true, status: 'scanning', message: 'Scan started, listen to SSE for completion' }, req, corsConfig);\r\n\r\n scanPromise.then(data => {\r\n if (deps.isDestroyed()) return;\r\n endPipelineTiming('scan');\r\n deps.setLastGraphData(data);\r\n scanCache.set(scanPath, { data, timestamp: Date.now() });\r\n // 管线阶段计时:merge 开始 → 缓存写入完成\r\n startPipelineTiming('merge');\r\n // 管线阶段计数:scan 完成进入 merge(三源合并)阶段\r\n recordPipelineStage('merge');\r\n if (scanCache.size > config.scanCacheLimit) {\r\n const oldest = scanCache.keys().next().value;\r\n if (oldest !== undefined) scanCache.delete(oldest);\r\n }\r\n endPipelineTiming('merge');\r\n ctx.emit('codegraph-visualizer/graph/updated', {\r\n repoId: RepoId(repoId),\r\n nodeCount: data.metadata.nodeCount,\r\n edgeCount: data.metadata.edgeCount,\r\n timestamp: data.metadata.timestamp,\r\n });\r\n ctx.emit('codegraph-visualizer/graph/data', {\r\n repoId: RepoId(repoId),\r\n nodes: data.nodes,\r\n edges: data.edges,\r\n timestamp: data.metadata.timestamp,\r\n });\r\n log.info('scan completed (background)', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount });\r\n }).catch(e => {\r\n log.error('scan failed (background)', e);\r\n }).finally(() => {\r\n deps.setScanInFlight(scanPath, null);\r\n });\r\n } catch (e) {\r\n log.error('scan route failed', e);\r\n sendError(res, 500, ERROR_CODES.SCAN_FAILED, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: scan route');\r\n}\r\n\r\nexport function registerInitRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter, invokeUpstream } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/init';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const pathInput = extractStringField(parsed, 'path', VALIDATION.MAX_PATH_LENGTH);\r\n const initPath = await resolveAndValidatePath(pathInput, ctx, deps, req, res, corsConfig);\r\n if (initPath === null) return;\r\n log.info('init requested (non-blocking)', { path: initPath });\r\n\r\n sendJson(res, 200, {\r\n success: true,\r\n status: 'initializing',\r\n path: initPath,\r\n message: 'Initialization started, listen to SSE for completion',\r\n timestamp: Date.now(),\r\n }, req, corsConfig);\r\n\r\n (async () => {\r\n try {\r\n const result = await invokeUpstream('codegraph_init', { path: initPath, force: true });\r\n if (deps.isDestroyed()) return;\r\n const success = result !== null;\r\n const initResult = {\r\n success,\r\n path: initPath,\r\n message: success ? 'Graph initialized successfully' : (ERROR_MESSAGES.INIT_FAILED?.en ?? 'Initialization failed'),\r\n timestamp: Date.now(),\r\n };\r\n deps.setLastInitResult(initResult);\r\n ctx.emit('codegraph-visualizer/graph/init-result', initResult);\r\n if (success) {\r\n const repoId = initPath || `workspace-${Date.now()}`;\r\n const raw = await fetchMergedGraph(invokeUpstream, repoId, 'both');\r\n if (deps.isDestroyed()) return;\r\n const data = slimGraphData(raw, config.maxNodes);\r\n deps.setLastGraphData(data);\r\n ctx.emit('codegraph-visualizer/graph/data', {\r\n repoId: RepoId(repoId),\r\n nodes: data.nodes,\r\n edges: data.edges,\r\n timestamp: data.metadata.timestamp,\r\n });\r\n log.info('init completed (background)', { path: initPath, nodes: data.metadata.nodeCount });\r\n }\r\n } catch (e) {\r\n log.error('init failed (background)', e);\r\n const errorResult = { success: false, path: initPath, message: ERROR_MESSAGES.INIT_FAILED?.en ?? 'Initialization failed', timestamp: Date.now() };\r\n deps.setLastInitResult(errorResult);\r\n ctx.emit('codegraph-visualizer/graph/init-result', errorResult);\r\n }\r\n })();\r\n } catch (e) {\r\n log.error('init route failed', e);\r\n const errorResult = { success: false, path: '', message: ERROR_MESSAGES.INIT_FAILED?.en ?? 'Initialization failed', timestamp: Date.now() };\r\n deps.setLastInitResult(errorResult);\r\n sendError(res, 500, ERROR_CODES.INIT_FAILED, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: init route');\r\n}\r\n\r\nexport function registerWatchRoute(rc: RouteContext): void {\r\n const { deps, corsConfig } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/watch';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rc.rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const enabled = extractBooleanField(parsed, 'enabled');\r\n const pathInput = extractStringField(parsed, 'path', VALIDATION.MAX_PATH_LENGTH);\r\n const watchPath = await resolveAndValidatePath(pathInput, ctx, deps, req, res, corsConfig);\r\n if (watchPath === null) return;\r\n ctx.emit('codegraph-visualizer/watch/toggle', { enabled: !!enabled, path: watchPath, timestamp: Date.now() });\r\n sendJson(res, 200, { success: true, enabled: !!enabled, path: watchPath }, req, corsConfig);\r\n } catch (e) {\r\n log.warn('watch route failed', e);\r\n sendError(res, 500, ERROR_CODES.WATCH_TOGGLE_FAILED, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: watch route');\r\n}\r\n\r\nexport function registerEventsRoute(rc: RouteContext): void {\r\n const { deps, corsConfig } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/events';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: (req, res) => {\r\n if (handleCorsPreflight(req, res, corsConfig)) return;\r\n if (!requireMethod(req, res, 'GET', corsConfig)) return;\r\n logRequest(req, routePath);\r\n\r\n const corsHeaders = buildCorsHeaders(req, corsConfig);\r\n res.writeHead(200, {\r\n ...corsHeaders,\r\n 'Content-Type': 'text/event-stream',\r\n 'Cache-Control': 'no-cache',\r\n 'Connection': 'keep-alive',\r\n 'X-Accel-Buffering': 'no',\r\n });\r\n res.write(': connected\\n\\n');\r\n\r\n const sendEvent = (event: string, data: unknown) => {\r\n res.write(`event: ${event}\\n`);\r\n res.write(`data: ${JSON.stringify(data)}\\n\\n`);\r\n };\r\n\r\n const disposers: Array<() => boolean> = [];\r\n\r\n disposers.push(ctx.on('codegraph-visualizer/graph/updated', (event: { repoId?: string; nodeCount?: number; edgeCount?: number; timestamp?: number; changedFiles?: string[] }) => {\r\n sendEvent('graph-updated', event);\r\n }));\r\n disposers.push(ctx.on('codegraph-visualizer/graph/data', (event: { repoId?: string; nodes?: unknown[]; edges?: unknown[]; timestamp?: number; changedFiles?: string[] }) => {\r\n sendEvent('graph-data', event);\r\n }));\r\n // P2-21: targeted updates. Forwarded as its own event so consumers that understand deltas can\r\n // take the cheap path while anything that does not simply keeps listening to `graph-updated`.\r\n disposers.push(ctx.on('codegraph-visualizer/graph/delta', (event: { repoId?: string; delta?: unknown; timestamp?: number; changedFiles?: string[] }) => {\r\n sendEvent('graph-delta', event);\r\n }));\r\n disposers.push(ctx.on('codegraph-visualizer/repo/imported', (event: { repoId?: string; timestamp?: number }) => {\r\n sendEvent('repo-imported', event);\r\n }));\r\n disposers.push(ctx.on('codegraph-visualizer/repo/scanned', (event: { repoId?: string; timestamp?: number }) => {\r\n sendEvent('repo-scanned', event);\r\n }));\r\n disposers.push(ctx.on('codegraph-visualizer/graph/init-result', (event: { success?: boolean; path?: string; message?: string; timestamp?: number }) => {\r\n sendEvent('init-result', event);\r\n }));\r\n\r\n const heartbeat = setInterval(() => {\r\n res.write(': heartbeat\\n\\n');\r\n }, 30000);\r\n\r\n const cleanup = () => {\r\n clearInterval(heartbeat);\r\n for (const dispose of disposers) {\r\n try { dispose(); } catch { /* already disposed */ }\r\n }\r\n };\r\n\r\n req.on('close', cleanup);\r\n req.on('error', cleanup);\r\n },\r\n }), 'codegraph: events SSE route');\r\n}\r\n// ── cycle-141:管理面板路由 ──────────────────────────────────────────────\r\n\r\n/**\r\n * POST /api/v1/codegraph/manage/delete — 删除原生索引(cycle-141)。\r\n *\r\n * ## 幂等与错误语义(frozen contract)\r\n *\r\n * - 删掉:HTTP 200 `{ success:true, deleted:true, errorCode:null }`\r\n * - 不存在:HTTP 200 `{ success:false, deleted:false, errorCode:'INDEX_NOT_FOUND' }`\r\n * —— 删除是幂等操作,「没有东西可删」不是服务端错误。\r\n * - 真失败:HTTP 500 `{ success:false, deleted:false, errorCode:'DELETE_FAILED', error }`\r\n *\r\n * 旧实现把 `catch` 也回 200 + `{success:false,error}`,把「磁盘删除失败」与「索引不存在」\r\n * 压成同一个 HTTP 200,客户端无法区分「再试一次」与「已经干净了」。这里按契约分开。\r\n *\r\n * ## 失效缓存与广播(不能只删文件)\r\n *\r\n * 删掉索引文件后,host 的 `scanCache` / `scanInFlightMap` 里该路径仍留着上一张图的条目:\r\n * 下次 `/scan` 会命中 TTL 内的缓存而**重新展示一张已经不存在的图**;在飞的扫描也会把\r\n * 旧数据写回槽位。所以成功删除后必须:\r\n * 1. `scanCache.delete(path)` + `setScanInFlight(path, null)`;\r\n * 2. `ctx.emit('codegraph-visualizer/graph/deleted', { repoId, path, timestamp })`\r\n * 让客户端丢掉陈旧状态(SSE 消费者据此清空画布)。\r\n * 只删文件不广播 = 用户看到一张死图。\r\n */\r\nexport function registerManageDeleteRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/manage/delete';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const pathInput = extractStringField(parsed, 'path', VALIDATION.MAX_PATH_LENGTH);\r\n const deletePath = await resolveAndValidatePath(pathInput, ctx, deps, req, res, corsConfig);\r\n if (deletePath === null) return;\r\n\r\n const { deleteNativeIndex } = await import('../../adapters/nativeIndex.ts');\r\n const deleted = deleteNativeIndex(deletePath);\r\n\r\n if (!deleted) {\r\n // 幂等删除:没有可删的索引不是错误,回 200 + INDEX_NOT_FOUND。\r\n sendJson(res, 200, {\r\n success: false,\r\n deleted: false,\r\n errorCode: ERROR_CODES.INDEX_NOT_FOUND,\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 删除成功后必须同时清掉 host 侧缓存,否则 TTL 内的旧图会被再次展示。\r\n deps.getScanCache().delete(deletePath);\r\n deps.setScanInFlight(deletePath, null);\r\n\r\n ctx.emit('codegraph-visualizer/graph/deleted', {\r\n repoId: RepoId(deletePath),\r\n path: deletePath,\r\n timestamp: Date.now(),\r\n });\r\n\r\n sendJson(res, 200, {\r\n success: true,\r\n deleted: true,\r\n errorCode: null,\r\n }, req, corsConfig);\r\n } catch (e) {\r\n log.error('manage delete failed', e);\r\n sendJson(res, 500, {\r\n success: false,\r\n deleted: false,\r\n errorCode: ERROR_CODES.DELETE_FAILED,\r\n error: e instanceof Error ? e.message : String(e),\r\n }, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: manage delete route');\r\n}\r\n\r\n/**\r\n * GET /api/v1/codegraph/manage/metrics?workspace=<abs> — 图指标(数学模型)。\r\n *\r\n * ## 为什么必须双重上界(P0 可用性)\r\n *\r\n * 旧实现的循环条件只界节点(`allDbNodes.length < 10000`),边侧完全没有界:只要节点数\r\n * 没到上限,循环就把**全库边**读进内存。代码图谱的边数通常是节点数的 2–5 倍,一个\r\n * 9 千节点 / 4 万边的仓库就足以让这条路由 OOM。节点与边是**同一处内存护栏的两半**,\r\n * 缺一即回到旧缺陷。\r\n *\r\n * ## 没有索引就必须说「没有」,不能报「健康空图」\r\n *\r\n * 旧实现在两个 DB 路径都不存在时照样走 `computeGraphMetrics([], [])`,回\r\n * `{success:true, metrics:{nodeCount:0,...}}`——面板据此显示「密度 0、PageRank 齐全」的\r\n * **假健康空图**,用户以为索引正常。现在两个路径都不存在时回\r\n * `INDEX_NOT_FOUND`(HTTP 200,`metrics:null, truncation:null`)。\r\n *\r\n * ## 截断必须如实披露\r\n *\r\n * `nodesTotal`/`edgesTotal` 取分页器 `page.stats` 累计到的库内行数;\r\n * `nodesSampled`/`edgesSampled` 是**实际映射进图**的节点/边数(映射层会过滤噪音路径与\r\n * 无效行,二者可以不等)。`truncated` 单独给出「没有读到库尾」与「部分行被过滤」两种\r\n * 采样情形——客户端据此知道这不是全量图。\r\n */\r\nexport function registerManageMetricsRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/manage/metrics';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\r\n if (!workspacePath) {\r\n sendJson(res, 200, {\r\n success: false,\r\n errorCode: ERROR_CODES.INDEX_NOT_FOUND,\r\n metrics: null,\r\n truncation: null,\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 内联 DB 路径解析:优先上游 DB,不存在则回落原生索引。\r\n // 两者都不存在 ⇒ 没有索引,必须如实报 INDEX_NOT_FOUND(见函数头)。\r\n let dbPath: string | null = null;\r\n const upstreamDbPath = resolveDbPath(workspacePath);\r\n if (existsSync(upstreamDbPath)) {\r\n dbPath = upstreamDbPath;\r\n } else {\r\n try {\r\n const { resolveNativeIndexPath } = await import('../../adapters/nativeIndex.ts');\r\n const nativePath = resolveNativeIndexPath(workspacePath);\r\n if (existsSync(nativePath)) dbPath = nativePath;\r\n } catch (e) {\r\n log.warn('manage metrics: native index path rejected', e);\r\n }\r\n }\r\n if (!dbPath) {\r\n sendJson(res, 200, {\r\n success: false,\r\n errorCode: ERROR_CODES.INDEX_NOT_FOUND,\r\n metrics: null,\r\n truncation: null,\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n const { readGraphFromDbPaged } = await import('../../adapters/codeGraphDb.ts');\r\n const { mapRowsToGraphNodes, mapRowsToGraphEdges } = await import('../../adapters/codeGraphRows.ts');\r\n\r\n // 双重上界:节点与边各自设上限(见 METRICS 常量注释)。\r\n const MAX_NODES = METRICS.MAX_NODES;\r\n const MAX_EDGES = METRICS.MAX_EDGES;\r\n const PAGE = METRICS.PAGE_SIZE;\r\n let cursor: string | null = null;\r\n const allDbNodes = [];\r\n const allDbEdges = [];\r\n let nodesTotal = 0;\r\n let edgesTotal = 0;\r\n let readExhausted = false;\r\n for (;;) {\r\n const page = readGraphFromDbPaged(dbPath, cursor, PAGE);\r\n // DB 文件在场却读不出一页 = 真实失败(索引损坏 / 连接被拒),不是「空索引」。\r\n // 若在这里 `break`,就会退化成旧实现的「假健康空图」——正是本路由要消灭的形态。\r\n if (!page) throw new Error('index database unreadable');\r\n allDbNodes.push(...page.nodes);\r\n allDbEdges.push(...page.edges);\r\n // page.stats 是分页器累计到的库内行数(不是采样子集),用于披露 totals。\r\n nodesTotal = page.stats.nodeCount;\r\n edgesTotal = page.stats.edgeCount;\r\n if (page.nextCursor === null) {\r\n readExhausted = true;\r\n break;\r\n }\r\n if (allDbNodes.length >= MAX_NODES || allDbEdges.length >= MAX_EDGES) break;\r\n cursor = page.nextCursor;\r\n }\r\n\r\n const nodes = mapRowsToGraphNodes(allDbNodes);\r\n const validNodeIds = new Set(nodes.map((n) => n.id));\r\n const edges = mapRowsToGraphEdges(allDbEdges, validNodeIds);\r\n\r\n // 重计算必须计时(客户端据此判断指标是否过期)。\r\n const startedAt = Date.now();\r\n const { computeGraphMetrics } = await import('../../xuanji/analysis/graphMetrics.ts');\r\n const metrics = computeGraphMetrics(nodes, edges);\r\n const computeMs = Date.now() - startedAt;\r\n recordMetricsCompute(computeMs);\r\n\r\n const nodesSampled = nodes.length;\r\n const edgesSampled = edges.length;\r\n const truncated = !readExhausted\r\n || nodesTotal !== nodesSampled\r\n || edgesTotal !== edgesSampled;\r\n\r\n // 索引覆盖范围(cycle-141 第七轮):原生索引读 meta 里写侧沉淀的 gitignore\r\n // 统计——「图谱为什么少了某目录」的唯一可诊断入口(排除是不可见操作)。\r\n // 上游索引无此数据面(graphLayer=upstream 时 coverage 为 null,面板不渲染该行)。\r\n let coverage: import('../../adapters/nativeIndex.ts').IndexCoverage | null = null;\r\n try {\r\n const { readNativeIndexCoverage } = await import('../../adapters/nativeIndex.ts');\r\n coverage = readNativeIndexCoverage(workspacePath);\r\n } catch {\r\n coverage = null;\r\n }\r\n\r\n sendJson(res, 200, {\r\n success: true,\r\n errorCode: null,\r\n metrics: {\r\n nodeCount: metrics.nodeCount,\r\n edgeCount: metrics.edgeCount,\r\n density: metrics.density,\r\n avgClustering: metrics.avgClustering,\r\n degreeDistribution: metrics.degreeDistribution,\r\n topBetweenness: metrics.topBetweenness,\r\n topPageRank: metrics.topPageRank,\r\n components: metrics.components,\r\n articulationPoints: metrics.articulationPoints,\r\n diameter: metrics.diameter,\r\n avgShortestPath: metrics.avgShortestPath,\r\n modularity: metrics.modularity,\r\n cyclomaticNumber: metrics.cyclomaticNumber,\r\n kCore: metrics.kCore,\r\n pageRankMass: metrics.pageRankMass,\r\n },\r\n truncation: {\r\n nodesTotal,\r\n nodesSampled,\r\n edgesTotal,\r\n edgesSampled,\r\n truncated,\r\n sampled: truncated,\r\n computeMs,\r\n },\r\n coverage,\r\n // cycle-146 T146-3:限流器白盒快照(additive 字段)。管理面板「限流观测」区\r\n // 据此渲染活跃桶/配额/窗口剩余——限流从「黑盒行为」变成可观测数据面。\r\n rateLimiter: rateLimiter.snapshot(),\r\n computedAt: Date.now(),\r\n }, req, corsConfig);\r\n } catch (e) {\r\n log.error('manage metrics failed', e);\r\n sendJson(res, 500, {\r\n success: false,\r\n errorCode: ERROR_CODES.METRICS_FAILED,\r\n error: e instanceof Error ? e.message : String(e),\r\n }, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: manage metrics route');\r\n}\r\n\r\n/**\r\n * POST /api/v1/codegraph/manage/update — 增量更新(codegraph_sync → 重扫)。\r\n *\r\n * ## 为什么不能只是 `/scan`\r\n *\r\n * 面板的「更新」按钮此前直接调 `/scan`,那是**全量重建**:上游已有索引时白白重扫一遍。\r\n * 增量路径是 `codegraph_sync`(只同步变更文件)→ 触发一次重扫(`triggerBackgroundBuild`)。\r\n *\r\n * ## 契约\r\n *\r\n * - 成功:HTTP 200 `{ success:true, errorCode:null, synced:true, scanned:boolean }`\r\n * - 上游同步不可用/失败:HTTP 200\r\n * `{ success:false, errorCode:'SYNC_FAILED', synced:false, scanned:boolean, error? }`\r\n * —— `createInvoke` 把任何异常归一成 `null`,所以「上游没装」「工具报错」都表现为\r\n * `synced:false`;路由仍尽力触发一次重扫(`scanned` 如实反映是否真的起了扫描),\r\n * 让用户能看到宿主侧已有图的最新状态。\r\n */\r\nexport function registerManageUpdateRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter, invokeUpstream } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/manage/update';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const pathInput = extractStringField(parsed, 'path', VALIDATION.MAX_PATH_LENGTH);\r\n const updatePath = await resolveAndValidatePath(pathInput, ctx, deps, req, res, corsConfig);\r\n if (updatePath === null) return;\r\n\r\n const syncResult = await invokeUpstream('codegraph_sync', { path: updatePath });\r\n const synced = syncResult !== null;\r\n\r\n // 无论同步成功与否都触发一次重扫:同步失败可能是「上游没装」,\r\n // 但宿主侧已有的图仍然值得刷新一次(幂等)。\r\n triggerBackgroundBuild(deps, invokeUpstream, updatePath);\r\n const scanned = deps.getScanInFlight(updatePath) !== null;\r\n\r\n if (!synced) {\r\n sendJson(res, 200, {\r\n success: false,\r\n errorCode: ERROR_CODES.SYNC_FAILED,\r\n synced: false,\r\n scanned,\r\n error: 'codegraph_sync unavailable or failed',\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n sendJson(res, 200, {\r\n success: true,\r\n errorCode: null,\r\n synced: true,\r\n scanned,\r\n }, req, corsConfig);\r\n } catch (e) {\r\n log.error('manage update failed', e);\r\n sendJson(res, 500, {\r\n success: false,\r\n errorCode: ERROR_CODES.SYNC_FAILED,\r\n synced: false,\r\n scanned: false,\r\n error: e instanceof Error ? e.message : String(e),\r\n }, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: manage update route');\r\n}\r\n","/**\r\n * git 文件级差异的纯函数层(P0-D,cycle-105 真机发现)。\r\n *\r\n * ## 为什么必须有这一层\r\n *\r\n * 升级前的 `/diff` 路由把「工作区路径 + `@` + 提交号」当成**仓库标识**去查\r\n * `.codegraph/codegraph.db`:\r\n *\r\n * ```\r\n * fetchMergedGraph(invoke, `${workspacePath}@${fromCommit}`, 'both')\r\n * → resolveDbPath('D:\\repo@abc123') = 'D:\\repo@abc123\\.codegraph\\codegraph.db'\r\n * ```\r\n *\r\n * 那个路径**永远不存在**,于是 `nodes: []`、`edges: []`,路由照样回 **HTTP 200**。\r\n * 这是最坏的一种失败:不是报错,而是**假成功**——面板显示「两个版本之间没有结构变更」,\r\n * 而实测两个提交之间改了 42 个文件。用户没有任何线索能看出这次对比根本没做成。\r\n *\r\n * 根因是数据面缺一个维度:`codegraph.db` 索引的是**当前工作树**,表里没有任何\r\n * revision/commit 列(`nodes`/`edges`/`files` 三张表都没有)。也就是说「按提交取图」\r\n * 这件事在本仓库的数据面上**不可实现**,不是实现得不好。\r\n *\r\n * 唯一诚实且可实现的粒度是**文件级**:\r\n *\r\n * 1. `git diff --raw -z <from> <to>` 给出精确的变更文件清单与状态(A/M/D);\r\n * 2. 画布上的文件节点 id 是可复现的 `file:<相对路径>`(探针实测 433/433 完全匹配,\r\n * 见 `scripts/probe-file-node-ids.mjs`);\r\n * 3. 于是 `file:<path>` 与画布**同一命名空间**,`getElementById` 能真正命中。\r\n *\r\n * 若强行做符号级 diff,只能拿 tree-sitter 现场解析两个提交的源码,而 tree-sitter 的\r\n * 节点 id 是位置化的(`${filePath}:${label}:${row}`),与 DB 的语义化 id\r\n * (`file:...` / `import:<hash>`)**不可能对齐**——那会退回「标记一个都命中不了」\r\n * 的老问题,只是换了个地方。\r\n *\r\n * ## 本文件的职责边界\r\n *\r\n * 只做两件**没有副作用**的事:解析 git 输出、把变更清单转成图负载。跑 git 子进程的\r\n * 部分在 `src/host/gitDiff.ts`。这样单测不需要真的建仓库,也不会因为机器上没装 git 而红。\r\n */\r\n\r\n/** 一个变更文件相对基线的状态。刻意复用 `diffStatus.ts` 的三值封闭联合。 */\r\nexport type GitFileStatus = 'added' | 'modified' | 'removed';\r\n\r\n/** git 输出里的一条文件变更。 */\r\nexport interface GitFileChange {\r\n /** 归一化后的仓库相对路径(正斜杠、无前导 `./`)。 */\r\n path: string;\r\n status: GitFileStatus;\r\n}\r\n\r\n/** `git diff` 采集的硬性上限。 */\r\nexport const GIT_DIFF = {\r\n /** 最多接受多少个变更文件;超出则截断并置 `truncated`。 */\r\n MAX_FILES: 5000,\r\n /** 单次 `git diff` 的超时(毫秒)。超时按「采集失败」处理,不抛给调用方。 */\r\n TIMEOUT_MS: 15_000,\r\n /** `git diff` 输出上限(字节)。 */\r\n MAX_OUTPUT_BYTES: 8 * 1024 * 1024,\r\n} as const;\r\n\r\n/** 一次文件级 diff 的采集结果。失败与「没有变更」必须可区分。 */\r\nexport interface GitFileDiffResult {\r\n changes: GitFileChange[];\r\n /** 采集**是否真的拿到了 git 数据**。`false` 时 `changes` 为空,`error` 说明原因。 */\r\n available: boolean;\r\n /** 采集失败时的原因(人类可读);成功为 `null`。静默失败无法排查,因此保留在载荷里。 */\r\n error: string | null;\r\n /** 是否被 `MAX_FILES` 截断。截断时面板必须能说明「只覆盖前 N 个文件」。 */\r\n truncated: boolean;\r\n /** 解析出的提交数(恒为 2:`from` 与 `to`)。供面板回显「A → B」。 */\r\n revisions: { from: string; to: string } | null;\r\n}\r\n\r\n/**\r\n * 把路径归一化成与 `codegraph.db` 的 `file_path` 列可比对的形状。\r\n *\r\n * 刻意与 `churn.ts` 的 `normalizeChurnPath` 保持**同一套规则**,但不直接 import 它:\r\n * 两个模块的语义不同(churn 是「统计」、这里是「身份」),共用一个函数会让任何一侧\r\n * 的规则调整意外改变另一侧。规则本身很短,重复是可接受的代价;真正不能重复的是\r\n * **节点 id 的拼法**,那件事在 `buildFileNodeId` 里只有一份实现。\r\n */\r\nexport function normalizeDiffPath(rawPath: string): string {\r\n return String(rawPath ?? '')\r\n // trim 是必需的:`-z` 分出的路径段理论上不带空白,但 git 的某些版本在\r\n // 状态码与路径之间会多一个空格(`--raw` 的人类可读格式就有)。\r\n .trim()\r\n .replace(/\\\\/g, '/')\r\n .replace(/^\\.\\//, '')\r\n .replace(/\\/+$/, '');\r\n}\r\n\r\n/**\r\n * 画布上文件节点的 id。\r\n *\r\n * 这是**唯一构造点**:宿主侧生成 diff 节点、客户端侧查画布元素都用它。探针\r\n * (`scripts/probe-file-node-ids.mjs`)实测 433/433 个文件节点的 id 恰好是\r\n * `file:<file_path>`,因此这里的拼法必须与 `CodeGraphAdapter.mapRowsToGraphNodes`\r\n * 保持一致(那里直接用 DB 的 `n.id`,而 DB 的 id 就是 `file:<path>`)。\r\n *\r\n * 拼错一个字符的后果是静默的:`getElementById` 找不到元素 ⇒ 标记一个都不出现 ⇒\r\n * 面板显示「N 处变更」而画布毫无反应,且没有任何报错。\r\n */\r\nexport function buildFileNodeId(path: string): string {\r\n return `file:${normalizeDiffPath(path)}`;\r\n}\r\n\r\n/** git `--raw` 的状态字母 → 三值语义。`--no-renames` 保证不会出现 R/C。 */\r\nconst RAW_STATUS_MAP: Record<string, GitFileStatus> = {\r\n A: 'added',\r\n M: 'modified',\r\n D: 'removed',\r\n T: 'modified', // 类型变更(普通文件 ↔ 符号链接等)在图上仍是「这个文件变了」\r\n};\r\n\r\n/**\r\n * 解析 `git diff --raw --no-renames -z` 的原始输出。\r\n *\r\n * 输出形状(`-z` 下用 NUL 分隔,状态行与路径**交替**出现):\r\n *\r\n * ```\r\n * :100644 100644 694c57a e3b3222 M\\0docs/a.md\\0:100644 000000 abc1234 0000000 D\\0src/b.ts\\0\r\n * ```\r\n *\r\n * 刻意解析**状态行 + 紧随其后的一个路径段**,而不是按「每两段一组」硬切:\r\n * `-z` 输出里路径段可能为空(极少数情况下 git 会给出空路径),硬切会让后续所有\r\n * 配对整体错位一格——这类错位在结果上表现为「一堆文件状态全是上一个文件的」,\r\n * 且没有任何报错。\r\n *\r\n * 无法识别的状态字母(`U` 未合并、`X` 未知)**不静默丢弃**:按 `modified` 计入,\r\n * 因为「这个文件在两个提交之间确实不同」是唯一可以确定的事实。丢掉它会让 diff\r\n * 少报一个文件,而少报比多报更危险(用户会以为没改)。\r\n */\r\nexport function parseGitRawDiff(output: string): GitFileChange[] {\r\n const segments = String(output ?? '').split('\\0');\r\n const changes: GitFileChange[] = [];\r\n\r\n for (let i = 0; i < segments.length; i += 1) {\r\n const segment = segments[i] ?? '';\r\n // 状态行以 `:` 开头;其余段都是路径,由「前一个状态行」消费掉。\r\n if (!segment.startsWith(':')) continue;\r\n\r\n // 状态行形如 `:100644 100644 <sha> <sha> <STATUS>`,STATUS 是最后一个空白分隔的字段。\r\n const fields = segment.trim().split(/\\s+/);\r\n const rawStatus = fields[fields.length - 1] ?? '';\r\n // 取状态字母的首字符:`M` / `A` / `D` / `T`;`R100` 之类在本仓库不会出现\r\n // (`--no-renames`),但首字符规则对它们同样安全。\r\n const letter = rawStatus.charAt(0).toUpperCase();\r\n const status = RAW_STATUS_MAP[letter] ?? 'modified';\r\n\r\n const rawPath = segments[i + 1] ?? '';\r\n // 消费掉路径段:下一轮从状态行继续。`i += 1` 而不是依赖「每两段一组」。\r\n i += 1;\r\n\r\n const path = normalizeDiffPath(rawPath);\r\n if (!path) continue;\r\n changes.push({ path, status });\r\n }\r\n\r\n return changes;\r\n}\r\n\r\n/**\r\n * 把变更清单转成 `/diff` 的响应图负载。\r\n *\r\n * **只产节点、不产边**:文件级 diff 里「两个文件之间新增了一条依赖」这件事在\r\n * `git diff --raw` 的输出里根本不存在(它只给文件清单,不给内容)。硬编一条边\r\n * 等于编造数据。节点上的 `diffStatus` 足以让画布上的文件节点亮起对应颜色。\r\n *\r\n * 节点字段刻意与 `CodeGraphAdapter.mapRowsToGraphNodes` 对文件节点的产出对齐:\r\n * `type: 'module'`(DB 的 `kind: 'file'` 映射到 `module`)、`label` 取文件名、\r\n * `filePath` 取归一化相对路径。这样即使客户端的 `coerceNode` 走同一套归一化,\r\n * 字段也不会因为「diff 节点比普通节点少了几个字段」而被过滤掉。\r\n */\r\nexport function fileChangesToDiffNodes(\r\n changes: readonly GitFileChange[],\r\n): Array<{\r\n id: string;\r\n label: string;\r\n type: 'module';\r\n filePath: string;\r\n properties: Record<string, unknown>;\r\n}> {\r\n const seen = new Set<string>();\r\n const nodes: Array<{\r\n id: string;\r\n label: string;\r\n type: 'module';\r\n filePath: string;\r\n properties: Record<string, unknown>;\r\n }> = [];\r\n\r\n for (const change of changes) {\r\n const filePath = normalizeDiffPath(change.path);\r\n if (!filePath) continue;\r\n const id = buildFileNodeId(filePath);\r\n // 同一路径出现两次(例如 rename 的两侧在 `--no-renames` 下被拆成 D + A)\r\n // 只保留**第一条**:后面的条目描述的是同一份文件的另一半生命周期,\r\n // 覆盖会让状态在「删除」与「新增」之间随机跳动。\r\n if (seen.has(id)) continue;\r\n seen.add(id);\r\n\r\n const segments = filePath.split('/');\r\n const label = segments[segments.length - 1] || filePath;\r\n\r\n nodes.push({\r\n id,\r\n label,\r\n type: 'module',\r\n filePath,\r\n properties: {\r\n // `diffStatus` 必须落在 `properties` 里:`diffStatus.ts` 的 `readDiffStatus`\r\n // 只读 `properties.diffStatus`,写到顶层会被整条忽略(标记不出现且无报错)。\r\n diffStatus: change.status,\r\n // 与 DB 文件节点同一套属性词汇,便于面板按 kind 过滤时行为一致。\r\n kind: 'file',\r\n source: 'git-diff',\r\n },\r\n });\r\n }\r\n\r\n return nodes;\r\n}\r\n","/**\n * git 变更频率采集器(研究报告 §六 #11)。\n *\n * 现有热点分析只有入度维度;本模块补上「变更频率」一侧:在工作区里跑一次\n * `git log --name-only`,把「每个文件被多少个提交触及过」交给客户端与入度相乘。\n *\n * **失败必须可见,不能静默编造数据。** 本函数永不抛异常给调用方:\n * git 没装 / 目录不是仓库 / 超时 / 输出超限,一律返回 `available: false` 的空结果,\n * 由路由把它原样发给客户端——客户端据此显示「未获取到变更频率」,\n * 而不是把它当成「所有文件 0 次变更」混进热点排序。\n *\n * **刻意不用 `shell: true`(P0-A,cycle-105 真机发现)。** `prerequisites.ts` 里的\n * `shell: win32` 只在探测 `codegraph` 命令时成立(那里需要 shell 解析 `.cmd`),\n * 而 `git` 是一个真正的可执行文件,走 shell 只会带来一个致命副作用:`execFile` 在\n * `shell: true` 下把参数**拼成一条命令行字符串而不做转义**(Node DEP0190 也警告这一点),\n * 于是工作区路径里只要有空格就会被 shell 断开——\n *\n * ```\n * git -C C:\\Users\\me\\My Projects\\repo ... → fatal: cannot change to 'C:\\Users\\me\\My'\n * ```\n *\n * 该失败是**永久性**的(不是瞬时超时):只要工作区路径含空格,churn 就永远不可用,\n * 而本仓库的大量用户工作区正是 `D:\\My Projects\\...` 这类路径。\n * 去掉 shell 后参数按 argv 逐个传递,空格与中文都能正确到达 git(已实测:\n * 含空格 + 中文的临时仓库路径在 `shell: false` 下正常、`shell: true` 下报\n * `fatal: cannot change to ...: No such file or directory`)。\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { scoped } from '../shared/Logger.ts';\nimport { CHURN, parseGitLogNames, type ChurnResult } from '../shared/churn.ts';\n\nconst log = scoped('host:churn');\nconst execFileAsync = promisify(execFile);\n\n/**\n * 采集结果的短 TTL 缓存(按工作区路径)。\n *\n * `git log` 在大型仓库上要几百毫秒到数秒,而热点面板每次打开、每次换图都会触发一次请求。\n * 没有缓存时,用户拖动一次图就会连发多次 git 子进程——这是本仓库反复出现的\n * 「一个昂贵操作被无节流地重复调用」形态(对照 cycle-85 的 prefetch 重复请求)。\n *\n * 刻意用**有界 Map + 主动淘汰**而不是无界对象:工作区路径来自请求参数,\n * 无界缓存等于把攻击面(或用户长时间使用后的内存占用)交给调用方。\n */\ninterface ChurnCacheEntry { result: ChurnResult; timestamp: number; }\nconst churnCache = new Map<string, ChurnCacheEntry>();\nconst CHURN_CACHE_MAX = 16;\n/** 缓存有效期:短到能反映新的提交,长到足以吸收一次面板操作里的重复请求。 */\nconst CHURN_CACHE_TTL_MS = 30_000;\n\n/** 清空采集缓存(测试与工作区切换时使用;也让「采集器有缓存」这件事可被观测)。 */\nexport function clearChurnCache(): void {\n churnCache.clear();\n}\n\n/** 采集失败时的空结果。`error` 是原因,其余字段是「什么都没有」的诚实表达。 */\nfunction failed(error: string): ChurnResult {\n return { byFile: {}, commitCount: 0, revision: null, truncated: false, available: false, error };\n}\n\n/** 成功时的空结果(git 可用但仓库里没有提交 / 没有文件变动)。 */\nfunction empty(): ChurnResult {\n return { byFile: {}, commitCount: 0, revision: null, truncated: false, available: true, error: null };\n}\n\n/** 归一化原因:把 git 的 stderr 压成一行,避免把整段帮助文本写进响应。 */\nfunction describeError(e: unknown): string {\n if (e instanceof Error) {\n const err = e as { stderr?: string; killed?: boolean; signal?: string; code?: string | number };\n const stderr = err.stderr;\n if (stderr) {\n const firstLine = stderr.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? '';\n if (firstLine.trim()) return firstLine.trim();\n }\n // 超时/被杀时 `stderr` 是空串、`e.message` 只有一条 `Command failed: git ...` 命令回显——\n // 那种原因对用户毫无信息量(真机日志里表现为 `reason: 'Command failed: git -C ...\\n'`)。\n // 这里显式识别出来并给出可读原因,否则「churn 不可用」永远无法排查。\n if (err.killed || err.signal) {\n return `git log 超时(>${CHURN.TIMEOUT_MS}ms)或被杀(signal=${err.signal ?? 'unknown'})`;\n }\n if (err.code === 'ENOENT') return '找不到 git 可执行文件(未安装或不在 PATH 中)';\n // 其余情况保留 message 的第一行,至少带上退出码。\n const firstMessageLine = e.message.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? e.message;\n return firstMessageLine.trim();\n }\n return String(e);\n}\n\n/**\n * 采集 `workspacePath` 的 git 变更频率(带短 TTL 缓存)。\n *\n * 刻意用 `-C <dir>` 而不是把路径塞进 args:路径里带空格时 shell 转义是脆弱的,\n * `-C` 是 git 自己的参数,安全且可测。\n *\n * 缓存只缓存**成功**结果:失败(`available: false`)不缓存,否则一次瞬时失败会让用户\n * 在 30 秒内反复看到「未获取到变更频率」,而期间 git 其实早就可用了。\n * 这与「退化默认值必须拒绝而不是承诺」同一取向——把失败固化成状态比重复失败更糟。\n */\nexport async function collectGitChurn(workspacePath: string): Promise<ChurnResult> {\n const cached = churnCache.get(workspacePath);\n if (cached && Date.now() - cached.timestamp < CHURN_CACHE_TTL_MS) return cached.result;\n\n const result = await collectGitChurnUncached(workspacePath);\n\n if (result.available) {\n if (churnCache.size >= CHURN_CACHE_MAX) {\n const oldest = churnCache.keys().next().value;\n if (oldest !== undefined) churnCache.delete(oldest);\n }\n churnCache.set(workspacePath, { result, timestamp: Date.now() });\n }\n return result;\n}\n\n/** 无缓存的采集实现;测试直接调它来验证「缓存不改变结果」之外的真实行为。 */\nexport async function collectGitChurnUncached(workspacePath: string): Promise<ChurnResult> {\n const baseArgs = ['-C', workspacePath, '-c', 'core.quotepath=false'];\n\n // 先拿 HEAD:即使 log 失败,revision 也能告诉用户「这份统计基于哪个提交」。\n let revision: string | null = null;\n try {\n const head = await execFileAsync('git', [...baseArgs, 'rev-parse', 'HEAD'], {\n timeout: CHURN.TIMEOUT_MS,\n maxBuffer: 1024 * 1024,\n windowsHide: true,\n });\n revision = head.stdout.trim() || null;\n } catch {\n // HEAD 取不到不阻断 log(例如空仓库);log 自己会给出更准确的原因。\n }\n\n try {\n const { stdout } = await execFileAsync(\n 'git',\n [\n ...baseArgs,\n 'log',\n '--name-only',\n '--format=%x1e',\n '--no-renames',\n `--max-count=${CHURN.MAX_COMMITS}`,\n ],\n {\n timeout: CHURN.TIMEOUT_MS,\n maxBuffer: CHURN.MAX_OUTPUT_BYTES,\n windowsHide: true,\n },\n );\n\n const parsed = parseGitLogNames(stdout);\n // `--max-count` 用尽且仓库提交数确实更多时,统计是不完整的——显式标出来,\n // 面板据此说明「只覆盖最近 N 个提交」。\n const truncated = parsed.commitCount >= CHURN.MAX_COMMITS;\n\n if (parsed.commitCount === 0 && Object.keys(parsed.byFile).length === 0) {\n const result = empty();\n result.revision = revision;\n return result;\n }\n\n return {\n byFile: parsed.byFile,\n commitCount: parsed.commitCount,\n revision,\n truncated,\n available: true,\n error: null,\n };\n } catch (e) {\n const reason = describeError(e);\n log.warn('collectGitChurn failed', { workspacePath, reason });\n return failed(reason);\n }\n}\n","/**\n * git 文件级差异采集器(P0-D,cycle-105 真机发现)。\n *\n * ## 为什么需要它\n *\n * 升级前的 `/diff` 把 `workspacePath@commit` 当仓库路径去查 `.codegraph/codegraph.db`,\n * 那个路径永远不存在,于是路由**恒返回空 diff 且 HTTP 200**——假成功。详见\n * `src/shared/gitFileDiff.ts` 文件头的完整推导。\n *\n * 本模块只负责跑 git 子进程,把原始输出交给 `parseGitRawDiff` 解析。所有「怎么理解输出」\n * 的逻辑都在纯函数层,因此这里的职责被压到最小:起进程、控超时、兜错误。\n *\n * ## 与 `gitChurn.ts` 共用同一条硬约束\n *\n * **绝不用 `shell: true`。** P0-A(cycle-105 真机)已经证明:`execFile` 在 `shell: true`\n * 下把参数拼成一条命令行字符串而不做转义,工作区路径含空格就会被断开——\n * `git -C C:\\Users\\me\\My Projects\\repo` → `fatal: cannot change to 'C:\\Users\\me\\My'`。\n * 该失败是**永久性**的,不是瞬时超时。\n *\n * ## 失败必须可见\n *\n * 本函数永不抛异常给调用方:git 没装 / 不是仓库 / 提交不存在 / 超时 / 输出超限,\n * 一律返回 `available: false` 并带上人类可读的 `error`。路由把它原样发给客户端,\n * 客户端据此显示「对比未完成」,而不是把一次失败渲染成「两版本没有差异」。\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { scoped } from '../shared/Logger.ts';\nimport {\n GIT_DIFF,\n parseGitRawDiff,\n type GitFileChange,\n type GitFileDiffResult,\n} from '../shared/gitFileDiff.ts';\n\nconst log = scoped('host:git-diff');\nconst execFileAsync = promisify(execFile);\n\n/**\n * 采集结果的短 TTL 缓存(按「工作区 + 两个引用」三元组)。\n *\n * 一次 `git diff` 在大型仓库上可能几百毫秒;面板重复提交同一对引用(用户反复点「对比」\n * 或改一下别的输入再改回来)不该重复起子进程。键里必须**同时**包含三个维度:\n * 只按工作区缓存会让 `A→B` 的结果冒充 `C→D`(与 P0-R16-02 家族「校验器与被校验对象\n * 无关」同一形态的缺陷,只是这里表现为「缓存与请求无关」)。\n *\n * 有界 Map + 主动淘汰:工作区路径与引用都来自请求参数,无界缓存等于把内存占用交给调用方。\n */\ninterface DiffCacheEntry { result: GitFileDiffResult; timestamp: number; }\nconst diffCache = new Map<string, DiffCacheEntry>();\nconst DIFF_CACHE_MAX = 16;\n/** 缓存有效期:短到能反映新的提交,长到足以吸收一次面板操作里的重复请求。 */\nconst DIFF_CACHE_TTL_MS = 30_000;\n\n/** 清空缓存(测试与工作区切换时使用;也让「采集器有缓存」这件事可被观测)。 */\nexport function clearGitDiffCache(): void {\n diffCache.clear();\n}\n\nfunction cacheKey(workspacePath: string, from: string, to: string): string {\n return `${workspacePath}\\u0001${from}\\u0001${to}`;\n}\n\n/** 采集失败时的空结果。`error` 是原因,其余字段是「什么都没有」的诚实表达。 */\nfunction failed(error: string, from: string, to: string): GitFileDiffResult {\n return {\n changes: [],\n available: false,\n error,\n truncated: false,\n revisions: { from, to },\n };\n}\n\n/** 归一化原因:把 git 的 stderr 压成一行,避免把整段帮助文本写进响应。 */\nfunction describeError(e: unknown): string {\n if (e instanceof Error) {\n const err = e as { stderr?: string; killed?: boolean; signal?: string; code?: string | number };\n const stderr = err.stderr;\n if (stderr) {\n const firstLine = stderr.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? '';\n if (firstLine.trim()) return firstLine.trim();\n }\n // 超时/被杀时 `stderr` 是空串、`e.message` 只有一条 `Command failed: git ...` 命令回显,\n // 对用户毫无信息量。显式识别并给出可读原因,否则「diff 不可用」永远无法排查。\n if (err.killed || err.signal) {\n return `git diff 超时(>${GIT_DIFF.TIMEOUT_MS}ms)或被杀(signal=${err.signal ?? 'unknown'})`;\n }\n if (err.code === 'ENOENT') return '找不到 git 可执行文件(未安装或不在 PATH 中)';\n const firstMessageLine = e.message.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? e.message;\n return firstMessageLine.trim();\n }\n return String(e);\n}\n\n/**\n * 采集 `from..to` 之间的文件变更清单(带短 TTL 缓存)。\n *\n * 用 `-z` 而不是默认的换行分隔:文件名里可以包含空格、引号、非 ASCII,换行格式下 git 会\n * 用 C 风格转义(`\"\\346\\226\\207...\"`)输出,解析那套转义等于重写一遍 git 的 quotepath 逻辑。\n * `-z` 给的是**原样字节**,路径永远不会被转义。\n *\n * `--no-renames`:重命名在 `--raw` 下会输出成 `R100\\0old\\0new`,是**三段**而不是两段。\n * 文件级 diff 只需要知道「这两个路径都变了」,禁用重命名检测后 git 会把它拆成 D + A,\n * 解析器就只需处理「状态行 + 一个路径」这一种形状。\n */\nexport async function collectGitFileDiff(\n workspacePath: string,\n from: string,\n to: string,\n): Promise<GitFileDiffResult> {\n const key = cacheKey(workspacePath, from, to);\n const cached = diffCache.get(key);\n if (cached && Date.now() - cached.timestamp < DIFF_CACHE_TTL_MS) return cached.result;\n\n const result = await collectGitFileDiffUncached(workspacePath, from, to);\n\n // 只缓存**成功**结果:失败不缓存,否则一次瞬时失败会让用户在 30 秒内反复看到\n // 「对比未完成」,而期间 git 其实早就可用了。把失败固化成状态比重复失败更糟。\n if (result.available) {\n if (diffCache.size >= DIFF_CACHE_MAX) {\n const oldest = diffCache.keys().next().value;\n if (oldest !== undefined) diffCache.delete(oldest);\n }\n diffCache.set(key, { result, timestamp: Date.now() });\n }\n return result;\n}\n\n/** 无缓存的采集实现;测试直接调它来验证真实行为。 */\nexport async function collectGitFileDiffUncached(\n workspacePath: string,\n from: string,\n to: string,\n): Promise<GitFileDiffResult> {\n const baseArgs = ['-C', workspacePath, '-c', 'core.quotepath=false'];\n\n try {\n const { stdout } = await execFileAsync(\n 'git',\n [\n ...baseArgs,\n 'diff',\n '--raw',\n '--no-renames',\n '-z',\n from,\n to,\n // `--` 之后是 pathspec;这里刻意留空,但**必须**有这个分隔符:\n // 否则一个长得像文件名的引用(例如恰好存在名为 `HEAD` 的文件)会被 git\n // 当成 pathspec 而不是 revision,导致「diff 忽然返回空」。\n '--',\n ],\n {\n timeout: GIT_DIFF.TIMEOUT_MS,\n maxBuffer: GIT_DIFF.MAX_OUTPUT_BYTES,\n windowsHide: true,\n },\n );\n\n const allChanges = parseGitRawDiff(stdout);\n // 截断必须显式标出:一份被截断的清单看起来和完整清单一模一样,用户会以为\n // 「只改了这 5000 个文件」而不是「还有更多没显示」。\n const truncated = allChanges.length > GIT_DIFF.MAX_FILES;\n const changes: GitFileChange[] = truncated ? allChanges.slice(0, GIT_DIFF.MAX_FILES) : allChanges;\n\n return {\n changes,\n available: true,\n error: null,\n truncated,\n revisions: { from, to },\n };\n } catch (e) {\n const reason = describeError(e);\n log.warn('collectGitFileDiff failed', { workspacePath, from, to, reason });\n return failed(reason, from, to);\n }\n}\n","import fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { VALIDATION, AST, ERROR_CODES } from '../../shared/constants.ts';\r\nimport { RepoId } from '../../types/index.ts';\r\nimport { parsePackageJson, parseCargoToml, parseGoMod, dependencyGraphToGraphData } from '../../adapters/DependencyAdapter.ts';\r\nimport { TreeSitterAdapter } from '../../adapters/TreeSitterAdapter.ts';\r\nimport { computeContentHash } from '../../shared/graphFingerprint.ts';\r\nimport { fileChangesToDiffNodes } from '../../shared/gitFileDiff.ts';\r\nimport { collectGitChurn } from '../gitChurn.ts';\r\nimport { collectGitFileDiff } from '../gitDiff.ts';\r\nimport {\r\n readBody, parseJsonBody, extractStringField,\r\n isSafeGitRef, sendJson, sendError,\r\n} from '../HttpHelpers.ts';\r\nimport {\r\n withRouteMiddleware, resolveAndValidateRelativePath, validateWorkspacePath,\r\n isSafeRelativePath, checkEtag, etagHeaders, type RouteContext, log,\r\n} from './shared.ts';\r\n\r\n/**\r\n * `/dependencies` 的内容校验器(P3-R27-04 决策矩阵中唯一\"按磁盘内容计算\"的 GET 分析路由)。\r\n *\r\n * 身份维度刻意取**请求方工作区 + 三个 manifest 文件的原始内容**,而不是缓存图槽\r\n * (`resourceEtag`)——`/dependencies` 的载荷来自磁盘上的 package.json / Cargo.toml / go.mod,\r\n * 与 `lastGraphData` 槽不是同一份资源。用槽的 revision 会复现 P0-R16-02 家族缺陷:\r\n * \"校验器与被校验资源无关\"(图变了 ⇒ manifest 没变也 200;manifest 变了 ⇒ 图没变就假 304)。\r\n *\r\n * 哈希输入是**解析前的原始内容**(含文件名标记),比哈希响应体便宜且等价:\r\n * 解析器是确定性的,相同原始内容必然产出相同载荷。文件增删(ENOENT 变化)同样改变拼接串。\r\n */\r\nexport function computeDependenciesEtag(workspacePath: string, manifestInputs: string[]): string {\r\n // 与 computeGraphEtag 同一 W/\"...\" 弱校验器形状;computeContentHash 自带版本前缀。\r\n return `W/\"${computeContentHash([workspacePath, ...manifestInputs].join('\\u0001'))}\"`;\r\n}\r\n\r\nexport function registerDependenciesRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/dependencies';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const workspacePath = await validateWorkspacePath(ctx, deps, req);\r\n if (workspacePath === '__denied__') {\r\n sendError(res, 403, ERROR_CODES.WORKSPACE_PATH_NOT_ALLOWED, req, corsConfig);\r\n return;\r\n }\r\n if (!workspacePath) {\r\n sendJson(res, 200, { nodes: [], edges: [], metadata: { repoId: RepoId('empty'), timestamp: 0, nodeCount: 0, edgeCount: 0 } }, req, corsConfig);\r\n return;\r\n }\r\n\r\n const allDepNodes: ReturnType<typeof dependencyGraphToGraphData>['nodes'] = [];\r\n const allDepEdges: ReturnType<typeof dependencyGraphToGraphData>['edges'] = [];\r\n\r\n const depFiles: Array<{ filename: string; parser: (content: string) => ReturnType<typeof parsePackageJson> }> = [\r\n { filename: 'package.json', parser: parsePackageJson },\r\n { filename: 'Cargo.toml', parser: parseCargoToml },\r\n { filename: 'go.mod', parser: parseGoMod },\r\n ];\r\n\r\n // 校验器输入 = 请求方工作区 + 成功读到的每个 manifest 的原始内容(解析前)。\r\n // 读不到的文件不进输入:文件从\"存在\"变\"不存在\"时,manifestInputs 少一段 ⇒ 哈希变 ⇒ 200。\r\n const manifestInputs: string[] = [];\r\n for (const { filename, parser } of depFiles) {\r\n try {\r\n const filePath = path.join(workspacePath, filename);\r\n const content = await fs.readFile(filePath, 'utf-8');\r\n manifestInputs.push(`${filename}\\u0000${content}`);\r\n const depGraph = parser(content);\r\n const converted = dependencyGraphToGraphData(depGraph);\r\n allDepNodes.push(...converted.nodes);\r\n allDepEdges.push(...converted.edges);\r\n } catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') log.warn(`Failed to read ${filename}:`, e); }\r\n }\r\n\r\n // 304 判定必须在算载荷之前(响应根本不用算);校验值与 200 回写共用同一构造点。\r\n const depEtag = computeDependenciesEtag(workspacePath, manifestInputs);\r\n if (checkEtag(req, res, corsConfig, depEtag)) return;\r\n\r\n const dedupNodes = new Map<string, typeof allDepNodes[number]>();\r\n for (const n of allDepNodes) dedupNodes.set(n.id, n);\r\n const dedupEdges = new Map<string, typeof allDepEdges[number]>();\r\n for (const e of allDepEdges) {\r\n if (e.source !== e.target) dedupEdges.set(e.id, e);\r\n }\r\n\r\n sendJson(res, 200, {\r\n nodes: [...dedupNodes.values()],\r\n edges: [...dedupEdges.values()],\r\n metadata: {\r\n repoId: RepoId(workspacePath),\r\n timestamp: Date.now(),\r\n nodeCount: allDepNodes.length,\r\n edgeCount: allDepEdges.length,\r\n },\r\n }, req, corsConfig, etagHeaders(depEtag));\r\n } catch (e) {\r\n log.error('dependencies route failed', e);\r\n sendError(res, 500, ERROR_CODES.INTERNAL_ERROR, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: dependencies route');\r\n}\r\n\r\n/**\r\n * `/churn` 的校验器(研究报告 §六 #11 的取数面)。\r\n *\r\n * 身份维度 = **请求方工作区 + HEAD 提交**。刻意不用缓存图的 revision:\r\n * 变更频率只取决于 git 历史,与 `lastGraphData` 槽无关——拿槽的 revision 会复现\r\n * P0-R16-02 家族缺陷「校验器与被校验资源无关」(换图但 git 没变 ⇒ 假 200;\r\n * 有新提交但图没变 ⇒ 假 304,用户永远看不到新的变更频率)。\r\n */\r\nexport function computeChurnEtag(workspacePath: string, revision: string | null, available: boolean): string {\r\n // 采集失败也要有一个稳定的校验值:否则每次重试都 200,客户端无从判断「还是失败」。\r\n return `W/\"${computeContentHash([workspacePath, revision ?? 'none', String(available)].join('\\u0001'))}\"`;\r\n}\r\n\r\n/**\r\n * `GET /api/v1/codegraph/churn` — 工作区的 git 变更频率(研究报告 §六 #11)。\r\n *\r\n * 返回 `ChurnResult` 原样(`available` / `error` 都在载荷里):**采集失败必须可见**,\r\n * 客户端据此显示「未获取到变更频率(仅按依赖关系排序)」,而不是把它当「所有文件 0 次变更」。\r\n * 这与 `fetchGraphDiff` 失败返回 `null`(而不是空 diff)是同一条纪律。\r\n */\r\nexport function registerChurnRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/churn';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const workspacePath = await validateWorkspacePath(ctx, deps, req);\r\n if (workspacePath === '__denied__') {\r\n sendError(res, 403, ERROR_CODES.WORKSPACE_PATH_NOT_ALLOWED, req, corsConfig);\r\n return;\r\n }\r\n if (!workspacePath) {\r\n const noWorkspace = { byFile: {}, commitCount: 0, revision: null, truncated: false, available: false, error: 'no workspace' };\r\n const etag = computeChurnEtag('', null, false);\r\n if (checkEtag(req, res, corsConfig, etag)) return;\r\n sendJson(res, 200, noWorkspace, req, corsConfig, etagHeaders(etag));\r\n return;\r\n }\r\n\r\n const result = await collectGitChurn(workspacePath);\r\n const etag = computeChurnEtag(workspacePath, result.revision, result.available);\r\n if (checkEtag(req, res, corsConfig, etag)) return;\r\n sendJson(res, 200, result, req, corsConfig, etagHeaders(etag));\r\n } catch (e) {\r\n log.error('churn route failed', e);\r\n sendError(res, 500, ERROR_CODES.INTERNAL_ERROR, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: churn route');\r\n}\r\n\r\n/**\r\n * `POST /api/v1/codegraph/diff` —— 两个 git 引用之间的**文件级**图差异。\r\n *\r\n * ## 这一版修掉了两个真机 P0(cycle-105)\r\n *\r\n * ### P0-D:旧实现恒返回空 diff(假成功)\r\n *\r\n * 旧代码是 `fetchMergedGraph(invoke, `${workspacePath}@${fromCommit}`, 'both')`,即把\r\n * 「工作区路径 + `@` + 提交号」当成仓库标识去查 `.codegraph/codegraph.db`。该路径永远\r\n * 不存在,于是 `nodes: []` 而 HTTP 仍回 200。真机实测:两个真实提交之间改了 42 个文件,\r\n * 面板却显示「没有结构变更」。\r\n *\r\n * 根因是**数据面缺一个维度**:`codegraph.db` 索引的是当前工作树,`nodes`/`edges`/`files`\r\n * 三张表都没有 revision 列。所以「按提交取图」不可实现,不是实现得不好。\r\n *\r\n * 修法:改用 `git diff --raw --no-renames -z` 拿变更文件清单,产出**文件级** diff 节点,\r\n * id 用 `file:<相对路径>`——与画布上文件节点的 id 完全同一命名空间(探针实测 433/433),\r\n * 因此 `HighlightController.applyDiffMarkers` 的 `getElementById` 能真正命中。\r\n *\r\n * 刻意**不**退回「拿 tree-sitter 现场解析两个提交」:tree-sitter 的节点 id 是位置化的\r\n * (`${filePath}:${label}:${row}`),DB 的 id 是语义化的(`file:...` / `import:<hash>`),\r\n * 两者不可能对齐——那只是把「标记命中不了」挪个地方。\r\n *\r\n * ### P0-E:workspace 从 body 读不到(跨工作区泄漏)\r\n *\r\n * 客户端把 workspace 放在 JSON body,而 `resolveWorkspaceFromRequest` 只读 query,\r\n * 于是回落到宿主列表第一个工作区。真机实测请求 A 仓库却拿到 B 仓库的 repoId。\r\n * 修法见 `shared.ts` 的 `resolveWorkspaceFromRequest`:现在同时接受 body 里的 workspace。\r\n *\r\n * ## 失败语义\r\n *\r\n * git 不可用 / 引用不存在 / 超时 ⇒ 回 **500 `GRAPH_DIFF_FAILED`** 并带上原因,\r\n * 绝不回「空 diff + 200」。后者会让用户把一次失败读成「两版本完全一样」。\r\n */\r\nexport function registerDiffRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = '/api/v1/codegraph/diff';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, deps.config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const fromCommit = extractStringField(parsed, 'from', VALIDATION.MAX_GIT_REF_LENGTH);\r\n const toCommit = extractStringField(parsed, 'to', VALIDATION.MAX_GIT_REF_LENGTH);\r\n if (!fromCommit || !toCommit) {\r\n sendError(res, 400, ERROR_CODES.COMMITS_REQUIRED, req, corsConfig);\r\n return;\r\n }\r\n if (!isSafeGitRef(fromCommit) || !isSafeGitRef(toCommit)) {\r\n sendError(res, 400, ERROR_CODES.INVALID_GIT_REF, req, corsConfig);\r\n return;\r\n }\r\n\r\n // P0-E:把 body 里的 workspace 一并交给解析器。不传这一项就会静默回落到\r\n // 宿主列表第一个工作区——真机上表现为「对比了另一个项目」。\r\n const bodyWorkspace = extractStringField(parsed, 'workspace', VALIDATION.MAX_PATH_LENGTH);\r\n const workspacePath = await validateWorkspacePath(ctx, deps, req, bodyWorkspace);\r\n if (workspacePath === '__denied__') {\r\n sendError(res, 403, ERROR_CODES.WORKSPACE_PATH_NOT_ALLOWED, req, corsConfig);\r\n return;\r\n }\r\n if (!workspacePath) {\r\n sendJson(res, 200, { nodes: [], edges: [], metadata: { repoId: RepoId('empty'), timestamp: 0, nodeCount: 0, edgeCount: 0 } }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // P0-D:真正的数据面是 git 的文件清单,不是 codegraph.db(后者没有 revision 维度)。\r\n const collected = await collectGitFileDiff(workspacePath, fromCommit, toCommit);\r\n if (!collected.available) {\r\n // 失败必须可见:把 git 给出的原因原样带回客户端。\r\n sendJson(res, 500, {\r\n success: false,\r\n error: ERROR_CODES.GRAPH_DIFF_FAILED,\r\n message: collected.error ?? 'git diff failed',\r\n from: fromCommit,\r\n to: toCommit,\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n const nodes = fileChangesToDiffNodes(collected.changes);\r\n\r\n sendJson(res, 200, {\r\n nodes,\r\n // 文件级 diff 不产边:`git diff --raw` 只给文件清单,不给「哪两个文件之间新增了\r\n // 依赖」。硬编一条边等于编造数据。节点上的 `diffStatus` 已足够让画布亮起来。\r\n edges: [],\r\n metadata: {\r\n repoId: RepoId(workspacePath),\r\n timestamp: Date.now(),\r\n nodeCount: nodes.length,\r\n edgeCount: 0,\r\n // 三个字段刻意放在 metadata 里而不是顶层:客户端 `fetchGraphDiff` 只读\r\n // `nodes`/`edges`,其余信息(截断、粒度、回显的引用)由面板从顶层读。\r\n },\r\n // 显式告诉客户端「这份 diff 是文件级的」——面板据此把文案从\r\n // 「结构变更」改成「文件变更」,避免用户以为符号级也对比过了。\r\n granularity: 'file',\r\n truncated: collected.truncated,\r\n from: fromCommit,\r\n to: toCommit,\r\n }, req, corsConfig);\r\n } catch (e) {\r\n log.error('diff route failed', e);\r\n sendError(res, 500, ERROR_CODES.GRAPH_DIFF_FAILED, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: diff route');\r\n}\r\n\r\nexport function registerAstRoute(rc: RouteContext, sharedTreeSitterAdapter: TreeSitterAdapter): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/ast';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const filePath = extractStringField(parsed, 'filePath', VALIDATION.MAX_FILEPATH_LENGTH);\r\n if (!filePath) {\r\n sendError(res, 400, ERROR_CODES.FILEPATH_REQUIRED, req, corsConfig);\r\n return;\r\n }\r\n const realPath = await resolveAndValidateRelativePath(filePath, ctx, deps, req, res, corsConfig);\r\n if (!realPath) return;\r\n const content = await fs.readFile(realPath, 'utf-8');\r\n const MAX_AST_CONTENT = AST.MAX_CONTENT_BYTES;\r\n if (content.length > MAX_AST_CONTENT) {\r\n sendError(res, 413, ERROR_CODES.FILE_TOO_LARGE, req, corsConfig, `max ${MAX_AST_CONTENT} bytes`);\r\n return;\r\n }\r\n const astRoot = await sharedTreeSitterAdapter.parseToAst(filePath, content);\r\n if (!astRoot) {\r\n sendError(res, 500, ERROR_CODES.AST_PARSE_FAILED, req, corsConfig, `parseToAst returned null for ${filePath}`);\r\n return;\r\n }\r\n sendJson(res, 200, astRoot, req, corsConfig);\r\n } catch (e) {\r\n log.error('ast route failed', e);\r\n sendError(res, 500, ERROR_CODES.AST_PARSE_FAILED, req, corsConfig, e instanceof Error ? e.message : String(e));\r\n }\r\n }),\r\n }), 'codegraph: ast route');\r\n}\r\n\r\nexport function registerTreeSitterRoute(rc: RouteContext, sharedTreeSitterAdapter: TreeSitterAdapter): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx, config } = deps;\r\n const routePath = '/api/v1/codegraph/tree-sitter';\r\n\r\n ctx.effect(() => ctx.webServer.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('POST', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n try {\r\n const body = await readBody(req, config.maxBodyBytes);\r\n const parsed = parseJsonBody(body);\r\n const filePath = extractStringField(parsed, 'filePath');\r\n const content = extractStringField(parsed, 'content');\r\n if (!filePath || !content) {\r\n sendError(res, 400, ERROR_CODES.FILEPATH_AND_CONTENT_REQUIRED, req, corsConfig);\r\n return;\r\n }\r\n if (!isSafeRelativePath(filePath, VALIDATION.MAX_FILEPATH_LENGTH)) {\r\n sendError(res, 400, ERROR_CODES.INVALID_FILEPATH, req, corsConfig);\r\n return;\r\n }\r\n const MAX_TREE_SITTER_CONTENT = AST.MAX_TREE_SITTER_CONTENT_BYTES;\r\n if (content.length > MAX_TREE_SITTER_CONTENT) {\r\n sendError(res, 413, ERROR_CODES.CONTENT_TOO_LARGE, req, corsConfig, `max ${MAX_TREE_SITTER_CONTENT} bytes, got ${content.length}`);\r\n return;\r\n }\r\n const result = await sharedTreeSitterAdapter.parseFile(filePath, content);\r\n // P0-6: contentHash lets a caller recognise an identical parse request (and lets a client\r\n // key its AST cache on content rather than on the file path alone).\r\n sendJson(res, 200, { ...result, contentHash: computeContentHash(content) }, req, corsConfig);\r\n } catch (e) {\r\n log.error('tree-sitter route failed', e);\r\n sendError(res, 500, ERROR_CODES.INTERNAL_ERROR, req, corsConfig);\r\n }\r\n }),\r\n }), 'codegraph: tree-sitter route');\r\n}","import { readFileSync, existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { join } from 'node:path';\nimport { LAYOUT_WORKER } from '../../shared/constants.ts';\nimport type { IncomingMessage, ServerResponse } from '../../types/index.ts';\nimport { buildCorsHeaders } from '../HttpHelpers.ts';\nimport { withRouteMiddleware, log, type RouteContext } from './shared.ts';\n\n/**\n * Candidate locations of the built layout worker.\n *\n * Packaged installs run the host as `dist/index.js`, so the worker sits next to it under\n * `dist/client/worker/`. When the plugin runs straight from a checkout (`pnpm dev`, and every test\n * run) the same file exists relative to the repository root instead. Both are tried in order and\n * the first hit wins, so one route serves every layout.\n */\nexport function workerFileCandidates(): string[] {\n const besideBundle = fileURLToPath(new URL('./client/worker/layoutWorker.js', import.meta.url));\n const fromRepoRoot = join(process.cwd(), LAYOUT_WORKER.ARTIFACT);\n return [besideBundle, fromRepoRoot];\n}\n\n/**\n * Write the worker bundle, or an honest 404 explaining that it was never built.\n *\n * Returns true when the bundle was served. The candidate list is injectable so both branches stay\n * unit-testable without depending on whether the caller has run a build.\n */\nexport function serveLayoutWorker(\n req: IncomingMessage,\n res: ServerResponse,\n corsConfig: RouteContext['corsConfig'],\n candidates: string[] = workerFileCandidates(),\n): boolean {\n const found = candidates.find((candidate) => existsSync(candidate));\n if (!found) {\n // Honest failure: the client only retries a bounded number of times and then keeps the\n // main-thread layout, but the log must say why the worker is missing.\n log.warn('layout worker bundle missing', { tried: candidates, artifact: LAYOUT_WORKER.ARTIFACT });\n res.writeHead(404, { 'content-type': 'application/json', ...buildCorsHeaders(req, corsConfig) });\n res.end(JSON.stringify({\n success: false,\n error: 'WORKER_NOT_BUILT',\n message: `layout worker bundle not found; run the build to produce ${LAYOUT_WORKER.ARTIFACT}`,\n }));\n return false;\n }\n\n const source = readFileSync(found, 'utf8');\n res.writeHead(200, {\n 'content-type': 'text/javascript; charset=utf-8',\n 'content-length': String(Buffer.byteLength(source)),\n 'cache-control': 'no-cache',\n ...buildCorsHeaders(req, corsConfig),\n });\n res.end(source);\n return true;\n}\n\n/**\n * Serve the layout worker bundle.\n *\n * A browser Worker is loaded from a URL, so the bundle has to be reachable over HTTP. The\n * alternative the old code assumed — that DSH exposes the plugin's own files under\n * `/plugins/<package>/...` — does not hold: no such asset route exists, so every worker request\n * answered 404 and each layout quietly fell back to the main thread. This route uses the plugin's\n * own HTTP surface, which is already the origin of every other request the client makes.\n */\nexport function registerLayoutWorkerRoute(rc: RouteContext): void {\n const { deps, corsConfig, rateLimiter } = rc;\n const { ctx } = deps;\n const routePath = LAYOUT_WORKER.ROUTE_PATH;\n\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: routePath,\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, (req, res) => {\n serveLayoutWorker(req, res, corsConfig);\n }),\n }), 'codegraph: layout worker route');\n}\n","import { computeContentHash } from '../../shared/graphFingerprint.ts';\nimport { ERROR_CODES } from '../../shared/constants.ts';\nimport type { GraphData, GraphEdge, GraphNode, IncomingMessage, ServerResponse } from '../../types/index.ts';\nimport {\n tokenizeQuery,\n selectSeeds,\n renderSubgraphText,\n // 库级深度上界(cycle-112 继承项 E):此前这里镜像 `LIBRARY_MAX_DEPTH: 3`,\n // 库改上界时镜像会静默撒谎。现在直接引用库导出的常量。\n MAX_QUERY_DEPTH,\n} from '../../xuanji/query/keyword-query.ts';\nimport { fetchMergedGraph } from '../../tools.ts';\nimport { sendJson, sendError } from '../HttpHelpers.ts';\nimport {\n withRouteMiddleware,\n validateWorkspacePath,\n getWorkspaceScopedGraphData,\n checkEtag,\n etagHeaders,\n type RouteContext,\n log,\n} from './shared.ts';\n\n/**\n * `GET /api/v1/codegraph/query` —— 关键词图检索。\n *\n * ## 为什么需要这条路由(本轮缺口 N-5)\n *\n * 关键词检索的**算法**早就在库里了:`src/xuanji/query/keyword-query.ts` 的\n * `tokenizeQuery` / `selectSeeds` / `renderSubgraphText`,`src/tools.ts` 的\n * `graph_query` 工具也一直在用它们(graphify 研究「把图查询当成一等输出」的落地)。\n * 但 `grep -rn \"keywordQuery|graph_query\" src/client/` = 0 命中——这套能力**只有 LLM 面,\n * 没有人类面**。研究结论只落地了一半,而且是看不见的那一半。\n *\n * 本模块只做三件宿主该做的事:**取图 → 调库 → 序列化 + 定校验器**。\n * 算法一行都不复制:token 估算(CJK 1 字 = 1 token 的口径)、种子分级与断层截断、\n * 超预算三分支全部留在库里,工具面与界面面因此共用同一份实现,不会出现\n * 「工具说截断了、面板说没截断」这种两套口径。\n *\n * ## 响应为什么必须是结构化子图而不是纯文本\n *\n * `graph_query` 面向 LLM,文本就够。面向人则不够:**人在图上要看见高亮**。\n * 因此响应同时给出 `text`(与工具同一份渲染结果)与 `nodes` / `edges`(可画高亮),\n * 两者由同一份渲染结果派生(见 `extractSubgraphFromText`),不是两套计算。\n *\n * ## 校验器(本仓最深的坑家族,P0-R16-02 / P3-R18-02)\n *\n * 三条纪律一条都不能少:\n * 1. **身份维度必须覆盖全部会改变响应的请求参数**:`工作区 + q + depth + tokenBudget + 图 revision`。\n * 少任一维 ⇒ 两个不同请求算出同一个校验值 ⇒ 假 304 ⇒ 客户端把 A 的答案当成 B 的答案\n * 永久显示(且没有任何错误可见)。\n * 2. **200 响应也必须回写 etag**(`etagHeaders`)。只写 304 分支正是 cycle-91 的死代码缺陷:\n * 客户端手里永远没有可回传的值,304 分支可达但永不命中。\n * 3. **304 与网络失败必须区分**:本路由只回 304 或 200/4xx/5xx,客户端侧用独立旁路表达 304。\n *\n * 为什么不用 `etagGuard` / `resourceEtag`:那两兄弟的校验值来自**缓存图槽的 revision**\n * 与 `GraphEtagResource` 资源维度。`/query` 的载荷是「请求参数 + 图内容」的派生结果,\n * 与 `/dependencies`(按磁盘 manifest 内容定域)、`/churn`(按 git 输出定域)同类:\n * 校验值必须由**本路由自己**按请求内容构造,套用槽的 revision 会得到\n * 「两个不同 q 共用一个校验值」——这正是 `GraphEtagResource` 用编译期必填参数\n * 想根除的那类缺陷换了个方向。\n */\nexport const QUERY = Object.freeze({\n /**\n * 查询串长度上限。\n *\n * 与 `VALIDATION.MAX_FILEPATH_LENGTH`(512)同值,但**刻意不复用那个常量**:它是\n * 「文件路径」的界,语义不同;共用会让将来调路径上限时静默改掉关键词上限。\n * 契约测试钉住 `<= 512`(一个查询串不该比一个路径还长)。\n */\n MAX_QUERY_LENGTH: 512,\n /** 展开跳数下界(0 = 只要种子本身)。 */\n MIN_DEPTH: 0,\n /** 调用方**允许请求**的最大跳数。 */\n MAX_DEPTH: 5,\n DEFAULT_DEPTH: 2,\n /**\n * 库实际生效的最大跳数 —— **直接引用库导出的 `MAX_QUERY_DEPTH`**(cycle-112 继承项 E)。\n *\n * cycle-111 时这里是镜像常量 `3`(库未导出),靠「depth=4 与 depth=5 载荷相同」的\n * 行为用例提醒同步。镜像已在库导出后消除:改库即改这里,不存在漂移。\n * 行为用例保留(它钉的是端到端语义,仍有效)。\n */\n LIBRARY_MAX_DEPTH: MAX_QUERY_DEPTH,\n MIN_TOKEN_BUDGET: 50,\n MAX_TOKEN_BUDGET: 4000,\n DEFAULT_TOKEN_BUDGET: 800,\n /**\n * 种子数上限。库的缺省是 3,但界面需要「多词查询时每词保底一个」的可见性\n * (`selectSeeds` 的保底条目可以越过 maxK),因此给到 5 让用户看到更多解释。\n */\n MAX_SEEDS: 5,\n ROUTE_PATH: '/api/v1/codegraph/query',\n} as const);\n\n/** `/query` 一次请求的有效参数(已规范化)。 */\nexport interface KeywordQueryInput {\n readonly query: string;\n /** 调用方请求的跳数(原样回显在 `meta.depth` 之前会被钳到库上限,见 `depthEffective`)。 */\n readonly depth: number;\n /** 库实际生效的跳数。 */\n readonly depthEffective: number;\n readonly tokenBudget: number;\n readonly workspacePath: string;\n}\n\n/**\n * `/query` 的校验器(唯一构造点)。\n *\n * 五个身份维度缺一不可,且**全部参与拼接**:\n * - `workspacePath`:宿主只有一个图槽,槽里可能是别的工作区的图;不含工作区维度时\n * A 的校验值可以让 B 的请求收到 304(P0-R16-02 的跨工作区分支);\n * - `query`:换词就是换问题;\n * - `depth`:跳数直接决定子图规模;\n * - `tokenBudget`:预算直接决定渲染到第几个节点(同一个 q 换预算得到**不同文本**);\n * - `revision`:同一请求在图上变了之后必须失配,否则真实更新被静默吞掉。\n *\n * `depth` 传的是**生效值**而不是请求值:`depth=4` 与 `depth=5` 在库里被钳到 3,\n * 载荷逐字节相同,共用校验值是正确的(同一份载荷本来就该有同一个校验值)。\n * 这不是\"少一维\",而是\"这一维的取值域被库归一化了\"——由 `depthEffective` 显式承担。\n *\n * `revision` 取图槽的 `metadata.timestamp`:它是宿主侧唯一的版本信号(`computeGraphEtag`\n * 也用它)。两个不同工作区的图理论上可能撞到同一毫秒,所以 `workspacePath` 必须一起进哈希。\n */\nexport function computeQueryEtag(input: {\n readonly workspacePath: string;\n readonly query: string;\n readonly depth: number;\n readonly tokenBudget: number;\n readonly revision: number;\n}): string {\n // 与 `computeGraphEtag` / `computeDependenciesEtag` 同一 `W/\"...\"` 弱校验器形状。\n // `\\u0001` 是不可见分隔符,保证「q='a\\u00012' + depth=3」与「q='a' + depth=…」不会拼出同一串。\n return `W/\"${computeContentHash([\n input.workspacePath,\n input.query,\n String(input.depth),\n String(input.tokenBudget),\n String(input.revision),\n ].join('\\u0001'))}\"`;\n}\n\n/** 解析后的子图:与渲染文本严格同集(见 `extractSubgraphFromText`)。 */\nexport interface RenderedSubgraphSlice {\n readonly nodes: GraphNode[];\n readonly edges: GraphEdge[];\n}\n\n/** `NODE <id> <label...>` / `EDGE <src> -> <tgt> (<type>)` 两行形状(库的文本契约)。 */\nconst NODE_LINE_RE = /^NODE (\\S+)(?: (.*))?$/;\nconst EDGE_LINE_RE = /^EDGE (\\S+) -> (\\S+) \\(([^)]+)\\)$/;\n\n/**\n * 从**库自己渲染出的文本**里取回 id 集合,再映射成完整节点/边对象。\n *\n * ## 为什么不重算一遍 BFS\n *\n * 「种子 → 有界 BFS → 按跳数/度数排序 → 预算内截断」全部住在\n * `renderSubgraphText` 里。在 route 里再走一遍 BFS 就是**复制实现**:两份一旦漂移,\n * 面板高亮的节点与文本里列出的节点会不一致,而且没有任何测试会红(两边各自自洽)。\n * 文本是库对外的唯一输出契约,解析它得到的就是「库认为该显示的集合」——\n * 与工具面读到的完全同一份。\n *\n * ## 稳健性\n *\n * 解析结果必须与 `RenderedSubgraph.nodeCount` / `edgeCount` 相等,这条由\n * `tests/unit/queryRoute.test.ts` 钉死:库的文本格式一旦变了,用例立刻红,\n * 而不是面板静默少画几个高亮(那在画面上与\"本来就没有这些节点\"无法区分)。\n * 解析不出完整节点(id 不在图里)时**丢弃该行**而不是编造对象——\n * 宁少不多,因为编造出来的节点 id 在画布上选不中。\n */\nexport function extractSubgraphFromText(text: string, graph: GraphData): RenderedSubgraphSlice {\n const nodeIds: string[] = [];\n const edgeTriples: Array<{ source: string; target: string; type: string }> = [];\n for (const line of text.split('\\n')) {\n const nodeMatch = NODE_LINE_RE.exec(line);\n if (nodeMatch) {\n const id = nodeMatch[1];\n if (id && id !== 'none') nodeIds.push(id);\n continue;\n }\n const edgeMatch = EDGE_LINE_RE.exec(line);\n if (edgeMatch) {\n const [, source, target, type] = edgeMatch;\n if (source && target && type) edgeTriples.push({ source, target, type });\n }\n }\n\n const byId = new Map<string, GraphNode>();\n for (const node of graph.nodes) byId.set(node.id as string, node);\n const nodes: GraphNode[] = [];\n for (const id of nodeIds) {\n const node = byId.get(id);\n if (node) nodes.push(node);\n }\n\n // 边按 (source, target, type) 三元组回查:文本里没有边 id,而同一对端点之间\n // 可以有多条不同类型的边(call + import),所以三维都要参与匹配。\n const edges: GraphEdge[] = [];\n for (const triple of edgeTriples) {\n const match = graph.edges.find(\n (edge) =>\n (edge.source as string) === triple.source &&\n (edge.target as string) === triple.target &&\n (edge.type as string) === triple.type,\n );\n if (match) edges.push(match);\n }\n return { nodes, edges };\n}\n\n/** 解析并规范化查询参数;非法时已回 400 并返回 `null`。 */\nfunction parseQueryParams(\n req: IncomingMessage,\n res: ServerResponse,\n rc: RouteContext,\n): Omit<KeywordQueryInput, 'workspacePath'> | null {\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\n\n // `q` 必填:没有查询串就没有\"检索\",回一份空结果会让用户以为「图里什么都没有」。\n const rawQuery = url.searchParams.get('q');\n const query = (rawQuery ?? '').trim();\n if (query.length === 0) {\n sendError(res, 400, ERROR_CODES.FILEPATH_REQUIRED, req, rc.corsConfig, 'q is required');\n return null;\n }\n if (query.length > QUERY.MAX_QUERY_LENGTH) {\n sendError(res, 400, ERROR_CODES.CONTENT_TOO_LARGE, req, rc.corsConfig, `q exceeds ${QUERY.MAX_QUERY_LENGTH} chars`);\n return null;\n }\n\n // `depth` / `tokenBudget`:**越界一律 400,不做静默钳制**。\n // 静默钳制会让用户以为「我要了 5 跳,拿到的是 5 跳」——这正是本仓反复记录的\n // 「降级默认必须拒绝,不能许诺」;唯一例外是库自身的 3 跳上限(没有可选余地),\n // 那一条通过 `meta.depthEffective` 显式披露。\n const rawDepth = url.searchParams.get('depth');\n const depth = rawDepth === null ? QUERY.DEFAULT_DEPTH : Number(rawDepth);\n if (!Number.isInteger(depth) || depth < QUERY.MIN_DEPTH || depth > QUERY.MAX_DEPTH) {\n sendError(res, 400, ERROR_CODES.INVALID_CONFIG, req, rc.corsConfig,\n `depth must be an integer in [${QUERY.MIN_DEPTH}, ${QUERY.MAX_DEPTH}]`);\n return null;\n }\n\n const rawBudget = url.searchParams.get('tokenBudget');\n const tokenBudget = rawBudget === null ? QUERY.DEFAULT_TOKEN_BUDGET : Number(rawBudget);\n if (!Number.isInteger(tokenBudget) || tokenBudget < QUERY.MIN_TOKEN_BUDGET || tokenBudget > QUERY.MAX_TOKEN_BUDGET) {\n sendError(res, 400, ERROR_CODES.INVALID_CONFIG, req, rc.corsConfig,\n `tokenBudget must be an integer in [${QUERY.MIN_TOKEN_BUDGET}, ${QUERY.MAX_TOKEN_BUDGET}]`);\n return null;\n }\n\n return {\n query,\n depth,\n depthEffective: Math.min(depth, QUERY.LIBRARY_MAX_DEPTH),\n tokenBudget,\n };\n}\n\n/**\n * 取本次请求的图:**优先缓存图槽,槽为空才回源**。\n *\n * 槽的读取必须走 `getWorkspaceScopedGraphData`,不能裸读 `deps.getLastGraphData()`:\n * 宿主只有一个图槽,槽里可能装着另一个工作区的图(P0-B)。裸读会让 B 工作区的关键词\n * 检索返回 A 工作区的节点——一个**静默的错误答案**,比空结果危险得多。\n *\n * 回源走 `fetchMergedGraph(rc.invokeUpstream, repoId, 'auto')`,与 `graph_query` 工具同一\n * 数据通道(研究结论要求「不要另造数据通道」:两条通道一旦语义不同,\n * 工具说查得到、面板说查不到,而谁也不知道哪条是对的)。\n */\nasync function resolveGraph(\n rc: RouteContext,\n workspacePath: string,\n): Promise<{ graph: GraphData; source: 'slot' | 'fetched' } | null> {\n const slot = getWorkspaceScopedGraphData(rc.deps, workspacePath);\n if (slot) return { graph: slot, source: 'slot' };\n try {\n const fetched = await fetchMergedGraph(rc.invokeUpstream, workspacePath, 'auto');\n return { graph: fetched, source: 'fetched' };\n } catch (e) {\n log.error('query route: upstream graph fetch failed', e);\n return null;\n }\n}\n\n/** 渲染 + 组装响应体(200 与 304 都不需要它以外的东西)。 */\nfunction buildPayload(\n input: KeywordQueryInput,\n graph: GraphData,\n source: 'slot' | 'fetched',\n): Record<string, unknown> {\n const terms = tokenizeQuery(input.query);\n\n // 查询串里没有任何可检索词(例如单个 ASCII 字符,被库的 `MIN_TERM_LENGTH` 丢弃):\n // 这不是错误,是「这一串问不出东西」,必须与「问了但图上没有」区分开。\n if (terms.length === 0) {\n return {\n query: input.query,\n terms,\n seeds: [],\n nodes: [],\n edges: [],\n text: 'No searchable term in query (need at least one letter/digit or CJK character).',\n tokens: 0,\n truncated: false,\n reason: 'fits',\n meta: {\n depth: input.depth,\n depthEffective: input.depthEffective,\n tokenBudget: input.tokenBudget,\n source,\n revision: graph.metadata.timestamp,\n reason: 'no-term',\n },\n };\n }\n\n const seeds = selectSeeds(\n graph.nodes.map((node) => ({\n id: node.id,\n label: node.label,\n ...(node.filePath === undefined ? {} : { filePath: node.filePath }),\n })),\n terms,\n { maxK: QUERY.MAX_SEEDS },\n );\n\n // 刻意**不写类型注解** `: RenderedSubgraph`:`tests/unit/xuanjiReachability.test.ts`\n // 的双向守卫把「生产代码显式 import 库类型名」当作可达性信号,写注解会逼着那边删掉\n // 一条豁免(那是 T1 的写域),而让类型推断推出同一形状就没有这个副作用。\n const rendered = renderSubgraphText(\n {\n nodes: graph.nodes.map((node) => ({\n id: node.id,\n label: node.label,\n ...(node.filePath === undefined ? {} : { filePath: node.filePath }),\n })),\n edges: graph.edges.map((edge) => ({ source: edge.source, target: edge.target, type: edge.type })),\n },\n seeds,\n { depth: input.depthEffective, tokenBudget: input.tokenBudget },\n );\n\n // 高亮集合直接由库的渲染结果派生(见 `extractSubgraphFromText` 的注释)。\n const slice = extractSubgraphFromText(rendered.text, graph);\n\n return {\n query: input.query,\n terms,\n seeds: seeds.map((seed) => ({\n id: seed.id,\n label: seed.label,\n score: seed.score,\n matchedTerms: [...seed.matchedTerms],\n })),\n nodes: slice.nodes,\n edges: slice.edges,\n // 截断必须显式:`reason` 与 `truncated` 都由库给出,route 不重新判定。\n // 面板据此渲染「这不是完整结果」,与 graphify「截断必须显式到不可能被误读」同一条。\n text: rendered.text,\n tokens: rendered.tokens,\n truncated: rendered.truncated,\n reason: rendered.reason,\n meta: {\n depth: input.depth,\n depthEffective: input.depthEffective,\n tokenBudget: input.tokenBudget,\n source,\n revision: graph.metadata.timestamp,\n reason: seeds.length === 0 ? 'no-seed' : rendered.reason,\n },\n };\n}\n\n/**\n * 注册 `GET /api/v1/codegraph/query`。\n *\n * 调用方:`src/host/routes.ts` 的 `registerRoutes`(本轮由 Lead 接线,见任务报告)。\n * 建议插在 `registerChurnRoute(rc)` **之前**:`registerRoutes` 的调用顺序就是\n * `webServer.register` 的调用顺序,既有用例大量用「第 N 次注册」(`getHandler(ctx, N)`)定位,\n * 插在中间会让十几条无关用例整体错位一格;churn 之前是\"末尾附近且不改变 churn 的语义位置\"。\n */\nexport function registerKeywordQueryRoute(rc: RouteContext): void {\n const { deps, corsConfig, rateLimiter } = rc;\n const { ctx } = deps;\n const routePath = QUERY.ROUTE_PATH;\n\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: routePath,\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\n const parsed = parseQueryParams(req, res, rc);\n if (!parsed) return;\n\n // 工作区解析必须在算校验值**之前**(否则身份维度里工作区还是空的,\n // 切到别的工作区会因槽里留着旧图而收到假 304——`dataRoutes` 的 children 路由\n // 在同一位置有同样的注释与顺序要求)。\n const workspacePath = await validateWorkspacePath(ctx, deps, req);\n if (workspacePath === '__denied__') {\n sendError(res, 403, ERROR_CODES.WORKSPACE_PATH_NOT_ALLOWED, req, corsConfig);\n return;\n }\n if (!workspacePath) {\n sendError(res, 400, ERROR_CODES.NO_WORKSPACE_PATH, req, corsConfig);\n return;\n }\n\n const input: KeywordQueryInput = { ...parsed, workspacePath };\n const resolved = await resolveGraph(rc, workspacePath);\n if (!resolved) {\n // 取图失败必须是**服务端错误**,不能回空结果:空结果在界面上与\n // 「图里确实没有这个关键词」完全一样(本仓反复记录的「失败伪装成没有数据」)。\n sendError(res, 503, ERROR_CODES.SCAN_FAILED, req, corsConfig, 'upstream graph fetch failed');\n return;\n }\n\n const { graph, source } = resolved;\n\n // 校验值的身份维度见 `computeQueryEtag`。生效跳数(`depthEffective`)参与定域,\n // 因为深度 4/5 在库里被钳到 3,载荷与 3 逐字节相同。\n const etag = computeQueryEtag({\n workspacePath,\n query: input.query,\n depth: input.depthEffective,\n tokenBudget: input.tokenBudget,\n revision: graph.metadata.timestamp,\n });\n if (checkEtag(req, res, corsConfig, etag)) return;\n\n const payload = buildPayload(input, graph, source);\n // 200 必须回写校验值:只写 304 分支会让客户端永远没有可回传的值,\n // 304 分支因此变成「可达但永不命中」的死代码(P3-R18-02)。\n sendJson(res, 200, payload, req, corsConfig, etagHeaders(etag));\n }),\n }), 'codegraph: keyword query route');\n}\n","/**\r\n * `GET /api/v1/codegraph/paths` —— 路径枚举读法的**用户侧出口**(cycle-124)。\r\n *\r\n * ## 缺口:璇玑图最强读法只有模型能调\r\n *\r\n * `graph_path_enumerate`(`src/tools.ts`)自 cycle-107 起就是璇玑图路径规则读法的\r\n * 真实调用方,但它是纯模型工具(ctx.tools.register)——**用户在面板上没有任何\r\n * 通道触达它**。功能业务的「注册-生产-消费」闭环在消费端断了一环:库有、工具\r\n * 有、路由无、UI 无。本路由补「路由无」这一环(UI 下一轮)。\r\n *\r\n * ## 与工具面的关系(同一份实现,不是第二份)\r\n *\r\n * 读取算法**只有一份**:`XuanjiAdapter.findPathsBetween`(XJ-03 的 `readPathPage`\r\n * 组装、Ω 惰性输出、分页)。本路由与工具的差异只在**供图方式**:\r\n * - 工具面:`fetchMergedGraph`(上游索引 + lens 三源合并,带缓存基线与增量);\r\n * - 本路由:`lastGraphData` 槽(P0-B 身份守卫限定)→ DB 回落——客户端画布上\r\n * 已经是哪张图,路径枚举就在哪张图上跑,**所见即所查**。\r\n *\r\n * 图空间池化:模块级 `XuanjiAdapterPool`(按 repoId LRU),`syncGraph` 的\r\n * `'none'` 快路径让重复请求零物化成本。\r\n *\r\n * ## 安全面\r\n *\r\n * 与全部只读路由同款:工作区解析(query 参数 + 允许列表 + realpath)、限流、\r\n * CORS、输入 clamp。参数与工具 schema 对齐(minHops/maxHops/limit/cursor/\r\n * pageSize/edgeTypes/truncation),越界一律夹紧不报错(与工具行为一致)。\r\n *\r\n * ## etag 裁决(cycle-124 收口轮,显式决策登记)\r\n *\r\n * **刻意不加 etag。** 三条理由,全部成立才维持此决策:\r\n * 1. **消费面不存在**:客户端 PathBar 是用户驱动的即时查询,无「同参重试」场景;\r\n * 挂 etag 而无 If-None-Match 消费方 = 死代码(cycle-91「校验器可达 ≠ 校验值\r\n * 有用」教训的直接应用);\r\n * 2. **请求键维度爆炸**:paths 结果由 workspace+source+target+minHops+maxHops+\r\n * limit+cursor+pageSize+edgeTypes+truncation+图 revision 共 11 维决定,任何\r\n * 一维漏哈希就是假 304(P0-R16-02 家族),而 11 维全哈希的维护成本 > 收益;\r\n * 3. **计算成本已被池化抵消**:syncGraph 的 'none' 快路径让重复请求零物化成本,\r\n * etag 省下的只有枚举本身的计算——而枚举有 maxPaths 上界,非无界开销。\r\n * 若未来客户端需要条件请求(如结果订阅),必须先补齐全部 11 维请求键并加\r\n * 双向守卫测试(参照 computeQueryEtag 的五维先例)。\r\n */\r\nimport { resolveWorkspaceFromRequest, getWorkspaceScopedGraphData, withRouteMiddleware, type RouteContext } from './shared.ts';\r\nimport { sendJson, sendError } from '../HttpHelpers.ts';\r\nimport { existsSync } from 'node:fs';\r\nimport type { GraphData } from '../../types/index.ts';\r\nimport { ERROR_CODES } from '../../shared/constants.ts';\r\nimport { XuanjiAdapterPool, formatPatternError, checkPatternSyntax, KNOWN_ADAPTER_EDGE_TYPES, type AdapterEdgeType, DEFAULT_PATH_EDGE_TYPES } from '../../shared/analysis/XuanjiAdapter.ts';\r\n\r\n/** 路由面独立池(与工具面 xuanjiPool 隔离——工具面的缓存基线语义不同)。 */\r\nconst routePool = new XuanjiAdapterPool(2);\r\n\r\n/**\r\n * clamp 到 [min,max],非数回退 default。\r\n *\r\n * P-1(测试先行抓出):URLSearchParams.get 返回**字符串**,`typeof v === 'number'`\r\n * 恒 false ⇒ 所有数值参数被静默替换成 default(`maxHops=1` 变 5,2 跳路径在\r\n * 「不可达」用例里冒出来)。query string 的入参必须先 Number 化再判有限——\r\n * 这是「参数加了、类型没对」家族在路由面的新形态。\r\n */\r\nfunction clampInt(v: unknown, min: number, max: number, dflt: number): number {\r\n const n = typeof v === 'number' && Number.isFinite(v)\r\n ? Math.floor(v)\r\n : typeof v === 'string' && v.trim() !== '' && Number.isFinite(Number(v))\r\n ? Math.floor(Number(v))\r\n : NaN;\r\n if (Number.isNaN(n)) return dflt;\r\n return Math.min(max, Math.max(min, n));\r\n}\r\n\r\n/**\r\n * 索引库直读回落(cycle-134,仅 `slot` 为空时使用)。\r\n *\r\n * 复用 `/bootstrap` 与四个按需路由的同一条读侧链(`resolveIndexDbPath` →\r\n * `readGraphFromDb` → `mapRowsToGraph*`),**不新建第二份图**——这是本仓\r\n * 「两个数据路径共享一个实现」纪律的直接应用。\r\n *\r\n * 与 `CodeGraphAdapter` 的区别只在「要不要聚类/过滤」:路径枚举需要**完整**图\r\n * (否则一段合法路径可能因为中间节点被过滤而断裂),因此这里不做噪声路径过滤。\r\n * 过滤与否会改变「图上有没有这条路径」这个答案,属语义差异,不是性能优化。\r\n *\r\n * 失败一律返回 `null`,由调用方走 `no-graph-data` 分支——索引是缓存层,\r\n * 读失败不该让路由 500。\r\n */\r\nasync function readIndexGraph(workspacePath: string): Promise<GraphData | null> {\r\n try {\r\n const [{ resolveDbPath }, { readGraphFromDb }, rows, native] = await Promise.all([\r\n import('../../adapters/CodeGraphAdapter.ts'),\r\n import('../../adapters/codeGraphDb.ts'),\r\n import('../../adapters/codeGraphRows.ts'),\r\n import('../../adapters/nativeIndex.ts'),\r\n ]);\r\n let dbPath = resolveDbPath(workspacePath);\r\n if (!existsSync(dbPath)) {\r\n try {\r\n dbPath = native.resolveNativeIndexPath(workspacePath);\r\n } catch {\r\n return null;\r\n }\r\n }\r\n const raw = readGraphFromDb(dbPath);\r\n if (!raw) return null;\r\n const nodes = rows.mapRowsToGraphNodes(raw.nodes);\r\n // 边映射需要合法节点集(否则会产出悬空边,路径枚举里表现为「一步都走不通」)。\r\n const validNodeIds = new Set(nodes.map((n) => String(n.id)));\r\n const edges = rows.mapRowsToGraphEdges(raw.edges, validNodeIds);\r\n if (nodes.length === 0) return null;\r\n return {\r\n nodes,\r\n edges,\r\n metadata: {\r\n repoId: workspacePath as GraphData['metadata']['repoId'],\r\n timestamp: Date.now(),\r\n nodeCount: nodes.length,\r\n edgeCount: edges.length,\r\n },\r\n };\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** 解析逗号分隔的边类型(与工具同语义:未知类型丢弃,全未知回缺省)。 */\r\nfunction parseEdgeTypes(raw: unknown): AdapterEdgeType[] | null {\r\n if (typeof raw !== 'string' || raw.trim() === '') return null;\r\n const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);\r\n const mapped = parts.filter((p): p is AdapterEdgeType => (DEFAULT_PATH_EDGE_TYPES as readonly string[]).includes(p));\r\n return mapped.length > 0 ? mapped : null;\r\n}\r\n\r\n/** 文档化路径常量(测试按路径定位,不按注册索引)。 */\r\nexport const PATHS_ROUTE_PATH = '/api/v1/codegraph/paths';\r\n\r\n/**\r\n * ρ 语法预检路由(cycle-135):`GET /api/v1/codegraph/path-pattern/syntax?pattern=...`\r\n *\r\n * ## 为什么单独一条路由而不是让客户端自己解析\r\n *\r\n * `parsePathPattern` 在 `src/xuanji/core/path-pattern.ts`,它依赖的是 `EdgeType`\r\n * 类型与 NFA 构造——把它打进客户端 bundle 会让「库的边界」在 bundle 层面消失\r\n * (`tests/unit/xuanji/libraryBoundary.test.ts` 断言 `src/xuanji` 零宿主依赖,\r\n * 但反过来客户端依赖库同样会让 Host/Client 隔离的**意图**落空)。\r\n * 更硬的理由:语法与类型名的合法集(Σ)是**宿主侧事实**——Σ 由 `GraphEdge.type`\r\n * 决定,客户端不应复制一份(复制就会静默分叉,本仓反复记录过)。\r\n *\r\n * ## 为什么预检**不**返回 unknownEdgeTypes\r\n *\r\n * 拼错类型名(`calls`)**语法合法**(它是一个合法的 type token),只有**枚举**\r\n * 时才能发现它不在 Σ 里。预检只回答「ρ 能不能被解析」,不回答「类型名对不对」——\r\n * 后者的答案依赖图上实际存在哪些边,那是 `/paths?pattern=` 的职责。\r\n * 把两件事混在一条路由上会让「语法对但类型错」被误报成语法错。\r\n */\r\nexport const PATH_PATTERN_SYNTAX_ROUTE_PATH = '/api/v1/codegraph/path-pattern/syntax';\r\n\r\n/**\r\n * 语法预检 handler —— 返回 `true` 表示已写响应。\r\n *\r\n * 只做 parse(`checkPatternSyntax`),不编译、不碰图:预检的语义是「这条 ρ\r\n * 文本能否被解析」,与图的状态无关。因此它**不需要**工作区解析(没有图可查),\r\n * 这让它能在宿主还没建索引时就可用——这正是界面逐键校验需要的。\r\n */\r\nfunction runSyntaxBranch(\r\n res: Parameters<typeof sendJson>[0],\r\n pattern: string,\r\n req: Parameters<typeof sendJson>[3],\r\n corsConfig: Parameters<typeof sendJson>[4],\r\n): boolean {\r\n const check = checkPatternSyntax(pattern);\r\n if (check.ok) {\r\n // 额外透出 Σ(合法类型名全集),让客户端能做「输入提示 / 自动补全」而不必\r\n // 复制一份类型名列表(复制即分叉)。\r\n sendJson(res, 200, {\r\n ok: true,\r\n pattern,\r\n edgeTypes: [...KNOWN_ADAPTER_EDGE_TYPES].sort(),\r\n }, req, corsConfig);\r\n return true;\r\n }\r\n sendJson(res, 200, {\r\n ok: false,\r\n pattern,\r\n error: check.error instanceof Error ? check.error.message : String(check.error),\r\n diagnostic: formatPatternError(pattern, check.error),\r\n edgeTypes: [...KNOWN_ADAPTER_EDGE_TYPES].sort(),\r\n }, req, corsConfig);\r\n return true;\r\n}\r\n\r\n/**\r\n * `pattern`(ρ)分支的具体求值——cycle-134。\r\n *\r\n * 抽成独立函数而不是内联进 handler:`registerPathsRoute` 的 handler 复杂度已经很高\r\n * (参数解析 + 工作区解析 + 图槽回落 + 分页 + label 映射),把 ρ 分支塞进去会让\r\n * 单函数分支数超预算(band-detector 口径 ≤ 100)。这里的分支自成一体,\r\n * 也只有它能返回「已经发过响应」这个第三种结果。\r\n *\r\n * @returns `true` = 已经写过响应(调用方直接 return);`false` = 未处理(不该发生)\r\n */\r\nfunction runPatternBranch(\r\n // 两套 HTTP 类型(node:http 与 src/types 的 electron 兼容别名)在本仓共存,\r\n // 而 sendJson 接受的是 node:http 那一套。辅助函数只转调 sendJson,因此这里用\r\n // 结构化最小面 + 一次 cast,而不是把两套类型都引进来(那会污染调用点)。\r\n res: Parameters<typeof sendJson>[0],\r\n graph: GraphData,\r\n workspacePath: string,\r\n pattern: string,\r\n url: URL,\r\n req: Parameters<typeof sendJson>[3],\r\n corsConfig: Parameters<typeof sendJson>[4],\r\n): boolean {\r\n const adapter = routePool.get(workspacePath);\r\n adapter.syncGraph(graph);\r\n try {\r\n const result = adapter.pathsByPattern(pattern, {\r\n ...(url.searchParams.get('startNode') ? { startNode: url.searchParams.get('startNode')! } : {}),\r\n ...(url.searchParams.get('endNode') ? { endNode: url.searchParams.get('endNode')! } : {}),\r\n minHops: clampInt(url.searchParams.get('minHops'), 0, 12, 0),\r\n maxHops: clampInt(url.searchParams.get('maxHops'), 0, 12, 8),\r\n limit: clampInt(url.searchParams.get('limit'), 1, 50, 20),\r\n cursor: clampInt(url.searchParams.get('cursor'), 0, Number.MAX_SAFE_INTEGER, 0),\r\n pageSize: url.searchParams.get('pageSize') !== null\r\n ? clampInt(url.searchParams.get('pageSize'), 1, 50, 20)\r\n : undefined,\r\n ...(url.searchParams.get('closed') === '1' ? { closed: true } : {}),\r\n ...(url.searchParams.get('truncation') === 'top-k-weight' ? { truncation: 'top-k-weight' as const } : {}),\r\n });\r\n const labelOf = new Map<string, string>(graph.nodes.map((n) => [String(n.id), n.label]));\r\n sendJson(res, 200, {\r\n pattern: result.pattern,\r\n patternEdgeTypes: result.patternEdgeTypes,\r\n stateCount: result.stateCount,\r\n /**\r\n * 拼错诊断(cycle-135):ρ 里出现但不在 Σ 里的类型名。\r\n * 与 `paths: []` 一起返回时,客户端据此区分「我写错了」与「图上没有这种边」——\r\n * 两者都是空结果,但用户下一步动作完全不同。\r\n */\r\n unknownEdgeTypes: result.unknownEdgeTypes,\r\n paths: result.paths.map((p) => ({ ids: p, labels: p.map((id) => labelOf.get(id) ?? id) })),\r\n truncated: result.truncated,\r\n total: result.total,\r\n nextCursor: result.nextCursor ?? null,\r\n metadata: { repoId: graph.metadata.repoId, edgeCount: graph.edges.length },\r\n }, req, corsConfig);\r\n return true;\r\n } catch (e) {\r\n // ρ 语法错误与「没有匹配」**必须**在返回面上可区分(与工具面同语义):\r\n // 400 + 带 ^ 指位的诊断,而不是 200 + 空数组。\r\n // 若不是 PathPatternError(真正的内部故障),照常上抛给统一错误处理。\r\n const isPatternError = e instanceof Error && e.name === 'PathPatternError';\r\n if (!isPatternError) throw e;\r\n sendJson(res, 400, {\r\n paths: [],\r\n truncated: false,\r\n total: 0,\r\n nextCursor: null,\r\n error: 'invalid-pattern',\r\n message: e instanceof Error ? e.message : String(e),\r\n diagnostic: formatPatternError(pattern, e),\r\n }, req, corsConfig);\r\n return true;\r\n }\r\n}\r\n\r\nexport function registerPathsRoute(rc: RouteContext): void {\r\n const { deps, corsConfig, rateLimiter } = rc;\r\n const { ctx } = deps;\r\n const routePath = PATHS_ROUTE_PATH;\r\n\r\n ctx.effect(() => ctx.webServer!.register({\r\n kind: 'exact',\r\n path: routePath,\r\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, async (req, res) => {\r\n // 0) 工作区解析(同款安全链:query 参数 + 允许列表 + realpath)。\r\n const workspacePath = await resolveWorkspaceFromRequest(req, deps.ctx, deps);\r\n\r\n // 1) 图源:lastGraphData 槽(身份守卫)优先——所见即所查。\r\n //\r\n // cycle-134 真机发现的缺口:宿主 fresh 启动、或用户从未触发过 `/data` 时,\r\n // 槽是空的,而 `/bootstrap` 早已从**索引库**读出 2193 个节点、面板上图画得好好的。\r\n // 于是「所见即所查」在这条路径上退化成「所见 ≠ 所查」——路径枚举在用户看得见图的\r\n // 情况下报 `no-graph-data`。这与本文件的头注设计意图直接矛盾。\r\n //\r\n // 修法:槽空时回落**索引库直读**(与 `/bootstrap` 同一数据面,不是第二份图)。\r\n // 只在槽空时才走,因此槽在场时的行为逐字节不变。\r\n let graph = getWorkspaceScopedGraphData(deps, workspacePath);\r\n if (!graph || graph.nodes.length === 0) {\r\n graph = await readIndexGraph(workspacePath);\r\n }\r\n if (!graph || graph.nodes.length === 0) {\r\n // 槽空(宿主 fresh 启动 / 索引未建)≠ 工作区不允许:真机冒烟发现 404\r\n // WORKSPACE_PATH_NOT_ALLOWED 有误导性(会让 API 消费方以为是权限问题)。\r\n // 与「端点不在图」同语义:200 + reason,客户端据此提示「先扫描/等索引」。\r\n sendJson(res, 200, {\r\n paths: [], truncated: false, total: 0, nextCursor: null,\r\n reason: 'no-graph-data',\r\n }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 2) 解析查询参数(query string;全部 clamp)。\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const sourceSymbol = url.searchParams.get('source') ?? '';\r\n const targetSymbol = url.searchParams.get('target') ?? '';\r\n /**\r\n * `pattern`(cycle-134):路径模式 ρ —— 与 `source`/`target` **互斥**的两条分支。\r\n *\r\n * 为什么互斥而不是叠加:两者是**不同的查询形态**。\r\n * - `source`+`target` ⇒ 「这两点之间的路径」(端点已知,ρ 未定);\r\n * - `pattern` ⇒ 「图上哪些路径符合这个形状」(形状已知,端点未知)。\r\n * 同时给出时语义可以定义(「两点之间且符合 ρ」),但会让参数组合爆炸\r\n * (`pattern` + `source` + `target` + `endNode` 四种子集各有不同含义),\r\n * 而调用方真正需要的那种组合用 `pattern` + `startNode`/`endNode` 已能表达。\r\n * 因此这里明确分支,并在同时给出时返回 400 —— 不含糊。\r\n */\r\n const pattern = url.searchParams.get('pattern') ?? '';\r\n\r\n if (pattern) {\r\n if (sourceSymbol || targetSymbol) {\r\n sendError(res, 400, ERROR_CODES.INVALID_CONFIG, req, corsConfig,\r\n 'pattern is mutually exclusive with source/target — use pattern with optional startNode/endNode');\r\n return;\r\n }\r\n const patternResult = runPatternBranch(res, graph, workspacePath, pattern, url, req, corsConfig);\r\n if (patternResult) return;\r\n return;\r\n }\r\n\r\n if (!sourceSymbol || !targetSymbol) {\r\n sendError(res, 400, ERROR_CODES.INVALID_CONFIG, req, corsConfig, 'source and target query params are required');\r\n return;\r\n }\r\n const nodeIds = new Set(graph.nodes.map((n) => n.id as string));\r\n if (!nodeIds.has(sourceSymbol) || !nodeIds.has(targetSymbol)) {\r\n // 与工具同语义:端点不在图里 = 空结果(不是错误——graph_query 先定位)。\r\n sendJson(res, 200, { paths: [], truncated: false, total: 0, nextCursor: null, reason: 'endpoint-not-in-graph' }, req, corsConfig);\r\n return;\r\n }\r\n\r\n // 3) 图空间池化 + 同步('none' 快路径让重复请求零物化成本)。\r\n const adapter = routePool.get(workspacePath);\r\n adapter.syncGraph(graph);\r\n\r\n // 4) 路径枚举(参数与工具 schema 对齐,越界夹紧)。\r\n const result = adapter.findPathsBetween(sourceSymbol, targetSymbol, {\r\n minHops: clampInt(url.searchParams.get('minHops'), 1, 10, 1),\r\n maxHops: clampInt(url.searchParams.get('maxHops'), 1, 10, 5),\r\n limit: clampInt(url.searchParams.get('limit'), 1, 50, 10),\r\n cursor: clampInt(url.searchParams.get('cursor'), 0, Number.MAX_SAFE_INTEGER, 0),\r\n pageSize: url.searchParams.get('pageSize') !== null\r\n ? clampInt(url.searchParams.get('pageSize'), 1, 50, 10)\r\n : undefined,\r\n ...(parseEdgeTypes(url.searchParams.get('edgeTypes'))\r\n ? { edgeTypes: parseEdgeTypes(url.searchParams.get('edgeTypes'))! }\r\n : {}),\r\n ...(url.searchParams.get('truncation') === 'top-k-weight' ? { truncation: 'top-k-weight' as const } : {}),\r\n });\r\n\r\n // 5) 路径 id → label(客户端渲染直接用,免二次查表)。\r\n const labelOf = new Map(graph.nodes.map((n) => [n.id as string, n.label]));\r\n const pathsWithLabels = result.paths.map((p) => ({\r\n ids: p,\r\n labels: p.map((id) => labelOf.get(id) ?? id),\r\n }));\r\n\r\n sendJson(res, 200, {\r\n paths: pathsWithLabels,\r\n truncated: result.truncated,\r\n total: result.total,\r\n nextCursor: 'nextCursor' in result ? (result as { nextCursor?: number | null }).nextCursor ?? null : null,\r\n metadata: {\r\n repoId: graph.metadata.repoId,\r\n source: sourceSymbol,\r\n target: targetSymbol,\r\n edgeCount: graph.edges.length,\r\n },\r\n }, req, corsConfig);\r\n }),\r\n }), 'codegraph: paths route');\r\n\r\n // cycle-135:ρ 语法预检路由。无图、无工作区解析——语法与 Σ 是宿主侧事实,\r\n // 且预检的语义不依赖图的状态,因此宿主还没建索引时它就能工作(界面逐键校验需要)。\r\n ctx.effect(() => ctx.webServer!.register({\r\n kind: 'exact',\r\n path: PATH_PATTERN_SYNTAX_ROUTE_PATH,\r\n handler: withRouteMiddleware('GET', PATH_PATTERN_SYNTAX_ROUTE_PATH, corsConfig, rateLimiter, async (req, res) => {\r\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\r\n const pattern = url.searchParams.get('pattern') ?? '';\r\n runSyntaxBranch(res, pattern, req, corsConfig);\r\n }),\r\n }), 'codegraph: path-pattern syntax route');\r\n\r\n void routePool;\r\n}\r\n","import { sendJson } from '../HttpHelpers.ts';\nimport { withRouteMiddleware, type RouteContext } from './shared.ts';\nimport { registrySummary, probeRegistry } from '../registry/businessRegistry.ts';\n\n/**\n * 业务注册-生产-消费矩阵路由(cycle-116 可观测性主轴)。\n *\n * 返回四面(tools/routes/entries/skill)全量清单 + 断链判定 + 生命周期五态计数。\n * 白盒透明化:任何「注册了但没人消费」的业务在这里直接可见(brokenLinks 非空即断链)。\n * 该路由本身也登记进 ROUTE_ENTRIES?不——看板不观测自己(自引用会造出无限清单),\n * 本路由由 routes.test.ts 的注册数守卫覆盖。\n */\nexport function registerRegistryRoute(rc: RouteContext): void {\n const { corsConfig, rateLimiter } = rc;\n const { ctx } = rc.deps;\n const routePath = '/api/v1/codegraph/registry';\n\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: routePath,\n handler: withRouteMiddleware('GET', routePath, corsConfig, rateLimiter, (req, res) => {\n sendJson(res, 200, {\n ...registrySummary(),\n // cycle-117:运行时实测层(lifecycle 声明式 → 实测式)。零副作用探测。\n live: probeRegistry(rc.deps),\n }, req, corsConfig);\n }),\n }), 'codegraph: registry route');\n}\n","/**\n * Π 视图的 git 原始输入采集器(cycle-144,T1)。\n *\n * ## 为什么需要这个模块\n *\n * `/views` 路由自 cycle-141 起支持 `commitLog` / `blameLog` 查询参数,但真机\n * 用户旅程显示时序/所有权视图恒为「不可用」——因为**没有任何调用方采集这两份\n * 输入**(客户端没有、宿主侧也没有),「支持传参」成了一个永远无人传的参数。\n * 这与本仓反复出现的「参数加了、没人传」缺陷同族(CLAUDE.md:把文档说 N 个\n * 调用点当假设去 grep,从不当 spec)。\n *\n * 本模块在宿主侧补上采集端:工作区里跑两条 git 命令,把原始文本喂给既有的\n * `buildTemporalFact` / `buildOwnershipFact`。降级语义不变——git 不可用 /\n * 不是仓库 / 超时时返回 `null`,路由继续走「available: false + 中文原因」\n * 的诚实路径,**绝不编造空数据冒充成功**。\n *\n * ## 复用 gitChurn.ts 的全部纪律(不再各写一份)\n *\n * - `execFile` 无 shell(工作区路径含空格/中文安全,cycle-105 P0-A 教训);\n * - `windowsHide: true`(不闪控制台窗口);\n * - 有界 Map + TTL 缓存,**只缓存成功结果**(失败不固化成状态);\n * - `clearCache()` 供测试与工作区切换;\n * - 失败原因压成一行可读文本。\n *\n * ## 输出格式与解析器逐字对齐\n *\n * - commitLog:`git log --name-only --format=%x1e%H%x1f%ct`——`parseCommitLog`\n * 按 `\\x1e` 分段、头 `<hash>\\x1f<unix-ts>`(shared/temporal.ts:163);\n * - blameLog:`git blame --porcelain`——`parseGitBlamePorcelain` 按行解析\n * `^<hash> <line> <group> <count>` 头(shared/ownership.ts:102)。\n * 采集器与解析器分属 host/shared 两侧,格式漂移会让「采集成功但解析为空」\n * 静默发生,因此测试里用真实 git 仓库往返验证(非 mock 解析器)。\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { scoped } from '../shared/Logger.ts';\n\nconst log = scoped('host:views-collector');\nconst execFileAsync = promisify(execFile);\n\n/** 单条 git 采集结果:原始文本 + 可用性 + 一行可读原因。 */\nexport interface GitRawInput {\n text: string | null;\n available: boolean;\n reason: string | null;\n /** 采集耗时(毫秒)——白盒披露,面板与审计日志都能看到采集成本。 */\n durationMs: number;\n /** 输出是否被截断(超过字节上限)——截断的输入对解析器仍是合法前缀。 */\n truncated: boolean;\n}\n\n/** 采集失败/跳过时的空结果。 */\nfunction unavailable(reason: string, durationMs = 0): GitRawInput {\n return { text: null, available: false, reason, durationMs, truncated: false };\n}\n\n/** 采集上限:输出字节与超时。与 gitChurn 的 CHURN 常量同族但独立——\n * blame 在大仓库上比 log 更重(逐行归属),给独立的调参位。 */\nconst COLLECT = {\n /** git log/blame 单条超时。 */\n TIMEOUT_MS: 8_000,\n /** 输出字节上限(8MB:大型仓库全量 log 的现实量级之上)。 */\n MAX_BUFFER: 8 * 1024 * 1024,\n /** 超出 MAX_BUFFER 后保留的尾部字节数(尾部是最新提交,对 days 窗口最有用)。 */\n TAIL_KEEP: 2 * 1024 * 1024,\n} as const;\n\n/** 采集缓存条目。 */\ninterface CollectorCacheEntry { result: GitRawInput; timestamp: number }\n\n/** 两个采集通道各自的缓存(key = `${workspacePath}::${channel}`)。 */\nconst collectCache = new Map<string, CollectorCacheEntry>();\nconst CACHE_MAX = 16;\nconst CACHE_TTL_MS = 30_000;\n\n/** 清空采集缓存(测试与工作区切换)。 */\nexport function clearViewsGitCache(): void {\n collectCache.clear();\n}\n\n/** 归一化 git 失败原因为一行可读文本(与 gitChurn.describeError 同策略)。 */\nfunction describeError(e: unknown): string {\n if (e instanceof Error) {\n const err = e as { stderr?: string; killed?: boolean; signal?: string; code?: string | number };\n if (err.stderr) {\n const firstLine = err.stderr.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? '';\n if (firstLine.trim()) return firstLine.trim();\n }\n if (err.killed || err.signal) {\n return `git 超时(>${COLLECT.TIMEOUT_MS}ms)或被杀(signal=${err.signal ?? 'unknown'})`;\n }\n if (err.code === 'ENOENT') return '找不到 git 可执行文件(未安装或不在 PATH 中)';\n const firstMessageLine = e.message.split(/\\r?\\n/).find((line) => line.trim().length > 0) ?? e.message;\n return firstMessageLine.trim();\n }\n return String(e);\n}\n\n/** 输出截断保护:超限时保留尾部(最新提交在尾部)。 */\nfunction clampTail(stdout: string): { text: string; truncated: boolean } {\n if (Buffer.byteLength(stdout, 'utf-8') <= COLLECT.MAX_BUFFER) {\n return { text: stdout, truncated: false };\n }\n const buf = Buffer.from(stdout, 'utf-8');\n // 从尾部截 COLLECT.TAIL_KEEP 字节;可能切在多字节字符中间,用 Buffer toString 容错。\n const tail = buf.subarray(buf.length - COLLECT.TAIL_KEEP).toString('utf-8');\n log.warn('git output truncated to tail', { keepBytes: COLLECT.TAIL_KEEP });\n return { text: tail, truncated: true };\n}\n\n/**\n * 带缓存的采集入口。显式传参(查询串)绕过缓存——调用方自带的输入以它为准。\n *\n * cycle-146 修复:`fileFilter` 限定 blame 的文件集。真机实证(dsh-anic,1056 个\n * 跟踪文件):全量逐文件 blame 77 秒——超过客户端 30s 请求预算,面板必然「加载失败」\n * (白盒披露存在但采集墙时间无界)。而消费端 `buildOwnershipMap` 只按图节点的\n * `filePath` 查表,全仓 blame 的其余文件白算。路由传入图节点文件集后采集量与\n * 图规模成正比(50 节点 ≈ 几十个文件 → 亚秒级)。缓存键随 fileFilter 变化:\n * 不同图规模的工作区各自缓存,互不污染。\n */\nexport async function collectGitRawInput(\n workspacePath: string,\n channel: 'commitLog' | 'blameLog',\n fileFilter?: ReadonlySet<string>,\n): Promise<GitRawInput> {\n const filterKey = fileFilter && fileFilter.size > 0 ? [...fileFilter].sort().join('\\u0000') : '';\n const cacheKey = `${workspacePath}::${channel}::${filterKey}`;\n const cached = collectCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.result;\n\n const result = await collectUncached(workspacePath, channel, fileFilter);\n if (result.available) {\n if (collectCache.size >= CACHE_MAX) {\n const oldest = collectCache.keys().next().value;\n if (oldest !== undefined) collectCache.delete(oldest);\n }\n collectCache.set(cacheKey, { result, timestamp: Date.now() });\n }\n return result;\n}\n\n/** 无缓存采集实现(测试直接调它验证真实 git 行为)。 */\nasync function collectUncached(\n workspacePath: string,\n channel: 'commitLog' | 'blameLog',\n fileFilter?: ReadonlySet<string>,\n): Promise<GitRawInput> {\n const started = Date.now();\n const baseArgs = ['-C', workspacePath, '-c', 'core.quotepath=false'];\n try {\n if (channel === 'commitLog') {\n const { stdout } = await execFileAsync('git', [...baseArgs, 'log', '--name-only', '--format=%x1e%H%x1f%ct'], {\n timeout: COLLECT.TIMEOUT_MS,\n maxBuffer: COLLECT.MAX_BUFFER * 2,\n windowsHide: true,\n });\n const { text, truncated } = clampTail(stdout);\n const durationMs = Date.now() - started;\n if (!text.trim()) return unavailable('git 输出为空(空仓库或无提交历史)', durationMs);\n return { text, available: true, reason: null, durationMs, truncated };\n }\n\n // blame:`-- .` 在空路径上会报 `fatal: no such path '' in HEAD`(真机实证),\n // 正确形态是先 ls-files 拿跟踪文件清单,再对文件清单逐个 blame(单条进程,\n // 文件名作为 argv 传递——无 shell 纪律下含空格/中文路径安全)。\n const ls = await execFileAsync('git', [...baseArgs, 'ls-files'], {\n timeout: COLLECT.TIMEOUT_MS,\n maxBuffer: COLLECT.MAX_BUFFER,\n windowsHide: true,\n });\n const tracked = ls.stdout.split(/\\r?\\n/).map((f) => f.trim()).filter(Boolean);\n if (tracked.length === 0) {\n return unavailable('仓库没有跟踪文件(空仓库或全部未跟踪)', Date.now() - started);\n }\n // cycle-146:按图节点文件集过滤——消费端 buildOwnershipMap 只按 node.filePath\n // 查表,跟踪文件全量 blame 的其余文件是白算的墙时间(dsh-anic 实测 77s vs\n // 过滤后亚秒)。归一化对齐 normalizeChurnPath(斜杠方向/前缀),否则键错位\n // 会让过滤后空集、所有权视图永远不可用——比全量慢更糟,所以空集时回退全量。\n let targets = tracked;\n if (fileFilter && fileFilter.size > 0) {\n const wanted = new Set(\n [...fileFilter].map((f) => f.trim().replace(/\\\\/g, '/').replace(/^\\.\\//, '').replace(/\\/+$/, '')),\n );\n const filtered = tracked.filter((f) => wanted.has(f));\n if (filtered.length > 0) targets = filtered;\n }\n // 分批:argv 长度有 OS 上限。但**多文件 blame 在本机 git 2.56(niugit)下\n // 直接报 `fatal: bad revision 'sub/b.ts'`(真机实证)——porcelain 模式只认\n // 单文件**。因此逐文件串行;吞吐靠 TTL 缓存摊平(30s 内的重复请求不重跑),\n // 而不是并行(并行会让大批仓库瞬间起几百个 git 进程)。\n const parts: string[] = [];\n let truncated = false;\n for (const file of targets) {\n const { stdout } = await execFileAsync('git', [...baseArgs, 'blame', '--porcelain', '--', file], {\n timeout: COLLECT.TIMEOUT_MS,\n maxBuffer: COLLECT.MAX_BUFFER,\n windowsHide: true,\n });\n parts.push(stdout);\n // 单文件输出超限只丢该文件的归属,不中止整批(其余文件照常采集)。\n if (Buffer.byteLength(stdout, 'utf-8') > COLLECT.MAX_BUFFER) truncated = true;\n }\n const joined = parts.join('');\n const { text, truncated: tailTruncated } = clampTail(joined);\n const durationMs = Date.now() - started;\n if (!text.trim()) return unavailable('blame 输出为空(文件全部为空或 HEAD 缺失)', durationMs);\n return { text, available: true, reason: null, durationMs, truncated: truncated || tailTruncated };\n } catch (e) {\n const durationMs = Date.now() - started;\n const reason = describeError(e);\n log.info('views git collect failed', { channel, reason, durationMs });\n return unavailable(reason, durationMs);\n }\n}\n\n/** 导出采集上限(测试断言与文档引用)。 */\nexport const VIEWS_COLLECT_LIMITS = COLLECT;\n/** 导出缓存参数(测试断言)。 */\nexport const VIEWS_COLLECT_CACHE = { MAX: CACHE_MAX, TTL_MS: CACHE_TTL_MS };\n","/**\n * `GET /api/v1/codegraph/views?workspace=<abs>` — Π 视图划分的用户侧出口(cycle-141 / workstream E)。\n *\n * ## 它解决什么\n *\n * `graph_views` 工具(`src/tools.ts`)此前只有模型能调:Π: V ∪ E → 2^{V_view}\n * 的正式实现存在,但用户在界面上没有任何通道看到「每个节点属于哪些正交视图」。\n * 「功能业务注册-生产-消费」在消费端断环——工具注册了、测试覆盖了,唯独没有人能看到。\n *\n * 本路由把这条划分**原样搬到 HTTP 面**:结构视图恒属;时序/所有权视图在缺少\n * 原始 git 输入(`commitLog` / `blameLog`)时**显式降级**(`available: false` + `reason`),\n * 绝不静默补全;领域视图用 Louvain 现算(与工具同一个 `computeDomainLabels`)。\n *\n * ## 图源与「没有索引」的语义\n *\n * 优先读宿主**内存图槽**(`getWorkspaceScopedGraphData`,工作区限定——跨工作区\n * 防护与 `/bootstrap` 等五条路由共用同一判据);槽空时回落到索引 DB(上游\n * `.codegraph/codegraph.db` 或原生 `.codegraph-visualizer/native-index.db`)。\n * 两者都拿不到 ⇒ HTTP 200 `{ success:false, errorCode:'INDEX_NOT_FOUND' }`。\n * 这与 `manage/metrics` 的口径一致:**没有索引必须说「没有」,不能报一份假的空 Π**。\n *\n * ## 截断必须如实披露\n *\n * `nodeViewMatrix` 采样到最多 500 个节点(UI 表格的可用性护栏),响应同时给出\n * `totalNodes` / `totalEdges` 与 `sampled`——客户端据此显示「已采样 / 共 N 个」,\n * 而不是让用户以为表格就是全图。采样按图内顺序取前 500,**确定性**(同一输入\n * 永远同一张表格)。\n */\nimport { existsSync } from 'node:fs';\nimport { isAbsolute, resolve, sep } from 'node:path';\nimport { ERROR_CODES, VALIDATION } from '../../shared/constants.ts';\nimport { RepoId } from '../../types/index.ts';\nimport type { GraphData, GraphNode } from '../../types/index.ts';\nimport { resolveDbPath } from '../../adapters/CodeGraphAdapter.ts';\nimport type { DbNodeRow, DbEdgeRow } from '../../adapters/codeGraphRows.ts';\nimport { partitionByView, serializeViewPartition } from '../../shared/views.ts';\nimport type { ViewName } from '../../shared/views.ts';\nimport { computeDomainLabels } from '../../shared/domain.ts';\nimport { parseCommitLog, filterSlices, type CommitRange } from '../../shared/temporal.ts';\nimport { parseGitBlamePorcelain, groupBlameByFile, buildOwnershipMap } from '../../shared/ownership.ts';\nimport { collectGitRawInput } from '../gitViewsCollector.ts';\nimport { sendJson, sendError } from '../HttpHelpers.ts';\nimport {\n withRouteMiddleware, resolveWorkspaceFromRequest, getWorkspaceScopedGraphData,\n type RouteContext, log,\n} from './shared.ts';\n\n/** 路由路径(客户端与测试共用)。 */\nexport const VIEWS_ROUTE_PATH = '/api/v1/codegraph/views';\n\n/** `nodeViewMatrix` 的采样上限(UI 表格护栏)。 */\nexport const VIEWS_MATRIX_MAX_NODES = 500;\n\n/**\n * 查询参数里原始 git 日志(`commitLog` / `blameLog`)的字符上限。\n *\n * 这两个参数可能非常大(一次全仓 `git log --name-only` 可达数 MB)。URL 传参没有\n * 服务器侧的天然上界,所以这里显式设限:**超限判为「未提供」而不是截断**——\n * 截断会让解析器拿到半截 git 输出,产生一份看似合法、其实错乱的视图归属。\n */\nconst VIEWS_RAW_LOG_MAX_CHARS = VALIDATION.MAX_GIT_REF_LENGTH * 64;\n\n/**\n * 回落到索引 DB 时的读取上界。与 `METRICS` 同族:节点与边是**同一处内存护栏的两半**,\n * 只界节点会让边侧无界(代码图谱边数通常是节点数的 2–5 倍)。\n */\nconst VIEWS_DB = {\n MAX_NODES: 2000,\n MAX_EDGES: 8000,\n PAGE_SIZE: 500,\n} as const;\n\n/** 响应里单个视图的摘要(结构视图恒可用;时序/所有权显式降级)。 */\nexport interface ViewSummary {\n structural: { nodeCount: number; edgeCount: number };\n temporal: { nodeCount: number; edgeCount: number; available: boolean; reason: string | null };\n ownership: { nodeCount: number; edgeCount: number; available: boolean; reason: string | null };\n domain: { nodeCount: number; edgeCount: number; domainCount: number; nodeCounts: Record<string, number> };\n}\n\n/** 采样后的节点视图矩阵行。 */\nexport interface NodeViewRow {\n id: string;\n label: string;\n views: string[];\n}\n\n/** 成功响应。 */\nexport interface ViewsSuccessResponse {\n success: true;\n errorCode: null;\n views: ViewSummary;\n totalNodes: number;\n totalEdges: number;\n nodeViewMatrix: NodeViewRow[];\n sampled: boolean;\n computedAt: number;\n /** git 输入来源披露(cycle-144):auto=宿主采集,query=调用方显式传参。 */\n gitSources: {\n commitLog: GitSourceInfo;\n blameLog: GitSourceInfo;\n };\n}\n\n/** 单条 git 输入通道的来源与采集元数据。 */\ninterface GitSourceInfo {\n source: 'auto' | 'query';\n available: boolean;\n durationMs: number | null;\n truncated: boolean;\n reason: string | null;\n}\n\n/** 失败响应(无索引 / 无工作区)。 */\nexport interface ViewsErrorResponse {\n success: false;\n errorCode: string;\n views: null;\n error: string;\n}\n\n/**\n * 把节点 filePath 投影为 git 相对路径(cycle-146 L2)。\n *\n * 原生扫描图的 filePath 是「工作区绝对路径」域(`D:\\…\\repo\\src\\a.ts`),git 输出与\n * 消费端查表键都在「仓库相对路径」域。规则:\n * - 绝对路径且位于 workspacePath 下 → 剥前缀 + 反斜杠归一;\n * - 已经是相对域(上游索引 / DB 图)→ 原样返回(投影是幂等的安全操作);\n * - 绝对但不在工作区下(异常图)→ 原样返回,让「查表落空」保持现状而不是编造。\n */\nexport function toGitRelative(filePath: string, workspacePath: string): string {\n const fp = String(filePath ?? '').trim();\n if (fp === '' || !isAbsolute(fp)) return fp;\n const root = resolve(workspacePath);\n const norm = fp.replace(/\\//g, sep);\n const resolved = resolve(norm);\n if (resolved === root || !resolved.toLowerCase().startsWith(root.toLowerCase() + sep)) return fp;\n return resolved.slice(root.length + 1).replace(/\\\\/g, '/');\n}\n\n/**\n * 从索引 DB 读一张有界图。\n *\n * 读不到任何一页视为真实失败(索引损坏),抛错由路由的 `catch` 转成 500;\n * 这与 `manage/metrics` 的口径一致——**不能**在文件在场却读不出时退化成「空索引」。\n */\nasync function readGraphFromIndex(workspacePath: string): Promise<GraphData | null> {\n let dbPath: string | null = null;\n const upstreamDbPath = resolveDbPath(workspacePath);\n if (existsSync(upstreamDbPath)) {\n dbPath = upstreamDbPath;\n } else {\n try {\n const { resolveNativeIndexPath } = await import('../../adapters/nativeIndex.ts');\n const nativePath = resolveNativeIndexPath(workspacePath);\n if (existsSync(nativePath)) dbPath = nativePath;\n } catch (e) {\n // 原生索引路径解析失败不是「没有索引」的同义词:记录后继续回落内存槽。\n log.warn('views route: native index path rejected', e);\n }\n }\n if (!dbPath) return null;\n\n const { readGraphFromDbPaged } = await import('../../adapters/codeGraphDb.ts');\n const { mapRowsToGraphNodes, mapRowsToGraphEdges } = await import('../../adapters/codeGraphRows.ts');\n\n let cursor: string | null = null;\n const allDbNodes: DbNodeRow[] = [];\n const allDbEdges: DbEdgeRow[] = [];\n for (;;) {\n const page = readGraphFromDbPaged(dbPath, cursor, VIEWS_DB.PAGE_SIZE);\n if (!page) throw new Error('index database unreadable');\n allDbNodes.push(...page.nodes);\n allDbEdges.push(...page.edges);\n if (page.nextCursor === null) break;\n if (allDbNodes.length >= VIEWS_DB.MAX_NODES || allDbEdges.length >= VIEWS_DB.MAX_EDGES) break;\n cursor = page.nextCursor;\n }\n\n const nodes = mapRowsToGraphNodes(allDbNodes);\n const validNodeIds = new Set(nodes.map((n) => n.id));\n const edges = mapRowsToGraphEdges(allDbEdges, validNodeIds);\n return {\n nodes,\n edges,\n metadata: {\n repoId: RepoId(workspacePath),\n timestamp: Date.now(),\n nodeCount: nodes.length,\n edgeCount: edges.length,\n },\n };\n}\n\n/** 从请求 URL 取一个可选的长文本查询参数(commitLog / blameLog 体积可能很大,故设上限)。 */\nfunction queryLongParam(url: URL, name: string): string | undefined {\n const raw = url.searchParams.get(name);\n if (raw === null) return undefined;\n // 超长输入直接判为「未提供」而不是截断:截断会让解析器拿到半截 git 输出,\n // 产生一份看似合法、其实错乱的视图归属。宁可显式降级。\n if (raw.length > VIEWS_RAW_LOG_MAX_CHARS) return undefined;\n return raw;\n}\n\n/**\n * 计算单个视图的摘要。\n *\n * 时序视图:`commitLog` 存在且非空时,解析出 commit 切片触及的文件集合;\n * 缺失时 `available:false` + 中文原因。所有权视图同理(`blameLog`)。\n */\nfunction buildTemporalFact(\n nodes: readonly GraphNode[],\n commitLog: string | undefined,\n range: CommitRange,\n /** 采集失败时的可读原因(cycle-144):覆盖默认的「未提供」——用户能看到真实失败原因。 */\n failureReason?: string | null,\n): { files: Set<string> | undefined; available: boolean; reason: string | null } {\n if (commitLog === undefined || commitLog.trim() === '') {\n return { files: undefined, available: false, reason: failureReason ?? '未提供 commitLog:时序视图不可用' };\n }\n const history = { slices: parseCommitLog(commitLog), head: null, available: true };\n const { slices } = filterSlices(history, range);\n const files = new Set<string>();\n for (const slice of slices) for (const file of slice.files) files.add(file);\n return { files, available: true, reason: null };\n}\n\nfunction buildOwnershipFact(\n nodes: readonly GraphNode[],\n blameLog: string | undefined,\n failureReason?: string | null,\n): { ids: Set<string> | undefined; available: boolean; reason: string | null } {\n if (blameLog === undefined || blameLog.trim() === '') {\n return { ids: undefined, available: false, reason: failureReason ?? '未提供 blameLog:所有权视图不可用' };\n }\n const ownership = buildOwnershipMap(nodes, groupBlameByFile(parseGitBlamePorcelain(blameLog)));\n return { ids: new Set(Object.keys(ownership.nodeLabels)), available: true, reason: null };\n}\n\nexport function registerViewsRoute(rc: RouteContext): void {\n const { deps, corsConfig, rateLimiter } = rc;\n const { ctx } = deps;\n\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: VIEWS_ROUTE_PATH,\n handler: withRouteMiddleware('GET', VIEWS_ROUTE_PATH, corsConfig, rateLimiter, async (req, res) => {\n try {\n const workspacePath = await resolveWorkspaceFromRequest(req, ctx, deps);\n if (!workspacePath) {\n const body: ViewsErrorResponse = {\n success: false,\n errorCode: ERROR_CODES.INDEX_NOT_FOUND,\n views: null,\n error: ERROR_CODES.INDEX_NOT_FOUND,\n };\n sendJson(res, 200, body, req, corsConfig);\n return;\n }\n\n // 图源:内存槽(当前图,工作区限定)优先;槽空回落索引 DB。\n const slot = getWorkspaceScopedGraphData(deps, workspacePath);\n const graph = slot ?? await readGraphFromIndex(workspacePath);\n if (!graph || graph.nodes.length === 0) {\n const body: ViewsErrorResponse = {\n success: false,\n errorCode: ERROR_CODES.INDEX_NOT_FOUND,\n views: null,\n error: ERROR_CODES.INDEX_NOT_FOUND,\n };\n sendJson(res, 200, body, req, corsConfig);\n return;\n }\n\n const url = new URL(req.url ?? '', `http://${req.headers.host ?? 'localhost'}`);\n const commitLog = queryLongParam(url, 'commitLog');\n const blameLog = queryLongParam(url, 'blameLog');\n const range: CommitRange = {\n ...(typeof url.searchParams.get('days') === 'string' && Number.isFinite(Number(url.searchParams.get('days')))\n ? { days: Number(url.searchParams.get('days')) }\n : {}),\n ...(url.searchParams.get('from') ? { from: url.searchParams.get('from') as string } : {}),\n ...(url.searchParams.get('to') ? { to: url.searchParams.get('to') as string } : {}),\n };\n\n // 领域事实:Louvain 社区(与 `graph_views` 工具同一个实现)。\n const domain = await computeDomainLabels(graph.nodes, graph.edges);\n const domainByNodeId = new Map<string, string>();\n for (const [id, label] of Object.entries(domain.labels)) {\n if (label) domainByNodeId.set(id, label);\n }\n\n // 时序/所有权事实。显式传参优先(查询串里的 commitLog/blameLog);\n // 缺省时宿主自动采集(cycle-144 T2)——此前「支持传参但无人传」让两个视图\n // 恒为不可用(真机用户旅程实证)。采集失败(非 git 仓库/超时)时返回 null,\n // buildTemporalFact/buildOwnershipFact 走既有的「available:false + 原因」诚实路径。\n // cycle-146 两层修复(真机 dsh-anic 实证):\n // L1 采集墙时间:blame 传图节点文件集,采集量与图规模成正比\n // (全仓 1056 文件逐文件 blame 77s > 客户端 30s 预算 → 面板必然失败);\n // L2 键域错位:原生扫描图的 filePath 是「工作区绝对路径」域\n // (如 D:\\…\\dsh-anic\\.dsh\\skills\\…),而 git 输出与消费端查表键\n // (partitionByView / buildOwnershipMap 的 normalizeChurnPath)都在\n // 「仓库相对路径」域 ⇒ 不投影就永不命中,temporal/ownership 恒 0。\n // 这里把节点 filePath 投影为相对域后再传两侧(采集器文件集 + 事实构建)。\n const relNodes: GraphNode[] = graph.nodes.map((n) => {\n const fp = typeof n.filePath === 'string' ? n.filePath : '';\n const rel = toGitRelative(fp, workspacePath);\n return rel === fp ? n : { ...n, filePath: rel };\n });\n const nodeFileSet = new Set(\n relNodes.map((n) => n.filePath).filter((p): p is string => typeof p === 'string' && p.trim() !== ''),\n );\n const collectedCommit = commitLog === undefined\n ? await collectGitRawInput(workspacePath, 'commitLog')\n : null;\n const collectedBlame = blameLog === undefined\n ? await collectGitRawInput(workspacePath, 'blameLog', nodeFileSet)\n : null;\n const temporal = buildTemporalFact(relNodes, commitLog ?? collectedCommit?.text ?? undefined, range, collectedCommit?.reason ?? undefined);\n const ownership = buildOwnershipFact(relNodes, blameLog ?? collectedBlame?.text ?? undefined, collectedBlame?.reason ?? undefined);\n // 采集元数据随响应披露(白盒:用户能看到「这次划分的 git 输入从哪来、花了多久」)。\n const gitSources = {\n commitLog: collectedCommit\n ? { source: 'auto' as const, available: collectedCommit.available, durationMs: collectedCommit.durationMs, truncated: collectedCommit.truncated, reason: collectedCommit.reason }\n : { source: 'query' as const, available: commitLog !== undefined && commitLog.trim() !== '', durationMs: null, truncated: false, reason: null },\n blameLog: collectedBlame\n ? { source: 'auto' as const, available: collectedBlame.available, durationMs: collectedBlame.durationMs, truncated: collectedBlame.truncated, reason: collectedBlame.reason }\n : { source: 'query' as const, available: blameLog !== undefined && blameLog.trim() !== '', durationMs: null, truncated: false, reason: null },\n };\n\n // 正式的 Π 函数。cycle-146:data 必须用投影后的 relNodes——temporal 按\n // filePath 匹配(相对域),ownership/domain 按节点 id 匹配(投影不改 id),\n // 用原始 graph 会让 temporal 恒 0 而 ownership 正常(真机分叉实证)。\n const partition = partitionByView({\n data: { ...graph, nodes: relNodes },\n temporalFiles: temporal.files,\n attributedNodeIds: ownership.ids,\n domainByNodeId,\n });\n\n // 领域节点计数(供面板的「领域分布」使用)。\n const domainNodeCounts: Record<string, number> = {};\n for (const node of graph.nodes) {\n const label = domain.labels[String(node.id)];\n if (label) domainNodeCounts[label] = (domainNodeCounts[label] ?? 0) + 1;\n }\n // 领域视图的节点/边数:节点 = 被分配到社区的节点(Louvain 保证每个节点都有标签,\n // 即等于全图节点数);边 = 两端点在同一社区的边(跨社区「桥」边不属于任何领域)。\n const domainNodeCount = Object.values(domainNodeCounts).reduce((acc, n) => acc + n, 0);\n\n // 采样:按图内顺序取前 500,确定性。\n const sampled = graph.nodes.length > VIEWS_MATRIX_MAX_NODES;\n const nodeViewMatrix: NodeViewRow[] = graph.nodes.slice(0, VIEWS_MATRIX_MAX_NODES).map((node) => {\n const views = partition.nodeViews.get(String(node.id));\n return {\n id: String(node.id),\n label: node.label,\n views: views ? [...views].sort() : ['structural'],\n };\n });\n\n // `serializeViewPartition` 保留给「需要逐边视图」的消费者;本路由只暴露矩阵,\n // 但仍走同一个序列化器以避免两处各写一份视图名排序。\n const serialized = serializeViewPartition(partition);\n\n const body: ViewsSuccessResponse = {\n success: true,\n errorCode: null,\n views: {\n structural: {\n nodeCount: serialized.viewCounts.structural ?? 0,\n edgeCount: serialized.edgeViewCounts.structural ?? 0,\n },\n temporal: {\n nodeCount: serialized.viewCounts.temporal ?? 0,\n edgeCount: serialized.edgeViewCounts.temporal ?? 0,\n available: temporal.available,\n reason: temporal.reason,\n },\n ownership: {\n nodeCount: serialized.viewCounts.ownership ?? 0,\n edgeCount: serialized.edgeViewCounts.ownership ?? 0,\n available: ownership.available,\n reason: ownership.reason,\n },\n domain: {\n nodeCount: domainNodeCount,\n edgeCount: serialized.edgeViewCounts.domain ?? 0,\n domainCount: domain.domainCount,\n nodeCounts: domainNodeCounts,\n },\n },\n totalNodes: graph.nodes.length,\n totalEdges: graph.edges.length,\n nodeViewMatrix,\n sampled,\n computedAt: Date.now(),\n gitSources,\n };\n sendJson(res, 200, body, req, corsConfig);\n } catch (e) {\n log.error('views route failed', e);\n sendError(res, 500, ERROR_CODES.INTERNAL_ERROR, req, corsConfig);\n }\n }),\n }), 'codegraph: views route');\n}\n\n/** 便于测试与复用的「视图名 → 摘要 key」映射(面板按同一顺序渲染四张卡片)。 */\nexport const VIEW_SUMMARY_KEYS: readonly ViewName[] = ['structural', 'temporal', 'ownership', 'domain'];\n","import { TreeSitterAdapter, isTreeSitterRuntimePresent } from '../adapters/TreeSitterAdapter.ts';\r\nimport { setAstSourceProbe } from './prerequisites.ts';\r\nimport { createRateLimiter } from './RateLimiter.ts';\r\nimport type { CorsConfig } from './HttpHelpers.ts';\r\n\r\nexport type { RouteDeps } from './routes/shared.ts';\r\nexport { slimGraphData, createInvokeUpstream, registerService } from './routes/shared.ts';\r\n\r\nimport {\r\n createInvokeUpstream,\r\n type RouteContext, type RouteDeps,\r\n} from './routes/shared.ts';\r\nimport {\r\n registerStatusRoute,\r\n registerHealthRoute,\r\n registerWorkspaceRoute,\r\n} from './routes/statusRoutes.ts';\r\nimport {\r\n registerDataRoute,\r\n registerBootstrapRoute,\r\n registerNodeChildrenRoute,\r\n registerFileOutlineRoute,\r\n registerSymbolASTRoute,\r\n registerEdgesRoute,\r\n} from './routes/dataRoutes.ts';\r\nimport {\r\n registerScanRoute,\r\n registerInitRoute,\r\n registerWatchRoute,\r\n registerEventsRoute,\r\n registerManageDeleteRoute,\r\n registerManageMetricsRoute,\r\n registerManageUpdateRoute,\r\n} from './routes/controlRoutes.ts';\r\nimport {\r\n registerDependenciesRoute,\r\n registerChurnRoute,\r\n registerDiffRoute,\r\n registerAstRoute,\r\n registerTreeSitterRoute,\r\n} from './routes/analysisRoutes.ts';\r\nimport { registerLayoutWorkerRoute } from './routes/workerRoutes.ts';\r\nimport { registerKeywordQueryRoute } from './routes/queryRoute.ts';\r\nimport { registerPathsRoute } from './routes/pathsRoute.ts';\r\nimport { registerRegistryRoute } from './routes/registryRoute.ts';\r\nimport { registerViewsRoute } from './routes/viewsRoute.ts';\r\nimport { resetSnapshotShrinkState } from './compat/index.ts';\r\n\r\nexport function registerRoutes(deps: RouteDeps): void {\r\n const { ctx, config } = deps;\r\n const invokeUpstream = deps.invokeUpstream ?? createInvokeUpstream(ctx, config);\r\n const corsConfig: CorsConfig = { origins: config.allowedOrigins };\r\n const rateLimiter = createRateLimiter();\r\n const sharedTreeSitterAdapter = new TreeSitterAdapter();\r\n\r\n /**\r\n * 把「AST 分析通道是否可用」接进能力位(`docs/需求分析.md` §61.10)。\r\n *\r\n * 能力位由 `checkPrerequisites(ctx)` 在**每次请求**里同步计算,而它拿不到适配器实例 ——\r\n * 所以这里把探针登记到模块级槽位,让那条同步路径能读到这个实例的真实状态。\r\n * 判据是「已初始化 **或** 运行时包可解析」:只用 `isAvailable()` 会在启动后很长一段时间\r\n * 恒为 false(初始化发生在第一次解析时),见 `isTreeSitterRuntimePresent` 的注释。\r\n *\r\n * 探针失败必须落到 `false` 而不是 `true`:这里任何异常都不该冒泡进 `checkPrerequisites`\r\n * 的 `catch` 分支(那会把 `codegraph` / `lens` 的真实探测结果一起丢掉)。\r\n */\r\n const probeAstSource = (): boolean => {\r\n try {\r\n return sharedTreeSitterAdapter.isAvailable() || isTreeSitterRuntimePresent();\r\n } catch {\r\n return false;\r\n }\r\n };\r\n ctx.effect(() => setAstSourceProbe(probeAstSource), 'codegraph: ast source capability probe');\r\n\r\n const rc: RouteContext = { deps, corsConfig, rateLimiter, invokeUpstream };\r\n\r\n registerStatusRoute(rc);\r\n registerHealthRoute(rc);\r\n registerWorkspaceRoute(rc);\r\n registerDataRoute(rc);\r\n registerScanRoute(rc);\r\n registerInitRoute(rc);\r\n registerWatchRoute(rc);\r\n registerDependenciesRoute(rc);\r\n registerDiffRoute(rc);\r\n registerAstRoute(rc, sharedTreeSitterAdapter);\r\n registerTreeSitterRoute(rc, sharedTreeSitterAdapter);\r\n registerBootstrapRoute(rc);\r\n registerNodeChildrenRoute(rc);\r\n registerFileOutlineRoute(rc);\r\n registerSymbolASTRoute(rc);\r\n registerEdgesRoute(rc);\r\n registerEventsRoute(rc);\r\n registerManageDeleteRoute(rc);\r\n registerManageMetricsRoute(rc);\r\n registerLayoutWorkerRoute(rc);\r\n // 变更频率(研究报告 §六 #11)刻意注册在**最后**:`registerRoutes` 的调用顺序\r\n // 就是 `webServer.register` 的调用顺序,而既有测试大量用「第 N 次注册」定位路由\r\n // (`getHandler(ctx, 3)`)。把它插在中间会让十几个无关用例整体错位一格,\r\n // 那种「改一个新增功能、红一片无关测试」的改动无法证明自己是对的。\r\n // 所有路由都是 `kind: 'exact'` 的精确路径,注册顺序不影响匹配语义。\r\n registerChurnRoute(rc);\r\n // 关键词图检索(cycle-111 / N-5)追加在 churn **之后**,理由同上:\r\n // 既有用例最高只引到 churn 这一格(索引 20),把新路由放在它前面会让 churn 变成 21,\r\n // 而 `getHandler(ctx, 20)` 一类断言就会错位。追加是唯一「零错位」的位置——\r\n // 代价是新路由的索引依赖 churn 先注册,因此两行必须保持相邻(下面有守卫测试)。\r\n registerKeywordQueryRoute(rc);\r\n registerPathsRoute(rc);\r\n // cycle-116 可观测性主轴:注册-生产-消费矩阵看板(四面全量清单 + 断链判定)。\r\n registerRegistryRoute(rc);\r\n // cycle-141:管理面板「更新」路由(codegraph_sync → 重扫)。\r\n // 刻意追加在**最后**:既有测试大量用「第 N 次注册」定位路由(`getHandler(ctx, N)`),\r\n // 插在中间会让一大片无关用例整体错位一格。所有路由都是 `kind: 'exact'` 的精确路径,\r\n // 注册顺序不影响匹配语义。代价是本路由的索引(24)依赖前面所有路由先注册。\r\n registerManageUpdateRoute(rc);\r\n // cycle-141 workstream E:Π 视图划分的用户侧出口(`/api/v1/codegraph/views`)。\r\n // 同样刻意追加在**最后**:既有测试用「第 N 次注册」定位路由(`getHandler(ctx, N)`),\r\n // 插在中间会让一大片无关用例整体错位一格。所有路由都是 `kind: 'exact'` 的精确路径,\r\n // 注册顺序不影响匹配语义。代价是本路由的索引(25)依赖前面所有路由先注册。\r\n registerViewsRoute(rc);\r\n\r\n ctx.effect(() => () => rateLimiter.reset(), 'codegraph: rate limiter cleanup');\r\n ctx.effect(() => () => sharedTreeSitterAdapter.destroy(), 'codegraph: tree-sitter adapter cleanup');\r\n // 快照防缩水守卫的「上一轮被拒指纹」是模块级状态:必须随插件卸载清空,\r\n // 否则同一进程内重新加载插件(HMR / profile 重载)会继承上一轮的拒绝记录,\r\n // 让本该被拦下的第一次缩水写入直接以「两次确认」放行。\r\n ctx.effect(() => () => resetSnapshotShrinkState(), 'codegraph: snapshot shrink guard reset');\r\n}\r\n","import { scoped } from '../shared/Logger.ts';\r\nimport { INCREMENTAL } from '../shared/constants.ts';\r\nimport { sameRepoId } from '../shared/pathUtils.ts';\r\nimport { readFilesFromDb, resolveDbPath, normalizeRepoPath, mapRowsToGraphNodes, mapRowsToGraphEdges, CODEGRAPH_SOURCE } from '../adapters/CodeGraphAdapter.ts';\r\nimport { computeGraphDiff } from '../merger/DiffEngine.ts';\r\nimport { applyGraphDelta, canApplyGraphDelta, isEmptyGraphDelta, graphDeltaSize, GRAPH_DELTA_SHRINK } from '../shared/graphDelta.ts';\r\nimport type { GraphDelta } from '../shared/graphDelta.ts';\r\nimport type { GraphData, GraphNode, GraphEdge } from '../types/index.ts';\r\n\r\nconst log = scoped('host');\r\n\r\nexport interface IncrementalUpdateInput {\r\n /** Workspace root; the index is resolved from it via `resolveDbPath`. */\r\n workspacePath: string;\r\n /** Repo-relative paths the watcher observed. Empty means \"nothing usable to target\". */\r\n changedFiles: string[];\r\n /** The graph the client currently renders; the diff is computed against this. */\r\n current: GraphData;\r\n /**\r\n * Absolute timestamp for the produced revision. Defaults to `Date.now()`; injectable so callers\r\n * can keep the revision time aligned with the rest of the event payload.\r\n */\r\n timestamp?: number;\r\n}\r\n\r\nexport interface IncrementalUpdateResult {\r\n /** Structural difference to publish. Empty when the changed files produced no graph change. */\r\n delta: GraphDelta;\r\n /** `current` with `delta` folded in — the host's new authoritative graph. */\r\n next: GraphData;\r\n /** How many index rows the targeted read pulled back, for logging/diagnostics. */\r\n scanned: { nodes: number; edges: number };\r\n}\r\n\r\n/** Does `node` live in one of the changed files (or under one of the changed directories)? */\r\nfunction belongsToChangedFile(node: GraphNode, changed: Set<string>): boolean {\r\n const file = normalizeRepoPath(node.filePath ?? '');\r\n if (!file) return false;\r\n if (changed.has(file)) return true;\r\n for (const c of changed) {\r\n if (file.startsWith(`${c}/`)) return true;\r\n }\r\n return false;\r\n}\r\n\r\n/** Recompute a graph's counts from its arrays; the index's own counts can lag the change. */\r\nfunction withCounts(nodes: GraphNode[], edges: GraphEdge[], previous: GraphData['metadata'], timestamp: number): GraphData {\r\n return {\r\n nodes,\r\n edges,\r\n metadata: {\r\n ...previous,\r\n timestamp,\r\n nodeCount: nodes.length,\r\n edgeCount: edges.length,\r\n totalNodeCount: nodes.length,\r\n totalEdgeCount: edges.length,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Build an incremental graph update from the files the watcher saw change (P2-21).\r\n *\r\n * The update layer used to be missing entirely: `scanAndPush` called `fetchMergedGraph` on every\r\n * debounce tick, re-reading and re-merging the whole index even for a one-line edit. This reads\r\n * back only the changed files' own rows and diffs them against the matching slice of the graph the\r\n * client already has, so the payload and the client's re-render are proportional to the edit rather\r\n * than to the repo.\r\n *\r\n * The diff is deliberately scoped: `before` is the subset of the current graph whose nodes live in\r\n * the changed files, `after` is what the index now reports for those same files. That scope is what\r\n * makes deletions detectable — a symbol removed from a file simply stops appearing in the targeted\r\n * read, while a node in an untouched file can never be reported as removed.\r\n *\r\n * Returns `null` when the targeted path is not usable (index unreadable, too many changed files, or\r\n * a delta that does not cleanly apply to the current graph); the caller then falls back to the\r\n * whole-graph fetch. A non-null result with an empty `delta` means the read succeeded and the graph\r\n * genuinely did not change.\r\n */\r\nexport async function buildIncrementalUpdate(input: IncrementalUpdateInput): Promise<IncrementalUpdateResult | null> {\r\n const { workspacePath, changedFiles, current } = input;\r\n if (changedFiles.length === 0) return null;\r\n // 跨工作区防护(P0):`current` 是 host 持有的\"最后一个图\",它可能属于另一个工作区——浏览器先给\r\n // 无索引的工作区跑了后台建图,`setLastGraphData` 就会把那个空图放进同一个槽里,而 watcher 监听的\r\n // 却是另一个仓库。此时若照常 diff,改动文件在 `current` 里找不到对应节点,全部会被判为 added,\r\n // 折叠后的 `next` 还会带着**旧工作区的 repoId** 被写回 host 权威图,把全量图替换成两个节点。\r\n // 这里直接返回 null 让调用方落回全量拉取,顺带把槽修回正确的工作区。\r\n if (!sameRepoId(current.metadata.repoId, workspacePath)) {\r\n log.warn('incremental update skipped: current graph belongs to another workspace', {\r\n workspacePath,\r\n heldRepoId: current.metadata.repoId,\r\n });\r\n return null;\r\n }\r\n if (changedFiles.length > INCREMENTAL.MAX_CHANGED_FILES) {\r\n log.info('incremental update skipped: too many changed files', { count: changedFiles.length, max: INCREMENTAL.MAX_CHANGED_FILES });\r\n return null;\r\n }\r\n\r\n let dbPath: string;\r\n try {\r\n dbPath = resolveDbPath(workspacePath);\r\n } catch (e) {\r\n log.warn('incremental update skipped: unresolvable workspace', e);\r\n return null;\r\n }\r\n\r\n // cycle-129 · T4:索引域抽象——上游库不可读(缺席/半写)时改用**原生索引**\r\n // (cycle-128 落库,schema 逐列兼容,readFilesFromDb 零改动可读)。此前该场景\r\n // 直接 return null → 全量实时扫描(TreeSitter wasm 全仓解析,秒级);原生索引\r\n // 在场时增量只读改动文件(毫秒级)。读出的行统一走既有 mapRowsToGraph* 转换\r\n // ——增量语义与上游路径完全一致(同一行契约),只是数据来源不同。\r\n let rows = readFilesFromDb(dbPath, changedFiles);\r\n if (!rows) {\r\n try {\r\n const { resolveNativeIndexPath } = await import('../adapters/nativeIndex.ts');\r\n const nativePath = resolveNativeIndexPath(workspacePath);\r\n rows = readFilesFromDb(nativePath, changedFiles);\r\n if (rows) {\r\n log.info('incremental update using native index', { changedFiles: changedFiles.length });\r\n }\r\n } catch (e) {\r\n log.warn('native index incremental read failed', { error: String(e) });\r\n }\r\n }\r\n if (!rows) {\r\n log.info('incremental update skipped: index unreadable', { dbPath });\r\n return null;\r\n }\r\n\r\n // 走与全量路径完全相同的行→图转换(mapRowsToGraphNodes / mapRowsToGraphEdges):父节点按每个\r\n // 节点自己的 file_path 解析、边经过自环与 FILTERED_EDGE_KINDS 过滤并按来源截断 import 上限。\r\n // 增量读回的节点还必须带上与 GraphDataMerger.merge 一致的 `properties.__source`,否则与已经过\r\n // merger 的 `current` 逐字段比对时每个节点都会被判为 modified,每次 watcher tick 都会重发全量。\r\n const touchedNodes = mapRowsToGraphNodes(rows.nodes, CODEGRAPH_SOURCE);\r\n\r\n // 边的\"合法节点集合\"必须是**全图**的节点集合,而不是本次读回的那一批:全量路径用的是索引里\r\n // 所有节点,而 `readFilesFromDb` 有意返回\"任一端点在改动集合内\"的边,所以一条从改动文件指向\r\n // 未改动文件的边,它的另一端并不在 touchedNodes 里。若只拿 touchedNodes 当合法集合,这些边会被\r\n // 误判为 removed,与全量路径再次漂移。`current` 就是客户端此刻持有的全图,取并集即可。\r\n const validNodeIds = new Set<string>(touchedNodes.map((n) => n.id as string));\r\n for (const n of current.nodes) validNodeIds.add(n.id as string);\r\n\r\n // import 上限必须基于\"当前图里该来源已经保留的 import 边\"来重算,不能只看本次读回的局部集合\r\n // (详见 mapRowsToGraphEdges 的注释:局部集合数到 20 会把全量路径早已丢弃的边重新加进来)。\r\n const keptImportIds = new Map<string, number[]>();\r\n for (const e of current.edges) {\r\n if (e.type !== 'import') continue;\r\n if (e.properties.__source !== CODEGRAPH_SOURCE) continue;\r\n const list = keptImportIds.get(e.source as string);\r\n if (list) list.push(Number(e.id));\r\n else keptImportIds.set(e.source as string, [Number(e.id)]);\r\n }\r\n\r\n const touchedEdges = mapRowsToGraphEdges(rows.edges, validNodeIds, { source: CODEGRAPH_SOURCE, keptImportIds });\r\n\r\n const changed = new Set<string>();\r\n for (const raw of changedFiles) {\r\n const normalized = normalizeRepoPath(raw);\r\n if (normalized) changed.add(normalized);\r\n }\r\n\r\n const beforeNodes = current.nodes.filter((n) => belongsToChangedFile(n, changed));\r\n const beforeNodeIds = new Set(beforeNodes.map((n) => n.id as string));\r\n const beforeEdges = current.edges.filter((e) =>\r\n beforeNodeIds.has(e.source as string) || beforeNodeIds.has(e.target as string));\r\n\r\n const delta = computeGraphDiff(beforeNodes, beforeEdges, touchedNodes, touchedEdges);\r\n\r\n // An incrementally-built delta must still describe a graph we can apply without inventing or\r\n // stranding entries; anything else (e.g. an edge into a node the read did not return) is a signal\r\n // that the targeted view is incomplete, so the caller falls back to a full fetch instead.\r\n //\r\n // 防缩水阈值与 client store 共用 `GRAPH_DELTA_SHRINK.MIN_RETAIN_RATIO`:定向读只拿回改动文件\r\n // 的行,一旦索引本身不完整(上游重建中途、DB 半写),折叠出的\"新图\"会把整棵子树删掉。宁可\r\n // 让调用方落回全量重取(一次传输),也不静默发布一张缩水的图(用户数据 + 渲染错误)。\r\n if (!canApplyGraphDelta(current, delta, { minRetainRatio: GRAPH_DELTA_SHRINK.MIN_RETAIN_RATIO })) {\r\n log.info('incremental update skipped: delta does not apply cleanly or would shrink the graph', {\r\n changedFiles: changedFiles.length,\r\n storedNodes: current.nodes.length,\r\n storedEdges: current.edges.length,\r\n ...graphDeltaSize(delta),\r\n });\r\n return null;\r\n }\r\n\r\n const timestamp = input.timestamp ?? Date.now();\r\n const folded = applyGraphDelta(current, delta);\r\n const next = withCounts(folded.nodes, folded.edges, current.metadata, timestamp);\r\n\r\n if (isEmptyGraphDelta(delta)) {\r\n log.debug('incremental update: no structural change', { changedFiles: changedFiles.length });\r\n }\r\n\r\n return {\r\n delta,\r\n next,\r\n scanned: { nodes: touchedNodes.length, edges: touchedEdges.length },\r\n };\r\n}\r\n","import { watch } from 'node:fs';\r\nimport { platform } from 'node:os';\r\nimport type { Context } from '@deepseek-ai/cordis';\r\nimport { scoped } from '../shared/Logger.ts';\r\nimport { WATCHER, ERROR_CODES, ERROR_MESSAGES } from '../shared/constants.ts';\r\nimport { computeGraphFingerprint } from '../shared/graphFingerprint.ts';\r\nimport { isEmptyGraphDelta } from '../shared/graphDelta.ts';\r\nimport { sameRepoId } from '../shared/pathUtils.ts';\r\nimport { buildIncrementalUpdate } from './incrementalUpdate.ts';\r\nimport { fetchMergedGraph } from '../tools.ts';\r\nimport { RepoId } from '../types/index.ts';\r\nimport type { GraphData } from '../types/index.ts';\r\nimport type { UpstreamInvoker } from '../adapters/IAdapter.ts';\r\nimport type { VisualizerConfig } from './config.ts';\r\n\r\nconst log = scoped('host');\r\nconst supportsRecursive = platform() === 'win32' || platform() === 'darwin';\r\n\r\nconst MAX_CHANGED_FILES_TRACK = 100;\r\n\r\n/** Minimal store surface the watcher needs to run the incremental path. */\r\nexport interface WatcherGraphState {\r\n getLastGraphData: () => GraphData | null;\r\n setLastGraphData: (data: GraphData | null) => void;\r\n}\r\n\r\nasync function scanAndPush(\r\n ctx: Context,\r\n invokeUpstream: UpstreamInvoker,\r\n path: string,\r\n changedFiles?: string[],\r\n state?: WatcherGraphState,\r\n): Promise<void> {\r\n log.info('scan requested', { path, incremental: changedFiles !== undefined });\r\n try {\r\n const repoId = path || 'workspace-default';\r\n\r\n // P2-21: when the watcher knows which files changed, try the targeted read first. It returns\r\n // null whenever the targeted view is unusable, in which case we fall through to the full fetch\r\n // below — correctness never depends on the incremental path succeeding.\r\n if (changedFiles && changedFiles.length > 0 && state) {\r\n const current = state.getLastGraphData();\r\n // P0: the host keeps exactly one `lastGraphData` slot, but the client can browse several\r\n // workspaces in one session. When a background build for workspace A lands in that slot while\r\n // the watcher is watching workspace B, diffing B's changed files against A's graph turns every\r\n // touched node into an \"added\" node and — worse — writes B's two-node result back into the slot\r\n // stamped with A's repoId, destroying the authoritative full graph. Only take the incremental\r\n // path when the held graph really belongs to the watched repo; otherwise fall through to the\r\n // full fetch below, which also repairs the slot.\r\n if (current && sameRepoId(current.metadata.repoId, repoId)) {\r\n // cycle-129 · T4:buildIncrementalUpdate 转异步(原生索引读侧动态 import)——\r\n // await 语义不变(watcher 扫描本身就在异步流程里)。\r\n const update = await buildIncrementalUpdate({ workspacePath: repoId, changedFiles, current });\r\n if (update) {\r\n state.setLastGraphData(update.next);\r\n const fingerprint = computeGraphFingerprint(update.next);\r\n // Nothing structural changed, so there is nothing for a consumer to re-render. Publishing\r\n // anyway would make every debounce tick look like a real update and defeat the point of\r\n // the fingerprint the previous round added.\r\n if (isEmptyGraphDelta(update.delta)) {\r\n log.info('incremental scan produced no change', {\r\n repoId, changedFiles: changedFiles.length, fingerprint,\r\n });\r\n return;\r\n }\r\n ctx.emit('codegraph-visualizer/graph/delta', {\r\n repoId: RepoId(repoId),\r\n delta: update.delta,\r\n timestamp: update.next.metadata.timestamp,\r\n fingerprint,\r\n changedFiles,\r\n });\r\n ctx.emit('codegraph-visualizer/graph/updated', {\r\n repoId: RepoId(repoId),\r\n nodeCount: update.next.metadata.nodeCount,\r\n edgeCount: update.next.metadata.edgeCount,\r\n timestamp: update.next.metadata.timestamp,\r\n fingerprint,\r\n changedFiles,\r\n incremental: true,\r\n });\r\n log.info('incremental scan completed', {\r\n repoId,\r\n changedFiles: changedFiles.length,\r\n scannedNodes: update.scanned.nodes,\r\n scannedEdges: update.scanned.edges,\r\n addedNodes: update.delta.addedNodes.length,\r\n removedNodes: update.delta.removedNodes.length,\r\n modifiedNodes: update.delta.modifiedNodes.length,\r\n addedEdges: update.delta.addedEdges.length,\r\n removedEdges: update.delta.removedEdges.length,\r\n modifiedEdges: update.delta.modifiedEdges.length,\r\n fingerprint,\r\n });\r\n return;\r\n }\r\n }\r\n }\r\n\r\n const data = await fetchMergedGraph(invokeUpstream, repoId, 'both');\r\n state?.setLastGraphData(data);\r\n // P0-6: emit the structural fingerprint alongside the payload so consumers can tell an\r\n // unchanged re-scan from a real change without deep-comparing nodes/edges.\r\n const fingerprint = computeGraphFingerprint(data);\r\n ctx.emit('codegraph-visualizer/graph/updated', {\r\n repoId: RepoId(repoId),\r\n nodeCount: data.metadata.nodeCount,\r\n edgeCount: data.metadata.edgeCount,\r\n timestamp: data.metadata.timestamp,\r\n fingerprint,\r\n ...(changedFiles ? { changedFiles } : {}),\r\n });\r\n ctx.emit('codegraph-visualizer/graph/data', {\r\n repoId: RepoId(repoId),\r\n nodes: data.nodes,\r\n edges: data.edges,\r\n timestamp: data.metadata.timestamp,\r\n fingerprint,\r\n ...(changedFiles ? { changedFiles } : {}),\r\n });\r\n log.info('scan completed', { repoId, nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount, changedFiles: changedFiles?.length ?? 0, fingerprint });\r\n } catch (e) {\r\n log.error('scan failed', e);\r\n }\r\n}\r\n\r\nexport function registerWatcher(\r\n ctx: Context,\r\n config: VisualizerConfig,\r\n invokeUpstream: UpstreamInvoker,\r\n state?: WatcherGraphState,\r\n): void {\r\n let watchTimer: ReturnType<typeof setTimeout> | null = null;\r\n let activeWatcher: { close: () => void } | null = null;\r\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\r\n let retryCount = 0;\r\n const MAX_RETRIES = WATCHER.MAX_RETRIES;\r\n const RETRY_BASE_DELAY = WATCHER.RETRY_BASE_DELAY_MS;\r\n const changedFilesSet = new Set<string>();\r\n\r\n const closeActiveWatcher = (): void => {\r\n if (activeWatcher) {\r\n try { activeWatcher.close(); } catch (e) { log.warn('Failed to close watcher', e); }\r\n activeWatcher = null;\r\n }\r\n if (watchTimer) { clearTimeout(watchTimer); watchTimer = null; }\r\n if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }\r\n changedFilesSet.clear();\r\n };\r\n\r\n const setupWatch = (watchPath: string): void => {\r\n try {\r\n const watchOpts = supportsRecursive ? { recursive: true } : {};\r\n activeWatcher = watch(watchPath, watchOpts, (_eventType: string, filename: string | null) => {\r\n if (!filename) return;\r\n const segments = filename.split(/[\\\\/]/);\r\n if (segments.includes('.codegraph')) return;\r\n if (segments.includes('node_modules')) return;\r\n if (segments.includes('.git')) return;\r\n\r\n changedFilesSet.add(filename);\r\n if (changedFilesSet.size > MAX_CHANGED_FILES_TRACK) {\r\n changedFilesSet.clear();\r\n }\r\n\r\n if (watchTimer) clearTimeout(watchTimer);\r\n watchTimer = setTimeout(() => {\r\n watchTimer = null;\r\n if (!activeWatcher) return;\r\n const changedFiles = [...changedFilesSet];\r\n changedFilesSet.clear();\r\n log.info('file changed, syncing + re-scanning', { count: changedFiles.length, files: changedFiles.slice(0, 5) });\r\n invokeUpstream('codegraph_sync', { path: watchPath })\r\n .then(() => scanAndPush(ctx, invokeUpstream, watchPath, changedFiles, state))\r\n .catch((e) => log.error('watch re-scan failed', e));\r\n }, config.watchDebounce);\r\n });\r\n retryCount = 0;\r\n } catch (e) {\r\n log.error('watch setup failed', e);\r\n if (retryCount < MAX_RETRIES) {\r\n retryCount++;\r\n const delay = RETRY_BASE_DELAY * Math.pow(2, retryCount - 1);\r\n log.info('retrying watch setup', { retryCount, delay, path: watchPath });\r\n retryTimer = setTimeout(() => setupWatch(watchPath), delay);\r\n } else {\r\n log.error('watch setup exhausted retries', { path: watchPath });\r\n ctx.emit('codegraph-visualizer/watch/error', { path: watchPath, message: ERROR_MESSAGES.WATCH_SETUP_FAILED?.en ?? 'Watch setup failed after retries', errorCode: ERROR_CODES.WATCH_SETUP_FAILED, timestamp: Date.now() });\r\n }\r\n }\r\n };\r\n\r\n ctx.effect(() => {\r\n const dispose = ctx.on('codegraph-visualizer/watch/toggle', (event) => {\r\n closeActiveWatcher();\r\n\r\n if (!event.enabled) {\r\n log.info('watch disabled');\r\n return;\r\n }\r\n\r\n log.info('watch enabled', { path: event.path });\r\n retryCount = 0;\r\n setupWatch(event.path);\r\n });\r\n return () => {\r\n dispose();\r\n closeActiveWatcher();\r\n };\r\n }, 'codegraph: watch toggle listener');\r\n}\r\n\r\nexport function registerEventListeners(\r\n ctx: Context,\r\n invokeUpstream: UpstreamInvoker,\r\n state?: WatcherGraphState,\r\n): void {\r\n ctx.effect(() => ctx.on('codegraph-visualizer/repo/imported', (event) => {\r\n ctx.emit('codegraph-visualizer/graph/updated', { repoId: event.repoId, nodeCount: 0, edgeCount: 0, timestamp: event.timestamp });\r\n }), 'codegraph: repo imported listener');\r\n\r\n ctx.effect(() => ctx.on('codegraph-visualizer/repo/scanned', (event) => {\r\n ctx.emit('codegraph-visualizer/graph/updated', { repoId: event.repoId, nodeCount: 0, edgeCount: 0, timestamp: event.timestamp });\r\n }), 'codegraph: repo scanned listener');\r\n\r\n ctx.effect(() => ctx.on('codegraph-visualizer/repo/request-scan', async (event) => {\r\n await scanAndPush(ctx, invokeUpstream, event.path, undefined, state);\r\n }), 'codegraph: repo request-scan listener');\r\n\r\n ctx.effect(() => ctx.on('codegraph-visualizer/graph/init', async (event) => {\r\n log.info('init requested', { path: event.path });\r\n try {\r\n const result = await invokeUpstream('codegraph_init', { path: event.path, force: true });\r\n const success = result !== null;\r\n const initResult: { success: boolean; path: string; message: string; errorCode?: string; timestamp: number } = {\r\n success,\r\n path: event.path,\r\n message: success ? 'Graph initialized successfully' : (ERROR_MESSAGES.INIT_FAILED?.en ?? 'Initialization failed'),\r\n timestamp: Date.now(),\r\n };\r\n if (!success) initResult.errorCode = ERROR_CODES.INIT_FAILED;\r\n ctx.emit('codegraph-visualizer/graph/init-result', initResult);\r\n if (success) {\r\n await scanAndPush(ctx, invokeUpstream, event.path, undefined, state);\r\n }\r\n } catch (e) {\r\n log.warn('graph init failed', e);\r\n ctx.emit('codegraph-visualizer/graph/init-result', {\r\n success: false,\r\n path: event.path,\r\n message: ERROR_MESSAGES.INIT_FAILED?.en ?? 'Initialization failed',\r\n errorCode: ERROR_CODES.INIT_FAILED,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n }), 'codegraph: graph init listener');\r\n}","import { createInterface } from 'node:readline';\r\nimport { scoped } from '../shared/Logger.ts';\r\nimport { PLUGIN_VERSION } from '../generated/version.ts';\r\nimport type { GraphData, GraphNode, GraphEdge } from '../types/index.ts';\r\nimport { safeString, safeNumber } from '../types/index.ts';\r\nimport { buildBackwardAdjacency } from '../shared/analysis/AdjacencyList.ts';\r\nimport { bfsFrontierExpansion } from '../shared/analysis/GraphBFS.ts';\r\nimport { MCP, ERROR_CODES } from '../shared/constants.ts';\r\n\r\nconst log = scoped('mcp-server');\r\nconst MAX_MESSAGE_LENGTH = MCP.MAX_MESSAGE_LENGTH;\r\n\r\ninterface McpTool {\r\n name: string;\r\n description: string;\r\n inputSchema: Record<string, unknown>;\r\n handler: (args: Record<string, unknown>) => Promise<unknown>;\r\n}\r\n\r\nexport class McpServer {\r\n private tools: Map<string, McpTool> = new Map();\r\n private running = false;\r\n private rl: ReturnType<typeof createInterface> | null = null;\r\n private messageQueue: string[] = [];\r\n private processing = false;\r\n\r\n registerTool(tool: McpTool): void {\r\n this.tools.set(tool.name, tool);\r\n }\r\n\r\n async start(getGraphData: () => GraphData | null): Promise<void> {\r\n this.registerGraphTools(getGraphData);\r\n\r\n this.rl = createInterface({ input: process.stdin, output: process.stdout });\r\n this.running = true;\r\n\r\n this.rl.on('line', (line: string) => {\r\n this.messageQueue.push(line);\r\n void this.processQueue();\r\n });\r\n\r\n this.rl.on('close', () => {\r\n this.running = false;\r\n log.info('MCP server stopped');\r\n });\r\n\r\n this.rl.on('error', (err: Error) => {\r\n log.error('MCP server stdin error', err);\r\n this.running = false;\r\n try { this.rl?.close(); } catch { /* already closed */ }\r\n this.rl = null;\r\n });\r\n\r\n log.info('MCP server started', { tools: this.tools.size });\r\n }\r\n\r\n\r\n private async processQueue(): Promise<void> {\r\n if (this.processing) return;\r\n this.processing = true;\r\n while (this.messageQueue.length > 0) {\r\n const line = this.messageQueue.shift()!;\r\n try {\r\n if (line.length > MAX_MESSAGE_LENGTH) {\r\n log.warn('MCP message too large, skipping', { length: line.length });\r\n continue;\r\n }\r\n const msg = JSON.parse(line);\r\n const response = await this.handleMessage(msg);\r\n if (response) {\r\n process.stdout.write(JSON.stringify(response) + '\\n');\r\n }\r\n } catch (e) {\r\n log.warn('MCP message handling failed', e);\r\n try {\r\n const errorResponse = {\r\n jsonrpc: '2.0',\r\n id: null,\r\n error: { code: -32700, message: 'Parse error' },\r\n };\r\n process.stdout.write(JSON.stringify(errorResponse) + '\\n');\r\n } catch (e) { log.warn('Failed to send MCP error response', e); }\r\n }\r\n }\r\n this.processing = false;\r\n }\r\n\r\n private async handleMessage(msg: Record<string, unknown>): Promise<Record<string, unknown> | null> {\r\n const method = msg['method'] as string;\r\n const id = msg['id'];\r\n if (id !== null && typeof id !== 'string' && typeof id !== 'number') {\r\n return {\r\n jsonrpc: '2.0',\r\n id: null,\r\n error: { code: -32600, message: 'Invalid Request: id must be string, number, or null' },\r\n };\r\n }\r\n if (msg['jsonrpc'] !== '2.0') {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32600, message: 'Invalid Request: jsonrpc must be \"2.0\"' },\r\n };\r\n }\r\n\r\n if (method === 'initialize') {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n result: {\r\n protocolVersion: '2024-11-05',\r\n capabilities: { tools: {} },\r\n serverInfo: { name: 'dsh-codegraph-visualizer', version: PLUGIN_VERSION },\r\n },\r\n };\r\n }\r\n\r\n if (method === 'tools/list') {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n result: {\r\n tools: Array.from(this.tools.values()).map((t) => ({\r\n name: t.name,\r\n description: t.description,\r\n inputSchema: t.inputSchema,\r\n })),\r\n },\r\n };\r\n }\r\n\r\n if (method === 'tools/call') {\r\n const params = msg['params'] as Record<string, unknown>;\r\n if (typeof params !== 'object' || params === null) {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32602, message: 'Invalid params: expected object' },\r\n };\r\n }\r\n const toolName = typeof params['name'] === 'string' ? params['name'] : '';\r\n if (!toolName) {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32602, message: 'Invalid params: tool name is required and must be a non-empty string' },\r\n };\r\n }\r\n const args = (typeof params['arguments'] === 'object' && params['arguments'] !== null\r\n ? params['arguments'] as Record<string, unknown>\r\n : {});\r\n const tool = this.tools.get(toolName);\r\n if (!tool) {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32601, message: `Unknown tool: ${toolName}` },\r\n };\r\n }\r\n try {\r\n const result = await tool.handler(args);\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },\r\n };\r\n } catch (e) {\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32603, message: e instanceof Error ? e.message : String(e) },\r\n };\r\n }\r\n }\r\n\r\n return {\r\n jsonrpc: '2.0',\r\n id,\r\n error: { code: -32601, message: `Method not found: ${method}` },\r\n };\r\n }\r\n\r\n private registerGraphTools(getGraphData: () => GraphData | null): void {\r\n this.registerTool({\r\n name: 'graph_status',\r\n description: 'Get the current graph status including node and edge counts',\r\n inputSchema: { type: 'object', properties: {} },\r\n handler: async () => {\r\n const data = getGraphData();\r\n if (!data) return { status: 'no data', nodes: 0, edges: 0 };\r\n return { status: 'ready', nodes: data.metadata.nodeCount, edges: data.metadata.edgeCount };\r\n },\r\n });\r\n\r\n this.registerTool({\r\n name: 'graph_data',\r\n description: 'Get the full graph data (nodes and edges)',\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n limit: { type: 'number', description: 'Max nodes to return (default 100)' },\r\n },\r\n },\r\n handler: async (args) => {\r\n const data = getGraphData();\r\n if (!data) return { nodes: [], edges: [] };\r\n const limit = safeNumber(args['limit'], 100);\r\n return {\r\n nodes: data.nodes.slice(0, limit).map((n: GraphNode) => ({ id: n.id, label: n.label, type: n.type, filePath: n.filePath })),\r\n edges: data.edges.slice(0, limit * 2).map((e: GraphEdge) => ({ source: e.source, target: e.target, type: e.type })),\r\n totalNodes: data.metadata.nodeCount,\r\n totalEdges: data.metadata.edgeCount,\r\n };\r\n },\r\n });\r\n\r\n this.registerTool({\r\n name: 'graph_symbol',\r\n description: 'Search for symbols in the graph by name',\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n search: { type: 'string', description: 'Symbol name to search for' },\r\n },\r\n required: ['search'],\r\n },\r\n handler: async (args) => {\r\n const data = getGraphData();\r\n if (!data) return { results: [] };\r\n const search = safeString(args['search']).toLowerCase();\r\n const exact: GraphNode[] = [];\r\n const partial: GraphNode[] = [];\r\n for (const n of data.nodes) {\r\n const labelLower = n.label.toLowerCase();\r\n if (labelLower === search) exact.push(n);\r\n else if (labelLower.includes(search)) partial.push(n);\r\n }\r\n const results = [...exact, ...partial].slice(0, 20)\r\n .map((n: GraphNode) => ({ id: n.id, label: n.label, type: n.type, filePath: n.filePath, lineNumber: n.lineNumber }));\r\n return { results, count: results.length };\r\n },\r\n });\r\n\r\n this.registerTool({\r\n name: 'graph_impact',\r\n description: 'Get impact analysis for a symbol (what depends on it)',\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n symbol: { type: 'string', description: 'Symbol name to analyze' },\r\n depth: { type: 'number', description: 'Depth of impact analysis (default 2)' },\r\n },\r\n required: ['symbol'],\r\n },\r\n handler: async (args) => {\r\n const data = getGraphData();\r\n if (!data) return { impacted: [] };\r\n const symbol = safeString(args['symbol']);\r\n const depth = safeNumber(args['depth'], 2);\r\n\r\n const targetNode = data.nodes.find((n: GraphNode) => n.label === symbol || n.label.endsWith(`.${symbol}`));\r\n if (!targetNode) return { impacted: [], message: 'Symbol not found', errorCode: ERROR_CODES.SYMBOL_NOT_FOUND };\r\n\r\n const reverseAdj = buildBackwardAdjacency(data.edges);\r\n const result = bfsFrontierExpansion(reverseAdj, targetNode.id, depth, MCP.MAX_IMPACT_RESULTS);\r\n return { impacted: result.impacted, count: result.count, truncated: result.truncated };\r\n },\r\n });\r\n }\r\n\r\n stop(): void {\r\n this.running = false;\r\n if (this.rl) {\r\n this.rl.close();\r\n this.rl = null;\r\n }\r\n }\r\n\r\n isRunning(): boolean {\r\n return this.running;\r\n }\r\n}","import { createRequire } from 'node:module';\r\nimport { join } from 'node:path';\r\nimport { mkdirSync } from 'node:fs';\r\nimport { scoped } from '../../shared/Logger.ts';\r\nimport { FINGERPRINT } from '../../shared/constants.ts';\r\n\r\nconst log = scoped('db:repository');\r\n\r\n/** Row shape of the `graph_snapshots` table (DDL authored in MIGRATION_SQL). */\r\nexport interface GraphSnapshot {\r\n id: number;\r\n repoId: string;\r\n snapshotHash: string;\r\n nodeCount: number;\r\n edgeCount: number;\r\n data: Buffer | null;\r\n createdAt: number;\r\n}\r\n\r\n/** Row shape of the `analysis_results` table. */\r\nexport interface AnalysisResult {\r\n id: number;\r\n repoId: string;\r\n analysisType: string;\r\n targetNode: string | null;\r\n result: string;\r\n createdAt: number;\r\n}\r\n\r\n/** Row shape of the `layout_presets` table. */\r\nexport interface LayoutPreset {\r\n id: number;\r\n repoId: string;\r\n name: string;\r\n layoutType: string;\r\n positions: string;\r\n createdAt: number;\r\n}\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\ntype SqliteDb = {\r\n exec(sql: string): void;\r\n prepare(sql: string): {\r\n run(...params: unknown[]): void;\r\n all(...params: unknown[]): unknown[];\r\n get(...params: unknown[]): unknown;\r\n };\r\n close(): void;\r\n};\r\n\r\nexport type SnapshotRecord = GraphSnapshot;\r\nexport type AnalysisRecord = AnalysisResult;\r\nexport type LayoutPresetRecord = Pick<LayoutPreset, 'id' | 'name' | 'layoutType' | 'positions'>;\r\n\r\nconst SQL = {\r\n SAVE_SNAPSHOT: `INSERT INTO graph_snapshots (repo_id, snapshot_hash, node_count, edge_count, data, created_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(repo_id, snapshot_hash) DO UPDATE SET node_count = excluded.node_count, edge_count = excluded.edge_count, data = excluded.data, created_at = excluded.created_at`,\r\n GET_LATEST_SNAPSHOT: `SELECT id, repo_id, snapshot_hash, node_count, edge_count, data, created_at FROM graph_snapshots WHERE repo_id = ? ORDER BY created_at DESC LIMIT 1`,\r\n SAVE_ANALYSIS: `INSERT INTO analysis_results (repo_id, analysis_type, target_node, result, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(repo_id, analysis_type, IFNULL(target_node, '')) DO UPDATE SET result = excluded.result, created_at = excluded.created_at`,\r\n GET_ANALYSIS: `SELECT id, repo_id, analysis_type, target_node, result, created_at FROM analysis_results WHERE repo_id = ? AND analysis_type = ? AND IFNULL(target_node, '') = IFNULL(?, '') ORDER BY created_at DESC LIMIT 1`,\r\n SET_PREFERENCE: `INSERT INTO user_preferences (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\r\n GET_PREFERENCE: `SELECT value FROM user_preferences WHERE key = ?`,\r\n SAVE_LAYOUT_PRESET: `INSERT INTO layout_presets (repo_id, name, layout_type, positions, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(repo_id, name) DO UPDATE SET layout_type = excluded.layout_type, positions = excluded.positions, created_at = excluded.created_at`,\r\n GET_LAYOUT_PRESETS: `SELECT id, name, layout_type, positions FROM layout_presets WHERE repo_id = ? ORDER BY created_at DESC`,\r\n COUNT_SNAPSHOTS: `SELECT COUNT(*) AS n FROM graph_snapshots WHERE repo_id = ?`,\r\n PRUNE_SNAPSHOTS: `DELETE FROM graph_snapshots WHERE repo_id = ? AND id NOT IN (SELECT id FROM graph_snapshots WHERE repo_id = ? ORDER BY created_at DESC LIMIT ?)`,\r\n PRUNE_ANALYSIS: `DELETE FROM analysis_results WHERE repo_id = ? AND analysis_type = ? AND id NOT IN (SELECT id FROM analysis_results WHERE repo_id = ? AND analysis_type = ? ORDER BY created_at DESC LIMIT ?)`,\r\n} as const;\r\n\r\nconst MIGRATION_SQL = [\r\n `CREATE TABLE IF NOT EXISTS graph_snapshots (id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT NOT NULL, snapshot_hash TEXT NOT NULL, node_count INTEGER NOT NULL DEFAULT 0, edge_count INTEGER NOT NULL DEFAULT 0, data BLOB, created_at INTEGER NOT NULL DEFAULT 0)`,\r\n `CREATE TABLE IF NOT EXISTS analysis_results (id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT NOT NULL, analysis_type TEXT NOT NULL, target_node TEXT, result TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT 0)`,\r\n `CREATE TABLE IF NOT EXISTS user_preferences (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL DEFAULT 0)`,\r\n `CREATE TABLE IF NOT EXISTS layout_presets (id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT NOT NULL, name TEXT NOT NULL, layout_type TEXT NOT NULL, positions TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT 0)`,\r\n `CREATE INDEX IF NOT EXISTS idx_snapshots_repo ON graph_snapshots(repo_id)`,\r\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_snapshots_repo_hash ON graph_snapshots(repo_id, snapshot_hash)`,\r\n `CREATE INDEX IF NOT EXISTS idx_snapshots_repo_created ON graph_snapshots(repo_id, created_at)`,\r\n `CREATE INDEX IF NOT EXISTS idx_analysis_repo_type ON analysis_results(repo_id, analysis_type)`,\r\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_analysis_repo_type_node ON analysis_results(repo_id, analysis_type, IFNULL(target_node, ''))`,\r\n `CREATE INDEX IF NOT EXISTS idx_analysis_repo_type_created ON analysis_results(repo_id, analysis_type, created_at)`,\r\n `CREATE INDEX IF NOT EXISTS idx_layout_presets_repo ON layout_presets(repo_id)`,\r\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_layout_presets_repo_name ON layout_presets(repo_id, name)`,\r\n `CREATE INDEX IF NOT EXISTS idx_layout_presets_repo_created ON layout_presets(repo_id, created_at)`,\r\n] as const;\r\n\r\nexport class GraphRepository {\r\n private db: SqliteDb | null = null;\r\n\r\n private dbPath: string;\r\n\r\n constructor(workspacePath: string) {\r\n this.dbPath = join(workspacePath, '.codegraph-visualizer', 'viz.db');\r\n }\r\n\r\n init(): boolean {\r\n try {\r\n mkdirSync(join(this.dbPath, '..'), { recursive: true });\r\n const { DatabaseSync } = require('node:sqlite') as typeof import('node:sqlite');\r\n this.db = new DatabaseSync(this.dbPath) as unknown as SqliteDb;\r\n this.db.exec('PRAGMA journal_mode = WAL');\r\n this.db.exec('PRAGMA busy_timeout = 5000');\r\n this.db.exec('PRAGMA synchronous = NORMAL');\r\n for (const sql of MIGRATION_SQL) {\r\n this.db!.exec(sql);\r\n }\r\n log.info('graph repository initialized', { path: this.dbPath });\r\n return true;\r\n } catch (e) {\r\n log.warn('graph repository init failed', e);\r\n return false;\r\n }\r\n }\r\n\r\n\r\n saveSnapshot(repoId: string, hash: string, nodeCount: number, edgeCount: number, data: Buffer): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.SAVE_SNAPSHOT).run(repoId, hash, nodeCount, edgeCount, data, Date.now());\r\n this.pruneSnapshots(repoId);\r\n } catch (e) { log.warn('saveSnapshot failed', e); }\r\n }\r\n\r\n /** Keep only the most recent `keep` snapshots per repo to bound disk growth. */\r\n pruneSnapshots(repoId: string, keep: number = FINGERPRINT.SNAPSHOT_KEEP): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.PRUNE_SNAPSHOTS).run(repoId, repoId, keep);\r\n } catch (e) { log.warn('pruneSnapshots failed', e); }\r\n }\r\n\r\n getLatestSnapshot(repoId: string): SnapshotRecord | null {\r\n if (!this.db) return null;\r\n try {\r\n const row = this.db.prepare(SQL.GET_LATEST_SNAPSHOT).get(repoId) as Record<string, unknown> | undefined;\r\n if (!row) return null;\r\n return {\r\n id: row['id'] as number,\r\n repoId: row['repo_id'] as string,\r\n snapshotHash: row['snapshot_hash'] as string,\r\n nodeCount: row['node_count'] as number,\r\n edgeCount: row['edge_count'] as number,\r\n data: row['data'] as Buffer | null,\r\n createdAt: row['created_at'] as number,\r\n };\r\n } catch (e) { log.warn('getLatestSnapshot failed', e); return null; }\r\n }\r\n\r\n /** Number of stored snapshots for a repo. Diagnostics and fingerprint-dedup assertions. */\r\n countSnapshots(repoId: string): number {\r\n if (!this.db) return 0;\r\n try {\r\n const row = this.db.prepare(SQL.COUNT_SNAPSHOTS).get(repoId) as Record<string, unknown> | undefined;\r\n return typeof row?.['n'] === 'number' ? (row['n'] as number) : 0;\r\n } catch (e) { log.warn('countSnapshots failed', e); return 0; }\r\n }\r\n\r\n saveAnalysis(repoId: string, type: string, result: string, targetNode?: string): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.SAVE_ANALYSIS).run(repoId, type, targetNode ?? null, result, Date.now());\r\n this.pruneAnalysisResults(repoId, type);\r\n } catch (e) { log.warn('saveAnalysis failed', e); }\r\n }\r\n\r\n pruneAnalysisResults(repoId: string, type: string, keep = 10): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.PRUNE_ANALYSIS).run(repoId, type, repoId, type, keep);\r\n } catch (e) { log.warn('pruneAnalysisResults failed', e); }\r\n }\r\n\r\n getAnalysis(repoId: string, type: string, targetNode?: string): AnalysisRecord | null {\r\n if (!this.db) return null;\r\n try {\r\n const row = this.db.prepare(SQL.GET_ANALYSIS).get(repoId, type, targetNode ?? null) as Record<string, unknown> | undefined;\r\n if (!row) return null;\r\n return {\r\n id: row['id'] as number,\r\n repoId: row['repo_id'] as string,\r\n analysisType: row['analysis_type'] as string,\r\n targetNode: row['target_node'] as string | null,\r\n result: row['result'] as string,\r\n createdAt: row['created_at'] as number,\r\n };\r\n } catch (e) { log.warn('getAnalysis failed', e); return null; }\r\n }\r\n\r\n setPreference(key: string, value: string): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.SET_PREFERENCE).run(key, value, Date.now());\r\n } catch (e) { log.warn('setPreference failed', e); }\r\n }\r\n\r\n getPreference(key: string): string | null {\r\n if (!this.db) return null;\r\n try {\r\n const row = this.db.prepare(SQL.GET_PREFERENCE).get(key) as Record<string, unknown> | undefined;\r\n return (row?.['value'] as string) ?? null;\r\n } catch (e) { log.warn('getPreference failed', e); return null; }\r\n }\r\n\r\n saveLayoutPreset(repoId: string, name: string, layoutType: string, positions: string): void {\r\n if (!this.db) return;\r\n try {\r\n this.db.prepare(SQL.SAVE_LAYOUT_PRESET).run(repoId, name, layoutType, positions, Date.now());\r\n } catch (e) { log.warn('saveLayoutPreset failed', e); }\r\n }\r\n\r\n getLayoutPresets(repoId: string): LayoutPresetRecord[] {\r\n if (!this.db) return [];\r\n try {\r\n const rows = this.db.prepare(SQL.GET_LAYOUT_PRESETS).all(repoId) as Array<Record<string, unknown>>;\r\n return rows.map((row) => ({\r\n id: row['id'] as number,\r\n name: row['name'] as string,\r\n layoutType: row['layout_type'] as string,\r\n positions: row['positions'] as string,\r\n }));\r\n } catch (e) { log.warn('getLayoutPresets failed', e); return []; }\r\n }\r\n\r\n close(): void {\r\n\r\n if (this.db) {\r\n try { this.db.exec('PRAGMA busy_timeout = 0'); } catch { /* ignore */ }\r\n try { this.db.close(); } catch { /* ignore */ }\r\n this.db = null;\r\n }\r\n }\r\n\r\n isReady(): boolean {\r\n return this.db !== null;\r\n }\r\n}","/**\r\n * 把本插件的 14 个 `graph_*` 工具注册成一条**运行时 Skill**(`ctx.skills.register`)。\r\n *\r\n * ## 为什么需要这条 Skill(而工具描述不够)\r\n *\r\n * 工具描述只回答「这个工具做什么」,回答不了「什么时候该用它」。DSH 的模型侧工具目录\r\n * 每条只给 name + description,模型在「我要在代码库里找某段逻辑」时并不知道仓库里还有一个\r\n * 可查询的图 —— 除非有人在指令层告诉它。这正是 Skill 的定位:**可选的指令,不是会话事件**\r\n * (kb `site/reference/subsystems/skills.md:9`)。\r\n *\r\n * 本仓的 14 个工具全部带 `presentCall` / `presentResult`(`src/tools.ts`),所以「轨迹里\r\n * 能看到本插件的调用」在**契约层**已经成立;缺的是**触发**:模型不知道何时挑它们。\r\n * 用户 objective #3 的「DSH 大模型使用时 dsh 的轨迹要有本技能插件的调用」因此是两件事:\r\n * 可见性(已有)+ 可发现性(本文件)。\r\n *\r\n * ## 契约依据(逐条核验自 dsh-plugin-dev-kb 知识库)\r\n *\r\n * - `ctx.skills.register(skill: SkillRegistration): () => void`\r\n * —— kb `site/reference/subsystems/skills.md:299`。JSDoc 原文:\r\n * 「Register a borrowed readonly runtime skill into the calling context's layer...\r\n * Same-name runtime entries in one layer are first-wins; a duplicate logs a warning\r\n * and receives a no-op disposer so it cannot remove the winner.」\r\n * 因此**重复注册不会抛错**,也不会顶掉别人 —— 这让注入失败时的降级是安全的。\r\n * - `SkillRegistration` 的必填字段只有 `name` / `description` / `content`\r\n * (`SkillDefinition` 去掉 `invocation` 与 `provider` 两项可选后,\r\n * kb 同文件 :183-190)。`invocation` 省略 = 模型与用户两侧都可见(`:132`)。\r\n * - 「Project entries outrank runtime entries, which outrank user entries, within one layer」\r\n * —— 运行时条目在**同一层内**排最后。所以用户在 `.dsh/skills/` 里放同名 skill\r\n * 会胜出,本条目不会与之冲突。这是刻意的:插件提供的是默认指令,项目可以覆盖。\r\n *\r\n * ## 为什么用响应式 `ctx.inject(['skills'], …)` 而不是在 apply 里 `ctx.get`\r\n *\r\n * 与 `src/client/integrations/CommandUiIntegration.ts` 同一条教训(cycle-83 结构性修复):\r\n * `ctx.get(name, strict = true)` 只返回**提供者 fiber 当前 active** 的实现\r\n * (`cordis/lib/index.js` 的 `_getImpl`,`reflect.d.ts:16`)。宿主侧 `skills` 由\r\n * `dsh-skill` bundle 行提供,它的 fiber 在本插件 `apply()` 的那一瞬间可能仍在 loading\r\n * (state 1),一次性探测会把「稍后可用」判成「不存在」,整条 Skill 贡献永久丢失,\r\n * 而且**不报错**。`ctx.inject` 的回调在依赖到齐后才跑,服务被替换时会先卸载旧 scope\r\n * 再重跑,依赖永不出现时回调永不执行且不阻塞父插件 apply。\r\n *\r\n * 注意:这里**不**把 `skills` 写进静态 `inject`。静态 inject 必须全部满足,否则整个插件\r\n * 被置为 INACTIVE —— 拿「一条可选 Skill」换「插件不加载」是不可接受的取舍\r\n * (与 `src/client/index.ts:36` 对 `betterSidebar`/`commandUi` 的处置同源)。\r\n *\r\n * 零第三方依赖,只依赖一个可选的宿主服务。\r\n *\r\n * @module host/skills\r\n */\r\n\r\nimport type { Context } from '@deepseek-ai/cordis';\r\nimport { scoped } from '../shared/Logger.ts';\r\n\r\nconst log = scoped('host');\r\n\r\n/** 运行时 Skill 的名字(kebab-case,与工具前缀一致,便于模型联想)。 */\r\nexport const GRAPH_SKILL_NAME = 'codegraph-visualizer';\r\n\r\n/**\r\n * Skill 正文。\r\n *\r\n * 写作原则(照 `agent-experience` 的做法):**先给触发条件,再给选择表,最后给禁忌**。\r\n * 模型读这段是为了「决定要不要调用」,不是为了「学习 API」;API 细节留在工具描述里,\r\n * 重复一遍只会挤占上下文。\r\n */\r\nconst SKILL_CONTENT = `# 代码图谱(dsh-codegraph-visualizer)\r\n\r\n本仓库的代码关系图可以通过 \\`graph_*\\` 系列工具**查询**,而不是先 grep 再逐个读文件。\r\n图由三部分合成:上游 codegraph 索引(SQLite edges 表)、上游 lens 分析、以及内置的\r\nmanifest 依赖解析。三者都没有时插件仍可提供 AST 解析(\\`/ast\\`、\\`/outline\\`),\r\n但**没有**图数据 —— 此时这些工具会返回\"没有图\",那不是错误,是能力位如实上报。\r\n\r\n## 什么时候用\r\n\r\n按优先级从高到低:\r\n\r\n1. **\"谁调用了 X\" / \"X 调用了谁\" / \"改这个函数会影响什么\"**\r\n → \\`graph_impact\\`(影响面)或 \\`graph_path_find\\`(两点间最短调用路径)。\r\n 这比 grep 函数名可靠,因为 grep 会命中注释、字符串、同名不同作用域。\r\n2. **\"这个仓库是怎么组织的\" / \"模块之间怎么依赖\"**\r\n → \\`graph_data\\` 取合并图,再 \\`graph_query\\` 用关键词定位到具体子图。\r\n3. **\"有没有循环依赖\" / \"死代码\" / \"哪些函数是核心\"**\r\n → \\`graph_cycles\\` / \\`graph_health\\` / \\`graph_rules_check\\`。\r\n4. **\"用几个关键词找一段逻辑\"**(不确定符号名)\r\n → \\`graph_query\\`。它做的是:关键词 → 种子(精确 > 前缀 > 子串 > 文件路径)→\r\n 有界子图 → **token 预算内**的文本摘要。中文关键词同样支持。\r\n \\`depth\\`(0–3,缺省 1)与 \\`tokenBudget\\`(50–8000,缺省 1200)是**模型可传参数**,\r\n 面板上的可调控件与它们同源。结果里的 \\`truncated: true\\` 表示摘要被截断了 ——\r\n 那是预算限制,不是\"没有更多内容\",要更多就提高 \\`tokenBudget\\` 或缩小 \\`depth\\`。\r\n5. **\"这个节点到底是什么\"** → \\`graph_symbol\\`(按 id 取详情)。\r\n6. **\"只看这一个节点附近,别把整张图铺开\"** → \\`graph_focus\\`(焦点 + 深度上下文)。\r\n 它是\"在图上做一次局部放大\",不是筛选 —— 筛选请用界面,查询请用 \\`graph_query\\`。\r\n7. **\"图引擎本身现在健康吗\" / \"增量索引有没有降级\"**\r\n → \\`graph_integrity\\`(白盒体检:变更日志水位 / 逐规则索引计数 / 增量管道是否休眠)。\r\n \\`incrementalIdle: true\\` 是正常态(枚举用一次性内联索引),不是故障;\r\n \\`drainChanges: true\\` 会**消费掉**变更日志(缺省只读水位,不排空)。\r\n8. **\"关系有先后顺序\"**(例如\"哪些地方是先调用再跨文件导入再读写依赖\")\r\n → \\`graph_path_pattern\\`。它和 \\`graph_path_enumerate\\` 的差别**只在表达能力**:\r\n 后者的 \\`edgeTypes\\` 是扁平集合(每一步走这些类型之一),表达不了顺序;\r\n 本工具的 \\`pattern\\` 是**边类型序列正则**(璇玑图的\"可枚举遍历规则\"):\r\n \\`call+\\` 传递调用闭包、\\`call{2,4}\\` 2~4 跳调用链、\r\n \\`(import|dependency)+\\` 任一依赖闭包、\\`call* . import\\` 先任意调用再跨文件导入、\r\n \\`call+ . import? . dependency\\` 跨视图路径。语法:类型名用空格或 \\`.\\` 拼接,\r\n \\`|\\` 为并、\\`(...)\\` 分组、\\`?\\` \\`*\\` \\`+\\` \\`{n,m}\\` 为量词。\r\n **模式写错会返回带 \\`^\\` 指位的诊断**,不要把它当成\"零结果\"——零结果是 \\`paths: []\\` 且没有 \\`error\\` 字段。\n 时序两刀(cycle-137/138):\\`commitLog\\`(+\\`days\\`/\\`from\\`/\\`to\\`) 先做**图级**切片\n (决定哪些文件存在),\\`since\\`(unix 毫秒)再做**边级**过滤(只保留首次共同触及\n ≥ since 的关系——它是边引入时间的下界,保留的边保证真正新)。\n \\`minVolatility\\`/\\`maxVolatility\\`(挥发度)**当前被显式拒绝**:需要边级事实,\n \\`--name-only\\` 只有文件级事实 ⇒ 返回 \\`edgeTemporal.available: false\\` + reason,\n 这不是缺陷,是诚实降级。\r\n\r\n9. **\"只看一个维度\" / \"这段代码属于哪个领域\" / \"这段代码是谁写的\" / \"哪些关系是最近才出现的\"**\r\n → \\`graph_slice\\`。四个正交视图共用**一个**入口(研究方案 §7.1 的统一接口):\r\n \\`view='structural'\\`(符号与静态关系)、\\`view='temporal'\\`(commit 切片 + 边挥发度)、\r\n \\`view='ownership'\\`(git blame 作者归属 + bus factor)、\\`view='domain'\\`(Louvain 社区 → 领域标签)。\r\n \\`granularity\\` 决定聚合层(\\`repo\\` 原样 / \\`file\\` 折叠成文件节点 / \\`symbol\\` 缺省);\r\n \\`focus\\` 取一块(nodeId/label 的 1 跳邻域,或文件路径)。\r\n \\`view='temporal'\\` 需要 \\`commitLog\\`(\\`git log --name-only --format=%x1e%H%x1f%ct\\` 的原始输出),\r\n \\`view='ownership'\\` 需要 \\`blameLog\\`(\\`git blame --porcelain\\` 的原始输出)。\r\n 缺这两个输入时返回 \\`viewDataAvailable: false\\` + \\`reason\\` —— 那是**显式降级**,\r\n 不要把它读成\"这个维度没有东西\"。\r\n\r\n10. **\"这个符号属于哪些视图\" / \"这条边是不是跨领域的桥\"**\r\n → \\`graph_views\\`。这是研究方案 §4 的 \\`Π\\` **视图划分函数**:它回答\r\n \"每个节点/边属于哪些正交视图\",而不是\"按某个视图切出子图\"(那是 \\`graph_slice\\`)。\r\n 四个视图:\\`structural\\`(基图,**恒属**所有节点/边)、\\`temporal\\`(被 commit 切片触及的\r\n 文件)、\\`ownership\\`(有 git blame 记录的节点)、\\`domain\\`(Louvain 分配到的节点)。\r\n \\`commitLog\\` / \\`blameLog\\` 的用法与 \\`graph_slice\\` 相同;\\`focus\\` 取一块邻域。\r\n **跨社区边不属于任何单一领域视图**——它是\"桥\",属于结构视图但不属于某个领域。\r\n\r\n## 用法纪律\r\n\r\n- **先 \\`graph_status\\` 再别的**:它告诉你这个仓库有没有图。没有就别继续,改用读文件。\r\n- \\`repoId\\` 通常是工作区路径。用 \\`graph_status\\` 的返回确认。\r\n- 大图不要一次要全部:\\`graph_data\\` 是合并图,可能很大;优先 \\`graph_query\\` 的\r\n 预算内子图,再按需 \\`graph_path_find\\` 取精确路径。\r\n- 路径读法(\\`graph_path_enumerate\\` / \\`graph_cycles\\`)返回的是**路径规则**的结果,\r\n \\`lMin\\`/\\`lMax\\` 是边数而非节点数,且受 \\`maxPaths\\` 截断。缺省是 **DFS 序**\r\n (确定、可复现);要按边权重保留最高分路径,显式传 \\`truncation: 'top-k-weight'\\`\r\n (候选超预算会抛错,先缩小 \\`lMax\\` 或边类型集合)。边权重来自上游索引的置信度\r\n (confidence ∈ [0,1]),没有置信度的边贡献 0——全部为 0 时 top-k 稳定退化为 DFS 序。\r\n\r\n## 不要做\r\n\r\n- 不要用 \\`graph_*\\` 替代读源码:图回答\"关系\",不回答\"实现细节\"。\r\n- 不要在没确认 \\`graph_status\\` 时说\"图里没有 X\" —— 没有图时所有查询都是空的,\r\n 那与\"X 不存在\"是两件事。\r\n- 不要把这里的节点 id 当成文件路径;用它去 \\`graph_symbol\\` 取 \\`filePath\\`。\r\n- 不要忽视 \\`graph_data\\` 返回值里的 \\`metadata.xuanji\\`:那是**增量引擎健康信号**——\r\n \\`applied: true\\` 且 \\`degradedRules\\` 非空表示部分路径规则因预算超限被降级重建,\r\n 此时路径查询的结果是**引擎降级下的近似**,重要的查询应重跑或在回答里披露;\r\n 没有该字段 = 本轮走的全量重建(不是异常)。\r\n`;\r\n\r\n/** 注册结果,供调用方与测试断言(不注册也要给出可诊断的原因)。 */\r\nexport interface SkillContribution {\r\n /** 是否真的调用到了 `ctx.skills.register`。 */\r\n readonly registered: boolean;\r\n /** 未注册时的原因(仅在 `registered === false` 时有值)。 */\r\n readonly reason?: string;\r\n}\r\n\r\n/**\r\n * 贡献运行时 Skill。\r\n *\r\n * 返回 `SkillContribution` 是给测试与调用方看的**判定证据**:拿不到注册就等于模型侧\r\n * 没有这条指令,而那种失败是完全静默的(`ctx.inject` 的回调永不执行)。\r\n */\r\nexport function contributeGraphSkill(ctx: Context): SkillContribution {\r\n let outcome: SkillContribution = { registered: false, reason: 'ctx.inject 尚未执行(依赖未到齐)' };\r\n\r\n try {\r\n ctx.inject(['skills'], (scope) => {\r\n const skills = (scope as unknown as {\r\n skills?: { register?: (skill: unknown) => unknown };\r\n }).skills;\r\n // 服务在但形状不对:这属于宿主换代导致的契约变化,必须响亮而不是静默跳过。\r\n if (!skills || typeof skills.register !== 'function') {\r\n outcome = { registered: false, reason: 'skills 服务存在但没有 register 方法' };\r\n log.warn('codegraph skill not contributed: ctx.skills.register is missing');\r\n return;\r\n }\r\n try {\r\n const disposer = skills.register({\r\n name: GRAPH_SKILL_NAME,\r\n description:\r\n 'Query the repository code graph (callers, callees, impact, cycles, dead code, keyword subgraph) instead of grepping. Use when asked about code relationships, dependency structure, or \"what breaks if I change X\".',\r\n content: SKILL_CONTENT,\r\n // 刻意不传 invocation:省略 = 模型与用户两侧都可见(kb skills.md:132)。\r\n // 只给模型侧会让人在 `/` 面板里找不到它,而这条指令对人同样有用。\r\n });\r\n // `register` 在「同层同名」时返回 **no-op disposer** 而不是抛错\r\n // (kb skills.md:299-303),所以拿到 disposer ≠ 一定是首次注册成功。\r\n // 我们无法从返回值区分这两者,如实记一条日志即可 —— 不要为此编造一个\r\n // 「已注册」的更强断言。\r\n if (typeof disposer === 'function') {\r\n scope.effect(() => disposer as () => void, 'codegraph: runtime skill');\r\n }\r\n outcome = { registered: true };\r\n log.info('codegraph runtime skill contributed', { name: GRAPH_SKILL_NAME });\r\n } catch (e) {\r\n outcome = { registered: false, reason: `skills.register 抛错: ${errorText(e)}` };\r\n log.warn('codegraph skill registration failed', { reason: outcome.reason });\r\n }\r\n });\r\n } catch (e) {\r\n outcome = { registered: false, reason: `ctx.inject 抛错: ${errorText(e)}` };\r\n log.warn('codegraph skill injection failed', { reason: outcome.reason });\r\n }\r\n\r\n return outcome;\r\n}\r\n\r\n/** 把未知异常压成一行文本(日志里不放 Error 对象,避免序列化噪音)。 */\r\nfunction errorText(e: unknown): string {\r\n if (e instanceof Error) return e.message;\r\n try {\r\n return String(e);\r\n } catch {\r\n return 'unknown error';\r\n }\r\n}\r\n","import type { Context } from '@deepseek-ai/cordis';\r\nimport { createGraphTools } from './tools.ts';\r\nimport { scoped } from './shared/Logger.ts';\r\nimport type { GraphData } from './types/index.ts';\r\nimport { PLUGIN_VERSION } from './generated/version.ts';\r\nimport { resolveConfig } from './host/config.ts';\r\nimport type { VisualizerConfig } from './host/config.ts';\r\nimport { createSecurityManager } from './host/security.ts';\r\nimport { extractWorkspacePaths, checkPrerequisites } from './host/prerequisites.ts';\r\nimport { createInvokeUpstream, registerRoutes, registerService } from './host/routes.ts';\r\nimport { registerWatcher, registerEventListeners } from './host/watcher.ts';\r\nimport { McpServer } from './host/McpServer.ts';\r\nimport { closeAllDbConnections } from './adapters/CodeGraphAdapter.ts';\r\nimport { detectHostVersion, createFeatureFlags, createApiCompatLayer, createHostCapabilityProbe } from './host/compat/index.ts';\r\nimport { GraphRepository } from './host/db/GraphRepository.ts';\r\nimport { contributeGraphSkill } from './host/skills.ts';\r\n\r\n\r\nexport { PLUGIN_VERSION };\r\nexport { resolveConfig, DEFAULT_CONFIG } from './host/config.ts';\r\nexport type { VisualizerConfig } from './host/config.ts';\r\nexport { Config } from './host/config.ts';\r\nexport { SecurityManager, createSecurityManager } from './host/security.ts';\r\n\r\nconst log = scoped('host');\r\n\r\nexport const name = 'dsh-codegraph-visualizer';\r\n// Required services only. `workspaceRegistry` is a gen2-only (DSH >=0.2) capability;\r\n// declaring it here would make the whole plugin INACTIVE on gen1/minimal hosts\r\n// (Cordis fiber inject must be fully satisfied). It is probed optionally at runtime\r\n// via ctx.get() in host/prerequisites.ts.\r\nexport const inject = ['tools', 'webServer', 'sessions'];\r\n\r\nexport async function apply(ctx: Context, userConfig?: Partial<VisualizerConfig>) {\r\n closeAllDbConnections();\r\n const config = resolveConfig(userConfig);\r\n const securityManager = createSecurityManager();\r\n securityManager.setAllowedWorkspaceRoots(extractWorkspacePaths(ctx));\r\n\r\n const hostVersionInfo = detectHostVersion(ctx);\r\n // The probe only ever switches capabilities ON (see resolveFeatureFlags), so a host whose\r\n // generation we cannot date still gets the capabilities it visibly advertises.\r\n const featureFlags = createFeatureFlags(hostVersionInfo, createHostCapabilityProbe(ctx));\r\n const apiCompat = createApiCompatLayer(hostVersionInfo);\r\n\r\n const workspacePaths = extractWorkspacePaths(ctx);\r\n const primaryWorkspace = workspacePaths[0] ?? process.cwd();\r\n const graphRepo = new GraphRepository(primaryWorkspace);\r\n const repoReady = graphRepo.init();\r\n if (!repoReady) { log.warn('graph repository init failed, persistence disabled'); }\r\n\r\n const { graphStatus, graphData, graphSymbol, graphImpact, graphPathFind, graphPathEnumerate, graphPathPattern, graphCycles, graphHealth, graphRulesCheck, graphFocus, graphQuery, graphIntegrity, graphSlice, graphViews, dispose: disposeGraphTools } = await createGraphTools(ctx, { requestTimeout: config.requestTimeout, apiCompat, nativeWorkspaceRoot: primaryWorkspace });\r\n\r\n ctx.effect(() => {\r\n const d1 = ctx.tools.register(graphStatus);\r\n const d2 = ctx.tools.register(graphData);\r\n const d3 = ctx.tools.register(graphSymbol);\r\n const d4 = ctx.tools.register(graphImpact);\r\n const d5 = ctx.tools.register(graphPathFind);\r\n const d6 = ctx.tools.register(graphHealth);\r\n const d7 = ctx.tools.register(graphRulesCheck);\r\n const d8 = ctx.tools.register(graphFocus);\r\n const d9 = ctx.tools.register(graphPathEnumerate);\r\n const d10 = ctx.tools.register(graphCycles);\r\n const d11 = ctx.tools.register(graphQuery);\r\n const d12 = ctx.tools.register(graphIntegrity);\r\n // cycle-134:路径模式 ρ 读法(序列约束的图谱查询)。\r\n const d13 = ctx.tools.register(graphPathPattern);\r\n // cycle-135:多维视图切片(时序/所有权/领域 + 结构),研究方案 §7.1 的统一接口。\r\n const d14 = ctx.tools.register(graphSlice);\r\n // cycle-138:Π 视图划分函数(研究方案 §4 的 Π 维)。\r\n const d15 = ctx.tools.register(graphViews);\r\n return () => { d1(); d2(); d3(); d4(); d5(); d6(); d7(); d8(); d9(); d10(); d11(); d12(); d13(); d14(); d15(); };\r\n }, 'codegraph: tool registrations');\r\n\r\n let lastGraphData: GraphData | null = null;\r\n let lastInitResult: { success: boolean; path: string; message: string; timestamp: number } | null = null;\r\n const scanInFlightMap = new Map<string, Promise<GraphData>>();\r\n const scanCache = new Map<string, { data: GraphData; timestamp: number }>();\r\n let destroyed = false;\r\n\r\n const invokeUpstream = createInvokeUpstream(ctx, config, apiCompat);\r\n\r\n const routeDeps = {\r\n ctx,\r\n config,\r\n securityManager,\r\n invokeUpstream,\r\n apiCompat,\r\n featureFlags,\r\n getLastGraphData: () => lastGraphData,\r\n setLastGraphData: (data: GraphData | null) => { lastGraphData = data; },\r\n getLastInitResult: () => lastInitResult,\r\n setLastInitResult: (data: { success: boolean; path: string; message: string; timestamp: number } | null) => { lastInitResult = data; },\r\n getScanCache: () => scanCache,\r\n getScanInFlight: (path: string) => scanInFlightMap.get(path) ?? null,\r\n setScanInFlight: (path: string, p: Promise<GraphData> | null) => {\r\n if (p === null) scanInFlightMap.delete(path);\r\n else scanInFlightMap.set(path, p);\r\n },\r\n graphRepo: repoReady ? graphRepo : undefined,\r\n isDestroyed: () => destroyed,\r\n };\r\n\r\n registerService(routeDeps);\r\n registerRoutes(routeDeps);\r\n\r\n // 把 14 个 graph_* 工具注册成一条运行时 Skill(`ctx.skills.register`)。\r\n //\r\n // 为什么放在这里而不是放进静态 `inject`:`skills` 是可选服务,写进静态 inject 会让\r\n // 「宿主没挂 skill 子系统」的 profile 把**整个插件**置为 INACTIVE —— 拿一条可选指令换\r\n // 插件不加载是不可接受的(与客户端 `betterSidebar`/`commandUi` 的处置同源)。\r\n // `contributeGraphSkill` 内部用响应式 `ctx.inject(['skills'], …)`,依赖不到齐只是\r\n // 静默不贡献,不影响工具与路由。\r\n //\r\n // 返回值刻意不丢:它是「模型侧到底有没有这条指令」的唯一证据,也是失败时唯一的原因来源。\r\n const skillContribution = contributeGraphSkill(ctx);\r\n ctx.effect(() => () => {\r\n // Skill 的 disposer 由 contributeGraphSkill 内部的 scope.effect 管理,\r\n // 这里只做一次状态收尾(没有要撤销的外部资源,保留钩子是为了将来加审计)。\r\n if (!skillContribution.registered) {\r\n log.info('codegraph skill not active on this host', { reason: skillContribution.reason });\r\n }\r\n }, 'codegraph: skill contribution summary');\r\n\r\n\r\n let lastStatus = '';\r\n /**\r\n * 变化检测键。包含**四个**位:`astSource` / `graphSource` 是独立能力,只看 codegraph/lens\r\n * 会让\"通道位翻转了但日志一声不响\",而那条 `prerequisite status` 日志是排查\r\n * `docs/需求分析.md` §61.10 那类假叙事时唯一的线索。\r\n */\r\n const prereqStatusKey = (s: ReturnType<typeof checkPrerequisites>): string =>\r\n `${s.codegraph}:${s.lens}:${s.astSource}:${s.graphSource}`;\r\n const emitPrereqStatus = () => {\r\n const status = checkPrerequisites(ctx, apiCompat);\r\n ctx.emit('codegraph-visualizer/prerequisite/status', {\r\n codegraph: status.codegraph,\r\n lens: status.lens,\r\n astSource: status.astSource,\r\n graphSource: status.graphSource,\r\n timestamp: Date.now(),\r\n });\r\n const statusKey = prereqStatusKey(status);\r\n if (statusKey !== lastStatus) {\r\n log.info('prerequisite status', status);\r\n lastStatus = statusKey;\r\n }\r\n };\r\n emitPrereqStatus();\r\n ctx.effect(() => {\r\n let pollCount = 0;\r\n const MAX_POLLS = 6;\r\n const prereqTimer = setInterval(() => {\r\n pollCount++;\r\n const status = checkPrerequisites(ctx, apiCompat);\r\n ctx.emit('codegraph-visualizer/prerequisite/status', {\r\n codegraph: status.codegraph,\r\n lens: status.lens,\r\n astSource: status.astSource,\r\n graphSource: status.graphSource,\r\n timestamp: Date.now(),\r\n });\r\n const statusKey = prereqStatusKey(status);\r\n if (statusKey !== lastStatus) {\r\n log.info('prerequisite status', status);\r\n lastStatus = statusKey;\r\n }\r\n if (status.codegraph || status.lens || pollCount >= MAX_POLLS) clearInterval(prereqTimer);\r\n }, config.prerequisiteRetryDelay);\r\n return () => clearInterval(prereqTimer);\r\n }, 'codegraph: prereq re-check timer');\r\n\r\n ctx.effect(() => ctx.on('codegraph-visualizer/prerequisite/request', () => {\r\n emitPrereqStatus();\r\n }), 'codegraph: prerequisite request listener');\r\n\r\n registerEventListeners(ctx, invokeUpstream, {\r\n getLastGraphData: () => lastGraphData,\r\n setLastGraphData: (data) => { lastGraphData = data; },\r\n });\r\n registerWatcher(ctx, config, invokeUpstream, {\r\n getLastGraphData: () => lastGraphData,\r\n setLastGraphData: (data) => { lastGraphData = data; },\r\n });\r\n\r\n const mcpServer = new McpServer();\r\n ctx.effect(() => {\r\n mcpServer.start(() => lastGraphData);\r\n return () => mcpServer.stop();\r\n }, 'codegraph: MCP server');\r\n\r\n ctx.effect(() => {\r\n return () => {\r\n destroyed = true;\r\n scanCache.clear();\r\n scanInFlightMap.clear();\r\n lastGraphData = null;\r\n lastInitResult = null;\r\n securityManager.reset();\r\n disposeGraphTools();\r\n closeAllDbConnections();\r\n // cycle-128:原生索引写连接池同步清理(与只读池同处置)。\r\n void import('./adapters/nativeIndex.ts').then(({ closeNativeIndexConnections }) => closeNativeIndexConnections());\r\n graphRepo.close();\r\n };\r\n }, 'codegraph: state cleanup');\r\n\r\n\r\n}\r\n"],"mappings":"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"}