| 1 |
- {"numTotalTestSuites":678,"numPassedTestSuites":661,"numFailedTestSuites":17,"numPendingTestSuites":0,"numTotalTests":1879,"numPassedTests":1870,"numFailedTests":9,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1781738696092,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["release notes for updater manifest"],"fullName":"release notes for updater manifest uses the full Chinese changelog for the current package version","status":"failed","title":"uses the full Chinese changelog for the current package version","duration":112.50020000000006,"failureMessages":["AssertionError: expected '1. LLM 模型增加多个自定义模型添加,修复模型等其他内容。\\n2. 增…' to contain 'Issue #10'\n at E:/QMAI/scripts/release-notes.spec.mjs:14:19\n at processTicksAndRejections (node:internal/process/task_queues:104:5)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]},{"ancestorTitles":["release notes for updater manifest"],"fullName":"release notes for updater manifest can write release notes directly to a UTF-8 file for CI scripts","status":"passed","title":"can write release notes directly to a UTF-8 file for CI scripts","duration":527.2845,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697132,"endTime":1781738697772.2844,"status":"failed","message":"","name":"E:/QMAI/scripts/release-notes.spec.mjs"},{"assertionResults":[{"ancestorTitles":["App search mode"],"fullName":"App search mode no longer wires standalone search-window bootstrap into the app entry","status":"passed","title":"no longer wires standalone search-window bootstrap into the app entry","duration":2.5885999999999854,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703948,"endTime":1781738703950.5886,"status":"passed","message":"","name":"E:/QMAI/src/App.search-mode.test.tsx"},{"assertionResults":[{"ancestorTitles":["Tauri 窗口标题权限"],"fullName":"Tauri 窗口标题权限 允许前端更新最顶层窗口标题","status":"passed","title":"允许前端更新最顶层窗口标题","duration":3.1824000000000012,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703960,"endTime":1781738703963.1824,"status":"passed","message":"","name":"E:/QMAI/src/tauri-window-title-permission.test.ts"},{"assertionResults":[{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) does not contain duplicate top-level JSON keys","status":"passed","title":"does not contain duplicate top-level JSON keys","duration":4.8508999999999105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) every referenced i18n key exists in en.json","status":"failed","title":"every referenced i18n key exists in en.json","duration":12.34680000000003,"failureMessages":["AssertionError: Referenced keys missing from en.json:\n chat.selectModel\n common.cancel\n common.save\n settings.sections.llm.savedModels.addModel\n settings.sections.llm.savedModels.apiKey\n settings.sections.llm.savedModels.apiKeyHint\n settings.sections.llm.savedModels.apiKeyPlaceholder\n settings.sections.llm.savedModels.confirmDelete\n settings.sections.llm.savedModels.customEndpoint\n settings.sections.llm.savedModels.customEndpointHint\n settings.sections.llm.savedModels.customEndpointPlaceholder\n settings.sections.llm.savedModels.delete\n settings.sections.llm.savedModels.description\n settings.sections.llm.savedModels.descriptionPlaceholder\n settings.sections.llm.savedModels.dialogDescription\n settings.sections.llm.savedModels.edit\n settings.sections.llm.savedModels.editModel\n settings.sections.llm.savedModels.empty\n settings.sections.llm.savedModels.modelId\n settings.sections.llm.savedModels.modelIdPlaceholder\n settings.sections.llm.savedModels.modelName\n settings.sections.llm.savedModels.modelNamePlaceholder\n settings.sections.llm.savedModels.title: expected [ 'chat.selectModel', …(22) ] to deeply equal []\n at E:/QMAI/src/i18n/i18n-parity.test.ts:163:7\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) every referenced i18n key exists in zh.json","status":"passed","title":"every referenced i18n key exists in zh.json","duration":1.0491000000000668,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) keeps B5 novel copy keys for context, review, and adjacent feedback UI","status":"passed","title":"keeps B5 novel copy keys for context, review, and adjacent feedback UI","duration":3.8467999999999165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) keeps the approved dynamic-key whitelist present in both bundles","status":"passed","title":"keeps the approved dynamic-key whitelist present in both bundles","duration":3.336500000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) has no scoped orphan keys in the confirmed cleanup set","status":"passed","title":"has no scoped orphan keys in the confirmed cleanup set","duration":0.8234999999999673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) keeps novel extract model copy explicit and distinct from embedding model","status":"passed","title":"keeps novel extract model copy explicit and distinct from embedding model","duration":0.6934999999999718,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) every leaf value is a non-empty string (no null / empty / placeholder slips)","status":"passed","title":"every leaf value is a non-empty string (no null / empty / placeholder slips)","duration":110.66989999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["i18n bundle parity (en.json ↔ zh.json)"],"fullName":"i18n bundle parity (en.json ↔ zh.json) pluralization keys come in pairs: every foo_plural has a matching foo","status":"passed","title":"pluralization keys come in pairs: every foo_plural has a matching foo","duration":2.79219999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738696915,"endTime":1781738697055.7922,"status":"failed","message":"","name":"E:/QMAI/src/i18n/i18n-parity.test.ts"},{"assertionResults":[{"ancestorTitles":["unwrapMermaidPre"],"fullName":"unwrapMermaidPre returns a MermaidDiagram element so markdown renderers can avoid nesting it in pre","status":"passed","title":"returns a MermaidDiagram element so markdown renderers can avoid nesting it in pre","duration":2.971300000000042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapMermaidPre"],"fullName":"unwrapMermaidPre leaves normal code children wrapped in pre","status":"passed","title":"leaves normal code children wrapped in pre","duration":0.4526000000000181,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapMermaidPre"],"fullName":"unwrapMermaidPre does not unwrap multiple children","status":"passed","title":"does not unwrap multiple children","duration":0.3077999999999861,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703786,"endTime":1781738703790.3079,"status":"passed","message":"","name":"E:/QMAI/src/components/mermaid-diagram.test.tsx"},{"assertionResults":[{"ancestorTitles":["book analysis store"],"fullName":"book analysis store updates book id and chapters without mutating a task object outside zustand","status":"passed","title":"updates book id and chapters without mutating a task object outside zustand","duration":4.038000000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode)"],"fullName":"book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode) setRecognitionStatus 改 status","status":"passed","title":"setRecognitionStatus 改 status","duration":0.5792000000000144,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode)"],"fullName":"book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode) setRecognizedCharacters 同时改 status 为 done + 写入列表","status":"passed","title":"setRecognizedCharacters 同时改 status 为 done + 写入列表","duration":0.9815999999999576,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode)"],"fullName":"book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode) setSelectedCharacterIds 改 ids","status":"passed","title":"setSelectedCharacterIds 改 ids","duration":0.5514999999999759,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode)"],"fullName":"book analysis store 角色识别 actions (feature/character-recognition-and-simple-mode) clearRecognition 重置所有识别状态","status":"passed","title":"clearRecognition 重置所有识别状态","duration":0.8430999999999926,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702328,"endTime":1781738702335.843,"status":"passed","message":"","name":"E:/QMAI/src/stores/book-analysis-store.spec.ts"},{"assertionResults":[{"ancestorTitles":["chat-store"],"fullName":"chat-store createConversation 会为新会话设置 deAiMode 默认值 false","status":"passed","title":"createConversation 会为新会话设置 deAiMode 默认值 false","duration":5.502400000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat-store"],"fullName":"chat-store setConversationDeAiMode 只更新目标会话的 deAiMode","status":"passed","title":"setConversationDeAiMode 只更新目标会话的 deAiMode","duration":2.3629999999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat-store"],"fullName":"chat-store 切换当前会话不会改动任一会话的 deAiMode","status":"passed","title":"切换当前会话不会改动任一会话的 deAiMode","duration":1.4522000000000048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat-store"],"fullName":"chat-store 重命名会话不会破坏 deAiMode","status":"passed","title":"重命名会话不会破坏 deAiMode","duration":0.8383000000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat-store"],"fullName":"chat-store 删除其他会话不会破坏剩余会话的 deAiMode","status":"passed","title":"删除其他会话不会破坏剩余会话的 deAiMode","duration":1.6383000000000152,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701071,"endTime":1781738701082.6382,"status":"passed","message":"","name":"E:/QMAI/src/stores/chat-store.test.ts"},{"assertionResults":[{"ancestorTitles":["import progress store"],"fullName":"import progress store keeps chapter memory extraction progress outside the sidebar component","status":"passed","title":"keeps chapter memory extraction progress outside the sidebar component","duration":3.242400000000032,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704509,"endTime":1781738704512.2424,"status":"passed","message":"","name":"E:/QMAI/src/stores/import-progress-store.spec.ts"},{"assertionResults":[{"ancestorTitles":["outline-generation-store"],"fullName":"outline-generation-store keeps tasks after the panel closes","status":"passed","title":"keeps tasks after the panel closes","duration":2.8605999999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-generation-store"],"fullName":"outline-generation-store updates generated tasks with the outline path","status":"passed","title":"updates generated tasks with the outline path","duration":0.5708000000000197,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-generation-store"],"fullName":"outline-generation-store removes completed tasks","status":"passed","title":"removes completed tasks","duration":0.41800000000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-generation-store"],"fullName":"outline-generation-store stores refinement metadata","status":"passed","title":"stores refinement metadata","duration":0.5217999999999847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-generation-store"],"fullName":"outline-generation-store stores ingest tasks with the outline path and initial status","status":"passed","title":"stores ingest tasks with the outline path and initial status","duration":0.45530000000002246,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703015,"endTime":1781738703020.4553,"status":"passed","message":"","name":"E:/QMAI/src/stores/outline-generation-store.test.ts"},{"assertionResults":[{"ancestorTitles":["review-store addItems — dedupe invariants"],"fullName":"review-store addItems — dedupe invariants after ANY sequence of addItems, pending items have unique (type, normalized title)","status":"passed","title":"after ANY sequence of addItems, pending items have unique (type, normalized title)","duration":48.341800000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems — dedupe invariants"],"fullName":"review-store addItems — dedupe invariants merge preserves the union of affectedPages across duplicates","status":"passed","title":"merge preserves the union of affectedPages across duplicates","duration":15.845899999999915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems — dedupe invariants"],"fullName":"review-store addItems — dedupe invariants resolveItem always makes resolved item eligible for a NEW pending item of same key","status":"passed","title":"resolveItem always makes resolved item eligible for a NEW pending item of same key","duration":21.757099999999923,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699101,"endTime":1781738699186.757,"status":"passed","message":"","name":"E:/QMAI/src/stores/review-store.property.test.ts"},{"assertionResults":[{"ancestorTitles":["review-store addItem"],"fullName":"review-store addItem adds a single item with generated id and resolved=false","status":"passed","title":"adds a single item with generated id and resolved=false","duration":3.836900000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItem"],"fullName":"review-store addItem does NOT dedupe in addItem (single-item path is append-only)","status":"passed","title":"does NOT dedupe in addItem (single-item path is append-only)","duration":0.45249999999998636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe merges two incoming items with the same type + normalized title","status":"passed","title":"merges two incoming items with the same type + normalized title","duration":1.4975000000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe merges against existing pending items","status":"passed","title":"merges against existing pending items","duration":0.5335999999999785,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe does NOT merge across different types","status":"passed","title":"does NOT merge across different types","duration":0.477800000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe does NOT merge into a resolved item (creates a new one)","status":"passed","title":"does NOT merge into a resolved item (creates a new one)","duration":0.925200000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe covers contradiction type (was previously skipped in dedupe)","status":"passed","title":"covers contradiction type (was previously skipped in dedupe)","duration":0.5167999999999893,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe covers confirm type","status":"passed","title":"covers confirm type","duration":0.32289999999994734,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe prefers the newer non-empty description on merge","status":"passed","title":"prefers the newer non-empty description on merge","duration":0.2689999999999486,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe keeps old description if incoming is empty","status":"passed","title":"keeps old description if incoming is empty","duration":0.32590000000004693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe deduplicates affectedPages within the merge","status":"passed","title":"deduplicates affectedPages within the merge","duration":0.3129000000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe merges searchQueries without duplicates","status":"passed","title":"merges searchQueries without duplicates","duration":0.28429999999997335,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe sets affectedPages to undefined when the merged result is empty","status":"passed","title":"sets affectedPages to undefined when the merged result is empty","duration":0.22610000000003083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe handles many incoming items at once, merging same-key pairs","status":"passed","title":"handles many incoming items at once, merging same-key pairs","duration":0.495900000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store addItems dedupe"],"fullName":"review-store addItems dedupe invariant: after addItems, no two pending items share (type, normalized title)","status":"passed","title":"invariant: after addItems, no two pending items share (type, normalized title)","duration":0.5617000000000303,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store resolveItem / dismissItem / clearResolved"],"fullName":"review-store resolveItem / dismissItem / clearResolved resolveItem flips the flag and stores action","status":"passed","title":"resolveItem flips the flag and stores action","duration":0.2574000000000183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store resolveItem / dismissItem / clearResolved"],"fullName":"review-store resolveItem / dismissItem / clearResolved resolveItem on missing id is a no-op (doesn't throw)","status":"passed","title":"resolveItem on missing id is a no-op (doesn't throw)","duration":0.917699999999968,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store resolveItem / dismissItem / clearResolved"],"fullName":"review-store resolveItem / dismissItem / clearResolved dismissItem removes the item entirely","status":"passed","title":"dismissItem removes the item entirely","duration":0.2676000000000158,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-store resolveItem / dismissItem / clearResolved"],"fullName":"review-store resolveItem / dismissItem / clearResolved clearResolved keeps only unresolved items","status":"passed","title":"clearResolved keeps only unresolved items","duration":0.3819000000000301,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700781,"endTime":1781738700794.3818,"status":"passed","message":"","name":"E:/QMAI/src/stores/review-store.test.ts"},{"assertionResults":[{"ancestorTitles":["wiki-store finalChapterSave"],"fullName":"wiki-store finalChapterSave 保存正式章节的状态在切换功能视图后仍然保留","status":"passed","title":"保存正式章节的状态在切换功能视图后仍然保留","duration":3.8173999999999637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store finalChapterSave"],"fullName":"wiki-store finalChapterSave 清除旧保存状态后,同一章节后续操作可以使用新的本地状态","status":"passed","title":"清除旧保存状态后,同一章节后续操作可以使用新的本地状态","duration":0.6078999999999724,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 开始检查后切换功能视图,检查状态仍然保留","status":"passed","title":"开始检查后切换功能视图,检查状态仍然保留","duration":0.5845999999999663,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 开始审稿后切换功能视图,审稿状态仍然保留","status":"passed","title":"开始审稿后切换功能视图,审稿状态仍然保留","duration":0.5083999999999946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 项目切换时清空保存、检查和审稿运行状态","status":"passed","title":"项目切换时清空保存、检查和审稿运行状态","duration":0.6916000000000508,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 旧检查任务不能覆盖新检查任务","status":"passed","title":"旧检查任务不能覆盖新检查任务","duration":0.5830000000000268,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 旧审稿任务不能覆盖新审稿任务","status":"passed","title":"旧审稿任务不能覆盖新审稿任务","duration":0.5504999999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store long running novel checks"],"fullName":"wiki-store long running novel checks 检查和审稿失败状态可以保留给界面显示","status":"passed","title":"检查和审稿失败状态可以保留给界面显示","duration":0.6962999999999511,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["wiki-store search panel"],"fullName":"wiki-store search panel keeps search panel state independent from activeView changes","status":"passed","title":"keeps search panel state independent from activeView changes","duration":0.49929999999994834,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701880,"endTime":1781738701889.4993,"status":"passed","message":"","name":"E:/QMAI/src/stores/wiki-store.test.ts"},{"assertionResults":[{"ancestorTitles":["应用标题"],"fullName":"应用标题 有项目名称时使用竖线追加项目名称","status":"passed","title":"有项目名称时使用竖线追加项目名称","duration":2.2930000000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["应用标题"],"fullName":"应用标题 没有项目名称时仅显示应用名称","status":"passed","title":"没有项目名称时仅显示应用名称","duration":0.3113999999999919,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704456,"endTime":1781738704458.3113,"status":"passed","message":"","name":"E:/QMAI/src/lib/app-title.test.ts"},{"assertionResults":[{"ancestorTitles":["runAppUpdateFlow"],"fullName":"runAppUpdateFlow 没有新版本时不弹窗也不安装","status":"passed","title":"没有新版本时不弹窗也不安装","duration":3.634299999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runAppUpdateFlow"],"fullName":"runAppUpdateFlow 用户确认后下载并安装更新","status":"passed","title":"用户确认后下载并安装更新","duration":0.6146999999999707,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703836,"endTime":1781738703840.6147,"status":"passed","message":"","name":"E:/QMAI/src/lib/app-updater.test.ts"},{"assertionResults":[{"ancestorTitles":["changelog"],"fullName":"changelog shows the 2.2.14 release before earlier visible releases","status":"failed","title":"shows the 2.2.14 release before earlier visible releases","duration":9.467600000000004,"failureMessages":["AssertionError: expected '2.2.16' to be '2.2.14' // Object.is equality\n at E:/QMAI/src/lib/changelog.spec.ts:9:25\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["changelog"],"fullName":"changelog returns the 2.2.0 changelog entry","status":"passed","title":"returns the 2.2.0 changelog entry","duration":1.1895000000000095,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["changelog"],"fullName":"changelog returns the 2.2.7 changelog entry for the hidden dismantling library and resume recovery","status":"passed","title":"returns the 2.2.7 changelog entry for the hidden dismantling library and resume recovery","duration":0.4060000000000059,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["changelog"],"fullName":"changelog returns the 2.2.8 changelog entry for review fixes and deep chapter length control","status":"passed","title":"returns the 2.2.8 changelog entry for review fixes and deep chapter length control","duration":0.3761000000000081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["changelog"],"fullName":"changelog returns the 2.2.9 changelog entry for the outline crash fix","status":"passed","title":"returns the 2.2.9 changelog entry for the outline crash fix","duration":0.46469999999999345,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["changelog"],"fullName":"changelog returns the 2.2.11 changelog entry for toolbar, de-ai, and local cli fixes","status":"passed","title":"returns the 2.2.11 changelog entry for toolbar, de-ai, and local cli fixes","duration":0.39179999999998927,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738696625,"endTime":1781738696638.3918,"status":"failed","message":"","name":"E:/QMAI/src/lib/changelog.spec.ts"},{"assertionResults":[{"ancestorTitles":["currentVersionChangelog"],"fullName":"currentVersionChangelog 只返回当前软件版本的更新日志","status":"passed","title":"只返回当前软件版本的更新日志","duration":4.452699999999993,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704226,"endTime":1781738704230.4526,"status":"passed","message":"","name":"E:/QMAI/src/lib/changelog.test.ts"},{"assertionResults":[{"ancestorTitles":["buildChapterTotalWordCountLabel"],"fullName":"buildChapterTotalWordCountLabel formats the total words label in Chinese","status":"passed","title":"formats the total words label in Chinese","duration":1.2571000000000083,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704812,"endTime":1781738704813.257,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-display.total.test.ts"},{"assertionResults":[{"ancestorTitles":["buildChapterEditorHeader status metadata"],"fullName":"buildChapterEditorHeader status metadata returns the final-chapter label next to word count metadata","status":"passed","title":"returns the final-chapter label next to word count metadata","duration":8.321700000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildChapterEditorHeader status metadata"],"fullName":"buildChapterEditorHeader status metadata returns the draft label for draft chapters","status":"passed","title":"returns the draft label for draft chapters","duration":1.0366999999999962,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701517,"endTime":1781738701527.0366,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-editor-header.status.test.ts"},{"assertionResults":[{"ancestorTitles":["buildChapterEditorHeader"],"fullName":"buildChapterEditorHeader returns heading text, status label, word count label, and title input width","status":"passed","title":"returns heading text, status label, word count label, and title input width","duration":9.252700000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildChapterEditorHeader"],"fullName":"buildChapterEditorHeader falls back to the frontmatter title when the chapter body has no heading yet","status":"passed","title":"falls back to the frontmatter title when the chapter body has no heading yet","duration":0.8923999999999523,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701650,"endTime":1781738701659.8923,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-editor-header.test.ts"},{"assertionResults":[{"ancestorTitles":["formatChapterWriting"],"fullName":"formatChapterWriting adds two-character indentation and removes blank lines between normal paragraphs","status":"passed","title":"adds two-character indentation and removes blank lines between normal paragraphs","duration":4.759700000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatChapterWriting"],"fullName":"formatChapterWriting preserves markdown structural blocks while formatting normal paragraphs","status":"passed","title":"preserves markdown structural blocks while formatting normal paragraphs","duration":0.46170000000000755,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702928,"endTime":1781738702933.4617,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-formatting.test.ts"},{"assertionResults":[{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection splits the chapter heading from the writing body","status":"passed","title":"splits the chapter heading from the writing body","duration":2.860500000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection rebuilds a chapter body with its heading","status":"passed","title":"rebuilds a chapter body with its heading","duration":0.6216000000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection replaces the selected body slice when the snapshot is still current","status":"passed","title":"replaces the selected body slice when the snapshot is still current","duration":0.7730999999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection refuses to replace when the body changed after the selection snapshot","status":"passed","title":"refuses to replace when the body changed after the selection snapshot","duration":0.2699000000000069,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection builds a polish prompt that returns only the polished segment","status":"passed","title":"builds a polish prompt that returns only the polished segment","duration":0.45539999999999736,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-selection"],"fullName":"chapter-selection preserves chapter frontmatter and heading when replacing the whole chapter body","status":"passed","title":"preserves chapter frontmatter and heading when replacing the whole chapter body","duration":4.106799999999964,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702022,"endTime":1781738702031.1067,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-selection.test.ts"},{"assertionResults":[{"ancestorTitles":["countChapterBodyWords"],"fullName":"countChapterBodyWords counts chapter body text without frontmatter or heading","status":"passed","title":"counts chapter body text without frontmatter or heading","duration":5.181100000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countChapterBodyWords"],"fullName":"countChapterBodyWords returns zero when a chapter only has a heading","status":"passed","title":"returns zero when a chapter only has a heading","duration":0.45799999999997,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702945,"endTime":1781738702950.458,"status":"passed","message":"","name":"E:/QMAI/src/lib/chapter-word-count.test.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"copies generated chapter edit content instead of surrounding context","status":"passed","title":"copies generated chapter edit content instead of surrounding context","duration":3.5652999999999793,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704050,"endTime":1781738704053.5652,"status":"passed","message":"","name":"E:/QMAI/src/lib/chat-copy-content.test.ts"},{"assertionResults":[{"ancestorTitles":["parseCodexCliLine"],"fullName":"parseCodexCliLine extracts completed agent messages from Codex JSONL","status":"passed","title":"extracts completed agent messages from Codex JSONL","duration":2.4032000000000266,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseCodexCliLine"],"fullName":"parseCodexCliLine ignores lifecycle events and malformed lines","status":"passed","title":"ignores lifecycle events and malformed lines","duration":0.41269999999997253,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPrompt"],"fullName":"buildPrompt escapes synthetic role tags in user-controlled content","status":"passed","title":"escapes synthetic role tags in user-controlled content","duration":0.8213000000000079,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildPrompt"],"fullName":"buildPrompt renders image blocks as inert placeholders","status":"passed","title":"renders image blocks as inert placeholders","duration":0.4644000000000119,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamCodexCli"],"fullName":"streamCodexCli waits for the done event before resolving so connection tests see emitted content","status":"passed","title":"waits for the done event before resolving so connection tests see emitted content","duration":13.437599999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamCodexCli"],"fullName":"streamCodexCli extracts the final Codex CLI JSONL failure message instead of dumping every event","status":"passed","title":"extracts the final Codex CLI JSONL failure message instead of dumping every event","duration":15.834199999999953,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699845,"endTime":1781738699878.8342,"status":"passed","message":"","name":"E:/QMAI/src/lib/codex-cli-transport.test.ts"},{"assertionResults":[{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget falls back to 204,800 chars when input is undefined / 0 / NaN","status":"passed","title":"falls back to 204,800 chars when input is undefined / 0 / NaN","duration":2.815300000000036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget 8K-class small model: per-page cap clamps to pageBudget so one page can fully use the budget","status":"passed","title":"8K-class small model: per-page cap clamps to pageBudget so one page can fully use the budget","duration":0.4024000000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget 32K mid-tier: floor of 5,000 chars/page is the binding cap, not the 30% rule","status":"passed","title":"32K mid-tier: floor of 5,000 chars/page is the binding cap, not the 30% rule","duration":0.2721000000000231,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget 128K config: 30% rule kicks in, per-page = 19,200","status":"passed","title":"128K config: 30% rule kicks in, per-page = 19,200","duration":0.2441000000000031,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget 200K config (legacy default): per-page is 30,720 — NOT the old 30,000 cap","status":"passed","title":"200K config (legacy default): per-page is 30,720 — NOT the old 30,000 cap","duration":0.28839999999996735,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget 1M config: per-page is 150,000 — used to be capped at 30K, wasting 5x the budget","status":"passed","title":"1M config: per-page is 150,000 — used to be capped at 30K, wasting 5x the budget","duration":0.2676999999999907,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget budgets always sum to ≤ maxCtx (never over-provision)","status":"passed","title":"budgets always sum to ≤ maxCtx (never over-provision)","duration":2.6854999999999905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget response reserve is exactly 15% (regression guard for the new behavior)","status":"passed","title":"response reserve is exactly 15% (regression guard for the new behavior)","duration":0.32389999999998054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["computeContextBudget"],"fullName":"computeContextBudget maxPageSize never exceeds pageBudget (a single page can't outgrow the entire page budget)","status":"passed","title":"maxPageSize never exceeds pageBudget (a single page can't outgrow the entire page budget)","duration":0.43739999999996826,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702821,"endTime":1781738702829.4375,"status":"passed","message":"","name":"E:/QMAI/src/lib/context-budget.test.ts"},{"assertionResults":[{"ancestorTitles":["dashboard issue actions"],"fullName":"dashboard issue actions parses fact check insert plan returned with prompt field names","status":"passed","title":"parses fact check insert plan returned with prompt field names","duration":2.8169000000000324,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704665,"endTime":1781738704667.817,"status":"passed","message":"","name":"E:/QMAI/src/lib/dashboard-issue-actions.spec.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"parses fact-check insert plan using snake_case keys","status":"passed","title":"parses fact-check insert plan using snake_case keys","duration":2.706200000000024,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704514,"endTime":1781738704516.7063,"status":"passed","message":"","name":"E:/QMAI/src/lib/dashboard-issue-actions.test.ts"},{"assertionResults":[{"ancestorTitles":["dedup-queue — basic enqueue + processing"],"fullName":"dedup-queue — basic enqueue + processing processes a queued merge and removes the task on success","status":"passed","title":"processes a queued merge and removes the task on success","duration":6.533800000000042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — basic enqueue + processing"],"fullName":"dedup-queue — basic enqueue + processing persists pending queue to disk","status":"passed","title":"persists pending queue to disk","duration":1.4902000000000157,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — basic enqueue + processing"],"fullName":"dedup-queue — basic enqueue + processing dedupes on slug-set: re-enqueueing the same group returns the same id","status":"passed","title":"dedupes on slug-set: re-enqueueing the same group returns the same id","duration":1.5054000000000087,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — basic enqueue + processing"],"fullName":"dedup-queue — basic enqueue + processing processes serially: second task only starts after first completes","status":"passed","title":"processes serially: second task only starts after first completes","duration":2.0258999999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — retries"],"fullName":"dedup-queue — retries retries on failure up to MAX_RETRIES (3) before marking failed","status":"passed","title":"retries on failure up to MAX_RETRIES (3) before marking failed","duration":2.1467999999999847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — retries"],"fullName":"dedup-queue — retries succeeds after a transient failure within retry budget","status":"passed","title":"succeeds after a transient failure within retry budget","duration":1.6751999999999612,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — retries"],"fullName":"dedup-queue — retries retryTask resets a failed task to pending and runs it again","status":"passed","title":"retryTask resets a failed task to pending and runs it again","duration":1.4794999999999732,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — cancel / delete"],"fullName":"dedup-queue — cancel / delete cancelTask removes a pending task before it runs","status":"passed","title":"cancelTask removes a pending task before it runs","duration":1.026399999999967,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — cancel / delete"],"fullName":"dedup-queue — cancel / delete cancelTask aborts an in-flight processing task","status":"passed","title":"cancelTask aborts an in-flight processing task","duration":1.1084000000000174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — pauseQueue / restoreQueue"],"fullName":"dedup-queue — pauseQueue / restoreQueue pauseQueue persists state, restoreQueue brings it back","status":"passed","title":"pauseQueue persists state, restoreQueue brings it back","duration":2.1685999999999694,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — pauseQueue / restoreQueue"],"fullName":"dedup-queue — pauseQueue / restoreQueue restoreQueue reverts processing tasks to pending so they re-run","status":"passed","title":"restoreQueue reverts processing tasks to pending so they re-run","duration":0.8762999999999579,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dedup-queue — pauseQueue / restoreQueue"],"fullName":"dedup-queue — pauseQueue / restoreQueue does not leak tasks across project switch","status":"passed","title":"does not leak tasks across project switch","duration":0.7049000000000092,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700152,"endTime":1781738700175.7048,"status":"passed","message":"","name":"E:/QMAI/src/lib/dedup-queue.test.ts"},{"assertionResults":[{"ancestorTitles":["extractEntitySummary"],"fullName":"extractEntitySummary returns null for content with no frontmatter","status":"passed","title":"returns null for content with no frontmatter","duration":2.3829000000000065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractEntitySummary"],"fullName":"extractEntitySummary derives slug from filename","status":"passed","title":"derives slug from filename","duration":3.3577000000000226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractEntitySummary"],"fullName":"extractEntitySummary uses frontmatter description when present","status":"passed","title":"uses frontmatter description when present","duration":0.9645000000000437,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractEntitySummary"],"fullName":"extractEntitySummary falls back to first non-heading body paragraph for description","status":"passed","title":"falls back to first non-heading body paragraph for description","duration":0.3673999999999751,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractEntitySummary"],"fullName":"extractEntitySummary truncates long descriptions to ~200 chars","status":"passed","title":"truncates long descriptions to ~200 chars","duration":0.5790000000000077,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse parses a clean JSON response","status":"passed","title":"parses a clean JSON response","duration":0.5101999999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse strips ```json code fences if the LLM wrapped its output","status":"passed","title":"strips ```json code fences if the LLM wrapped its output","duration":1.1813000000000216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse strips conversational preamble before the JSON","status":"passed","title":"strips conversational preamble before the JSON","duration":0.4048000000000229,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse rejects groups with fewer than 2 slugs","status":"passed","title":"rejects groups with fewer than 2 slugs","duration":0.31529999999997926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse defaults invalid confidence values to 'low'","status":"passed","title":"defaults invalid confidence values to 'low'","duration":0.335899999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse returns [] for malformed JSON","status":"passed","title":"returns [] for malformed JSON","duration":0.3240999999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse returns [] when the JSON object has no groups field","status":"passed","title":"returns [] when the JSON object has no groups field","duration":0.19669999999996435,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseDetectorResponse"],"fullName":"parseDetectorResponse survives quoted braces inside reason strings","status":"passed","title":"survives quoted braces inside reason strings","duration":0.18549999999999045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectDuplicateGroups"],"fullName":"detectDuplicateGroups returns [] when fewer than 2 summaries are passed","status":"passed","title":"returns [] when fewer than 2 summaries are passed","duration":1.7583000000000197,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectDuplicateGroups"],"fullName":"detectDuplicateGroups filters out groups whose slugs aren't in the input list","status":"passed","title":"filters out groups whose slugs aren't in the input list","duration":0.8464999999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectDuplicateGroups"],"fullName":"detectDuplicateGroups filters out groups already on the user's not-duplicates whitelist","status":"passed","title":"filters out groups already on the user's not-duplicates whitelist","duration":0.3643999999999892,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectDuplicateGroups"],"fullName":"detectDuplicateGroups treats whitelist entries case-insensitively and order-independently","status":"passed","title":"treats whitelist entries case-insensitively and order-independently","duration":0.2832000000000221,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectDuplicateGroups"],"fullName":"detectDuplicateGroups formats the user message with type / slug / title / tags / description","status":"passed","title":"formats the user message with type / slug / title / tags / description","duration":2.5552000000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences rewrites bare wikilinks","status":"passed","title":"rewrites bare wikilinks","duration":0.6985000000000241,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences rewrites wikilinks with aliases, preserving the alias","status":"passed","title":"rewrites wikilinks with aliases, preserving the alias","duration":0.178100000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences does not touch wikilinks pointing at unrelated slugs","status":"passed","title":"does not touch wikilinks pointing at unrelated slugs","duration":0.16840000000001965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences rewrites the related field (inline form)","status":"passed","title":"rewrites the related field (inline form)","duration":0.6249000000000251,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences rewrites the related field (block form)","status":"passed","title":"rewrites the related field (block form)","duration":0.3217999999999961,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences dedupes related when canonical was already in the list","status":"passed","title":"dedupes related when canonical was already in the list","duration":0.32099999999996953,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences returns content unchanged when no redirects apply","status":"passed","title":"returns content unchanged when no redirects apply","duration":0.210899999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteCrossReferences"],"fullName":"rewriteCrossReferences rewrites multiple slugs in one pass","status":"passed","title":"rewrites multiple slugs in one pass","duration":0.22050000000001546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteIndexMd"],"fullName":"rewriteIndexMd removes lines linking to merged-away slugs (markdown link form)","status":"passed","title":"removes lines linking to merged-away slugs (markdown link form)","duration":0.4601999999999862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteIndexMd"],"fullName":"rewriteIndexMd removes lines with wikilinks to merged-away slugs","status":"passed","title":"removes lines with wikilinks to merged-away slugs","duration":0.3994999999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteIndexMd"],"fullName":"rewriteIndexMd preserves headings and prose between lists","status":"passed","title":"preserves headings and prose between lists","duration":0.23540000000002692,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rewriteIndexMd"],"fullName":"rewriteIndexMd is a no-op when removedSlugs is empty","status":"passed","title":"is a no-op when removedSlugs is empty","duration":0.12969999999995707,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup throws when canonicalSlug isn't in the group","status":"passed","title":"throws when canonicalSlug isn't in the group","duration":2.7712000000000216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup throws when group has fewer than 2 pages","status":"passed","title":"throws when group has fewer than 2 pages","duration":0.5012000000000398,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup merges bodies via LLM, unions sources/tags/related, stamps updated","status":"passed","title":"merges bodies via LLM, unions sources/tags/related, stamps updated","duration":2.1709000000000174,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup rewrites cross-references in other wiki pages","status":"passed","title":"rewrites cross-references in other wiki pages","duration":1.3111999999999853,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup doesn't include unchanged pages in rewrites","status":"passed","title":"doesn't include unchanged pages in rewrites","duration":0.8072999999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeDuplicateGroup"],"fullName":"mergeDuplicateGroup backup snapshots the pre-merge state of every touched file","status":"passed","title":"backup snapshots the pre-merge state of every touched file","duration":0.49020000000001573,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700017,"endTime":1781738700047.4902,"status":"passed","message":"","name":"E:/QMAI/src/lib/dedup.test.ts"},{"assertionResults":[{"ancestorTitles":["deep research removal"],"fullName":"deep research removal removes UI and action entry points from layout, review, and graph","status":"passed","title":"removes UI and action entry points from layout, review, and graph","duration":3.928699999999992,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703537,"endTime":1781738703540.9287,"status":"passed","message":"","name":"E:/QMAI/src/lib/deep-research-removal.test.ts"},{"assertionResults":[{"ancestorTitles":["deep thinking stream renderer"],"fullName":"deep thinking stream renderer updates the same stage in place instead of appending duplicate thinking blocks","status":"passed","title":"updates the same stage in place instead of appending duplicate thinking blocks","duration":3.4132000000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deep thinking stream renderer"],"fullName":"deep thinking stream renderer keeps different stages ordered and appends final content after thinking","status":"passed","title":"keeps different stages ordered and appends final content after thinking","duration":0.5416000000000167,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703895,"endTime":1781738703898.5415,"status":"passed","message":"","name":"E:/QMAI/src/lib/deep-thinking-stream.spec.ts"},{"assertionResults":[{"ancestorTitles":["default paths"],"fullName":"default paths uses the install drive for the default novel directory","status":"passed","title":"uses the install drive for the default novel directory","duration":2.217199999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["default paths"],"fullName":"default paths falls back to D drive when a Windows drive cannot be inferred","status":"passed","title":"falls back to D drive when a Windows drive cannot be inferred","duration":0.2644999999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704404,"endTime":1781738704406.2644,"status":"passed","message":"","name":"E:/QMAI/src/lib/default-paths.test.ts"},{"assertionResults":[{"ancestorTitles":["detectLanguage — properties"],"fullName":"detectLanguage — properties pure ASCII random text always returns English","status":"passed","title":"pure ASCII random text always returns English","duration":15.992200000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage — properties"],"fullName":"detectLanguage — properties 2+ CJK characters yield Chinese","status":"passed","title":"2+ CJK characters yield Chinese","duration":5.3928000000000225,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage — properties"],"fullName":"detectLanguage — properties 2+ Hiragana characters yield Japanese","status":"passed","title":"2+ Hiragana characters yield Japanese","duration":5.199600000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage — properties"],"fullName":"detectLanguage — properties 2+ Hangul characters yield Korean","status":"passed","title":"2+ Hangul characters yield Korean","duration":18.694999999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage — properties"],"fullName":"detectLanguage — properties never throws on arbitrary strings","status":"passed","title":"never throws on arbitrary strings","duration":13.06329999999997,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699112,"endTime":1781738699171.0632,"status":"passed","message":"","name":"E:/QMAI/src/lib/detect-language.property.test.ts"},{"assertionResults":[{"ancestorTitles":["detectLanguage","defaults"],"fullName":"detectLanguage defaults returns English for empty string","status":"passed","title":"returns English for empty string","duration":3.100900000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","defaults"],"fullName":"detectLanguage defaults returns English for pure ASCII without language clues","status":"passed","title":"returns English for pure ASCII without language clues","duration":0.8789000000000442,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Chinese (CJK Unified Ideographs)","status":"passed","title":"detects Chinese (CJK Unified Ideographs)","duration":0.32220000000000937,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Japanese (Hiragana)","status":"passed","title":"detects Japanese (Hiragana)","duration":0.23279999999999745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Korean (Hangul)","status":"passed","title":"detects Korean (Hangul)","duration":0.242999999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Arabic","status":"passed","title":"detects Arabic","duration":1.199700000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Persian via Persian-specific letters and words","status":"passed","title":"detects Persian via Persian-specific letters and words","duration":0.27849999999995134,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts keeps Arabic distinct from Persian","status":"passed","title":"keeps Arabic distinct from Persian","duration":0.2411000000000172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts keeps ambiguous short Arabic-script snippets conservative","status":"passed","title":"keeps ambiguous short Arabic-script snippets conservative","duration":0.271000000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Thai","status":"passed","title":"detects Thai","duration":0.3061000000000149,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Hindi (Devanagari)","status":"passed","title":"detects Hindi (Devanagari)","duration":0.1815000000000282,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Russian (Cyrillic)","status":"passed","title":"detects Russian (Cyrillic)","duration":0.15960000000001173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts detects Greek","status":"passed","title":"detects Greek","duration":0.1634000000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts requires at least 2 non-Latin chars to commit","status":"passed","title":"requires at least 2 non-Latin chars to commit","duration":0.828200000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","non-Latin scripts"],"fullName":"detectLanguage non-Latin scripts picks the dominant script when mixed","status":"passed","title":"picks the dominant script when mixed","duration":0.15119999999996026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects Vietnamese via hook mark (ử, ả)","status":"passed","title":"detects Vietnamese via hook mark (ử, ả)","duration":0.13440000000002783,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects Vietnamese via tone-combination marks (ệ, ớ)","status":"passed","title":"detects Vietnamese via tone-combination marks (ệ, ớ)","duration":0.12590000000000146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects French via word patterns (no shared diacritics)","status":"passed","title":"detects French via word patterns (no shared diacritics)","duration":0.20120000000002847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects French via its own diacritics (é, è, ê, à)","status":"passed","title":"detects French via its own diacritics (é, è, ê, à)","duration":0.1705000000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects German via word patterns","status":"passed","title":"detects German via word patterns","duration":0.21340000000003556,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects Spanish via word patterns","status":"passed","title":"detects Spanish via word patterns","duration":0.19589999999999463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects Polish via diacritics","status":"passed","title":"detects Polish via diacritics","duration":0.15179999999998017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","Latin-script languages"],"fullName":"detectLanguage Latin-script languages detects Portuguese with ã / ç (regression: no longer misclassified as VN)","status":"passed","title":"detects Portuguese with ã / ç (regression: no longer misclassified as VN)","duration":0.2863999999999578,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","edge cases"],"fullName":"detectLanguage edge cases ignores plain ASCII digits and punctuation","status":"passed","title":"ignores plain ASCII digits and punctuation","duration":0.16610000000002856,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLanguage","edge cases"],"fullName":"detectLanguage edge cases handles very long pure-ASCII strings","status":"passed","title":"handles very long pure-ASCII strings","duration":1.17289999999997,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701266,"endTime":1781738701278.1729,"status":"passed","message":"","name":"E:/QMAI/src/lib/detect-language.test.ts"},{"assertionResults":[{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation returns [] when embedding call fails (null vector)","status":"passed","title":"returns [] when embedding call fails (null vector)","duration":37.06419999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation returns [] when no chunks come back from LanceDB","status":"passed","title":"returns [] when no chunks come back from LanceDB","duration":1.829999999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation groups chunks by page and ranks by max-pool score","status":"passed","title":"groups chunks by page and ranks by max-pool score","duration":1.616199999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation caps tail contribution so the blended score cannot exceed 1.0","status":"passed","title":"caps tail contribution so the blended score cannot exceed 1.0","duration":1.0405000000000086,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation applies the tail contribution below the cap when weighted tail < (1 - top)","status":"passed","title":"applies the tail contribution below the cap when weighted tail < (1 - top)","duration":0.8465999999999667,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation over-fetches topK × 3 chunks with a floor of 30 so the page-grouping has enough candidates","status":"passed","title":"over-fetches topK × 3 chunks with a floor of 30 so the page-grouping has enough candidates","duration":1.0953999999999837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation returns [] when the LanceDB search command throws (doesn't leak the error)","status":"passed","title":"returns [] when the LanceDB search command throws (doesn't leak the error)","duration":0.7479000000000156,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation sorts matchedChunks by score descending regardless of server return order","status":"passed","title":"sorts matchedChunks by score descending regardless of server return order","duration":0.6938000000000102,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation attaches up to 3 matched chunks with metadata","status":"passed","title":"attaches up to 3 matched chunks with metadata","duration":1.7004999999999768,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchByEmbedding — aggregation"],"fullName":"searchByEmbedding — aggregation respects the topK cutoff","status":"passed","title":"respects the topK cutoff","duration":1.1136999999999944,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats does not route OpenAI text-embedding models through Gemini","status":"passed","title":"does not route OpenAI text-embedding models through Gemini","duration":0.762800000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats does not route LM Studio text-embedding models through Gemini","status":"passed","title":"does not route LM Studio text-embedding models through Gemini","duration":0.536200000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats supports Gemini native embedContent endpoint and response shape","status":"passed","title":"supports Gemini native embedContent endpoint and response shape","duration":1.0728000000000293,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats accepts a full Gemini model embedContent endpoint","status":"passed","title":"accepts a full Gemini model embedContent endpoint","duration":0.4993999999999801,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats preserves non-key query parameters on pasted Gemini endpoints","status":"passed","title":"preserves non-key query parameters on pasted Gemini endpoints","duration":0.5020000000000095,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats routes custom embedContent proxy endpoints through Gemini format","status":"passed","title":"routes custom embedContent proxy endpoints through Gemini format","duration":0.5178999999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats trims trailing slashes before building Gemini embedContent endpoint","status":"passed","title":"trims trailing slashes before building Gemini embedContent endpoint","duration":0.4796000000000049,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats sends Gemini output_dimensionality when configured","status":"passed","title":"sends Gemini output_dimensionality when configured","duration":0.540300000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats omits invalid Gemini output_dimensionality values","status":"passed","title":"omits invalid Gemini output_dimensionality values","duration":0.8501000000000545,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats floors fractional Gemini output_dimensionality values","status":"passed","title":"floors fractional Gemini output_dimensionality values","duration":0.37659999999999627,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats normalizes a pasted Gemini batch endpoint to single embedContent","status":"passed","title":"normalizes a pasted Gemini batch endpoint to single embedContent","duration":0.48469999999997526,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats strips pasted Gemini key query parameters from the endpoint","status":"passed","title":"strips pasted Gemini key query parameters from the endpoint","duration":0.5387000000000057,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats rejects empty Gemini embedding vectors","status":"passed","title":"rejects empty Gemini embedding vectors","duration":3.7689000000000306,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats rejects Gemini embedding vectors with NaN or Infinity values","status":"passed","title":"rejects Gemini embedding vectors with NaN or Infinity values","duration":1.785000000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats rejects Gemini embedding vectors with mixed value types","status":"passed","title":"rejects Gemini embedding vectors with mixed value types","duration":0.6177000000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats surfaces Gemini auth errors with HTTP status","status":"passed","title":"surfaces Gemini auth errors with HTTP status","duration":0.8768000000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding — provider wire formats"],"fullName":"fetchEmbedding — provider wire formats auto-halves Gemini requests after an oversize error","status":"passed","title":"auto-halves Gemini requests after an oversize error","duration":1.117399999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve retries after an oversize 400 with halved text and succeeds","status":"passed","title":"retries after an oversize 400 with halved text and succeeds","duration":4.052999999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve recognises HTTP 413 as oversize and halves","status":"passed","title":"recognises HTTP 413 as oversize and halves","duration":0.8521000000000072,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve does NOT halve on auth errors (401) — retries there would be pointless","status":"passed","title":"does NOT halve on auth errors (401) — retries there would be pointless","duration":0.7872000000000412,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve gives up after 3 halvings and surfaces the 'rejected at N chars' message","status":"passed","title":"gives up after 3 halvings and surfaces the 'rejected at N chars' message","duration":1.302599999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve halving floor: 128-char input stops after 2 attempts (128 → 64; 64 is not > 64 so no further halving)","status":"passed","title":"halving floor: 128-char input stops after 2 attempts (128 → 64; 64 is not > 64 so no further halving)","duration":0.747099999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve halving floor: 130-char input produces 3 attempts (130 → 65 is > 64 so halves once more to 32)","status":"passed","title":"halving floor: 130-char input produces 3 attempts (130 → 65 is > 64 so halves once more to 32)","duration":1.1877000000000066,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve recognizes a variety of oversize error phrases (not just 'exceeds maximum context')","status":"passed","title":"recognizes a variety of oversize error phrases (not just 'exceeds maximum context')","duration":3.161100000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve surfaces a 'Network error' message when the fetch itself throws a TypeError","status":"passed","title":"surfaces a 'Network error' message when the fetch itself throws a TypeError","duration":0.49099999999998545,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve returns null + surfaces a descriptive error when 200 response is missing data[0].embedding","status":"passed","title":"returns null + surfaces a descriptive error when 200 response is missing data[0].embedding","duration":0.4423999999999637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fetchEmbedding (via searchByEmbedding) — auto-halve"],"fullName":"fetchEmbedding (via searchByEmbedding) — auto-halve clears lastEmbeddingError after a successful fetch following a failure","status":"passed","title":"clears lastEmbeddingError after a successful fetch following a failure","duration":0.6422999999999774,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage chunks the page, embeds each, and upserts","status":"passed","title":"chunks the page, embeds each, and upserts","duration":1.9288999999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage keeps successful chunks even when some fail, preserving original chunk_index gaps","status":"passed","title":"keeps successful chunks even when some fail, preserving original chunk_index gaps","duration":2.7153999999999883,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage skips LanceDB call when all chunks fail to embed","status":"passed","title":"skips LanceDB call when all chunks fail to embed","duration":1.3191999999999666,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage no-ops when embedding is disabled in config","status":"passed","title":"no-ops when embedding is disabled in config","duration":0.24869999999998527,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage no-ops when the content produces zero chunks (empty page)","status":"passed","title":"no-ops when the content produces zero chunks (empty page)","duration":0.362700000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage passes page title + heading path + chunk text joined by blank lines to the embed request","status":"passed","title":"passes page title + heading path + chunk text joined by blank lines to the embed request","duration":0.6023999999999887,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage omits the heading-path prefix when the chunk is preamble (empty headingPath)","status":"passed","title":"omits the heading-path prefix when the chunk is preamble (empty headingPath)","duration":0.7198999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage omits the title prefix when pageTitle is empty/whitespace","status":"passed","title":"omits the title prefix when pageTitle is empty/whitespace","duration":0.5008000000000266,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage no-ops when cfg.model is empty (uninitialized embedding config)","status":"passed","title":"no-ops when cfg.model is empty (uninitialized embedding config)","duration":0.26720000000000255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage honors cfg.maxChunkChars: smaller value produces more chunks","status":"passed","title":"honors cfg.maxChunkChars: smaller value produces more chunks","duration":1.8326000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedPage"],"fullName":"embedPage honors cfg.overlapChunkChars: larger overlap produces longer subsequent chunks","status":"passed","title":"honors cfg.overlapChunkChars: larger overlap produces longer subsequent chunks","duration":4.571899999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages indexes every non-structural .md file, recursing into subdirs","status":"passed","title":"indexes every non-structural .md file, recursing into subdirs","duration":4.23539999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages extracts the title from YAML frontmatter when present","status":"passed","title":"extracts the title from YAML frontmatter when present","duration":1.3133000000000266,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages falls back to the file id (without .md) when frontmatter has no title","status":"passed","title":"falls back to the file id (without .md) when frontmatter has no title","duration":0.6812000000000467,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages invokes onProgress exactly once per indexed file with increasing done & constant total","status":"passed","title":"invokes onProgress exactly once per indexed file with increasing done & constant total","duration":1.0382999999999925,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages returns 0 and makes no network calls when embedding is disabled","status":"passed","title":"returns 0 and makes no network calls when embedding is disabled","duration":0.4311000000000149,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages returns 0 when listDirectory throws (project has no wiki dir yet)","status":"passed","title":"returns 0 when listDirectory throws (project has no wiki dir yet)","duration":0.34660000000002356,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["embedAllPages"],"fullName":"embedAllPages continues with remaining files when one file's readFile throws","status":"passed","title":"continues with remaining files when one file's readFile throws","duration":0.7345000000000255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding"],"fullName":"legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding legacyVectorRowCount: returns the Rust row count on success, 0 on error","status":"passed","title":"legacyVectorRowCount: returns the Rust row count on success, 0 on error","duration":1.1336000000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding"],"fullName":"legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding dropLegacyVectorTable: invokes the Rust command with normalized path, propagates throws","status":"passed","title":"dropLegacyVectorTable: invokes the Rust command with normalized path, propagates throws","duration":1.7905999999999835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding"],"fullName":"legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding getEmbeddingCount: returns chunk count, swallows errors to 0","status":"passed","title":"getEmbeddingCount: returns chunk count, swallows errors to 0","duration":0.48980000000000246,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding"],"fullName":"legacyVectorRowCount / dropLegacyVectorTable / getEmbeddingCount / removePageEmbedding removePageEmbedding: invokes vector_delete_page, swallows errors silently","status":"passed","title":"removePageEmbedding: invokes vector_delete_page, swallows errors silently","duration":0.7198000000000206,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698505,"endTime":1781738698611.7197,"status":"passed","message":"","name":"E:/QMAI/src/lib/embedding.test.ts"},{"assertionResults":[{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode leaves a well-formed URL untouched","status":"passed","title":"leaves a well-formed URL untouched","duration":2.8059000000000083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips trailing slash","status":"passed","title":"strips trailing slash","duration":0.47230000000001837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips pasted /chat/completions","status":"passed","title":"strips pasted /chat/completions","duration":0.3702999999999861,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips pasted /chat/completions with trailing slash","status":"passed","title":"strips pasted /chat/completions with trailing slash","duration":0.22320000000001983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips pasted /embeddings","status":"passed","title":"strips pasted /embeddings","duration":0.24940000000003693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode preserves non-/v1 version segments (Zhipu, Arcee, etc.)","status":"passed","title":"preserves non-/v1 version segments (Zhipu, Arcee, etc.)","duration":0.29570000000001073,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips /chat/completions while keeping a non-/v1 version segment","status":"passed","title":"strips /chat/completions while keeping a non-/v1 version segment","duration":0.2477000000000089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode warns when the URL is a bare host with no version path","status":"passed","title":"warns when the URL is a bare host with no version path","duration":0.29750000000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode warns when protocol is missing","status":"passed","title":"warns when protocol is missing","duration":0.2796999999999912,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode handles empty / whitespace input","status":"passed","title":"handles empty / whitespace input","duration":0.3073999999999728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips enclosing whitespace","status":"passed","title":"strips enclosing whitespace","duration":0.21510000000000673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode handles localhost with a port and version segment","status":"passed","title":"handles localhost with a port and version segment","duration":0.20629999999999882,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — chat_completions mode"],"fullName":"normalizeEndpoint — chat_completions mode strips /chat/completions on a localhost llama.cpp URL","status":"passed","title":"strips /chat/completions on a localhost llama.cpp URL","duration":0.25420000000002574,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — anthropic_messages mode"],"fullName":"normalizeEndpoint — anthropic_messages mode keeps a bare /anthropic base as-is (dispatch appends /v1/messages)","status":"passed","title":"keeps a bare /anthropic base as-is (dispatch appends /v1/messages)","duration":0.209699999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — anthropic_messages mode"],"fullName":"normalizeEndpoint — anthropic_messages mode keeps a full /v1/messages URL as-is (dispatch uses it verbatim)","status":"passed","title":"keeps a full /v1/messages URL as-is (dispatch uses it verbatim)","duration":0.16660000000001673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — anthropic_messages mode"],"fullName":"normalizeEndpoint — anthropic_messages mode strips trailing slash on an anthropic base","status":"passed","title":"strips trailing slash on an anthropic base","duration":0.16440000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — anthropic_messages mode"],"fullName":"normalizeEndpoint — anthropic_messages mode strips stray /chat/completions (user pasted the wrong shape)","status":"passed","title":"strips stray /chat/completions (user pasted the wrong shape)","duration":0.17169999999998709,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches flags a 5-octet IP-shaped host as malformed (real user paste)","status":"passed","title":"flags a 5-octet IP-shaped host as malformed (real user paste)","duration":0.36820000000000164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches rejects an octet > 255 at the URL-parsing layer","status":"passed","title":"rejects an octet > 255 at the URL-parsing layer","duration":0.21819999999996753,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches accepts a well-formed IPv4 host with port and path","status":"passed","title":"accepts a well-formed IPv4 host with port and path","duration":0.1803000000000452,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches does NOT flag DNS-name-shaped hosts that contain dots","status":"passed","title":"does NOT flag DNS-name-shaped hosts that contain dots","duration":0.14400000000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches rejects an unparseable URL (triple-t protocol typo)","status":"passed","title":"rejects an unparseable URL (triple-t protocol typo)","duration":0.15859999999997854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches rejects URL with illegal characters","status":"passed","title":"rejects URL with illegal characters","duration":0.17149999999998045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeEndpoint — URL well-formedness catches"],"fullName":"normalizeEndpoint — URL well-formedness catches accepts localhost host (non-IP) without flagging","status":"passed","title":"accepts localhost host (non-IP) without flagging","duration":0.12669999999997117,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701661,"endTime":1781738701670.1714,"status":"passed","message":"","name":"E:/QMAI/src/lib/endpoint-normalizer.test.ts"},{"assertionResults":[{"ancestorTitles":["enrich-wikilinks scenarios (fixture-driven)"],"fullName":"enrich-wikilinks scenarios (fixture-driven) adds-wikilinks","status":"passed","title":"adds-wikilinks","duration":22.966499999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrich-wikilinks scenarios (fixture-driven)"],"fullName":"enrich-wikilinks scenarios (fixture-driven) preserves-frontmatter","status":"passed","title":"preserves-frontmatter","duration":7.738200000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrich-wikilinks scenarios (fixture-driven)"],"fullName":"enrich-wikilinks scenarios (fixture-driven) no-matches-no-write","status":"passed","title":"no-matches-no-write","duration":6.835999999999899,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrich-wikilinks scenarios (fixture-driven)"],"fullName":"enrich-wikilinks scenarios (fixture-driven) cjk-terms","status":"passed","title":"cjk-terms","duration":10.823600000000056,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698000,"endTime":1781738698048.8235,"status":"passed","message":"","name":"E:/QMAI/src/lib/enrich-wikilinks.scenarios.test.ts"},{"assertionResults":[{"ancestorTitles":["enrichWithWikilinks — language directive is built at call time"],"fullName":"enrichWithWikilinks — language directive is built at call time uses the language configured at call time, not at module load","status":"passed","title":"uses the language configured at call time, not at module load","duration":3.747099999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — language directive is built at call time"],"fullName":"enrichWithWikilinks — language directive is built at call time picks up a language change between two successive calls","status":"passed","title":"picks up a language change between two successive calls","duration":0.8677000000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — language directive is built at call time"],"fullName":"enrichWithWikilinks — language directive is built at call time auto mode falls back to detecting from the page content","status":"passed","title":"auto mode falls back to detecting from the page content","duration":0.8716000000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — language directive is built at call time"],"fullName":"enrichWithWikilinks — language directive is built at call time explicit setting beats source content detection","status":"passed","title":"explicit setting beats source content detection","duration":0.6007000000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — JSON-based substitution"],"fullName":"enrichWithWikilinks — JSON-based substitution does NOT overwrite when LLM response is not parseable JSON","status":"passed","title":"does NOT overwrite when LLM response is not parseable JSON","duration":1.7092999999999847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — JSON-based substitution"],"fullName":"enrichWithWikilinks — JSON-based substitution writes back when LLM returns a valid JSON substitution list","status":"passed","title":"writes back when LLM returns a valid JSON substitution list","duration":1.6037999999999784,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — JSON-based substitution"],"fullName":"enrichWithWikilinks — JSON-based substitution does NOT overwrite when LLM returns an empty links list","status":"passed","title":"does NOT overwrite when LLM returns an empty links list","duration":0.4317999999999529,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["enrichWithWikilinks — JSON-based substitution"],"fullName":"enrichWithWikilinks — JSON-based substitution returns early when content or index is missing","status":"passed","title":"returns early when content or index is missing","duration":0.40159999999997353,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701377,"endTime":1781738701387.432,"status":"passed","message":"","name":"E:/QMAI/src/lib/enrich-wikilinks.test.ts"},{"assertionResults":[{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter returns null + full body when content has no frontmatter","status":"passed","title":"returns null + full body when content has no frontmatter","duration":2.428699999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter strips a basic frontmatter block and returns the body","status":"passed","title":"strips a basic frontmatter block and returns the body","duration":3.6725000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter rawBlock + body equals the original content for well-formed input","status":"passed","title":"rawBlock + body equals the original content for well-formed input","duration":0.8919999999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter parses inline arrays","status":"passed","title":"parses inline arrays","duration":0.4866000000000099,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter parses block-style arrays (the YAML `- item` syntax)","status":"passed","title":"parses block-style arrays (the YAML `- item` syntax)","duration":0.5577999999999861,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter doesn't interpret YYYY-MM-DD scalars as Date objects (JSON_SCHEMA)","status":"passed","title":"doesn't interpret YYYY-MM-DD scalars as Date objects (JSON_SCHEMA)","duration":0.28820000000001755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter handles CJK/Unicode tags without quoting","status":"passed","title":"handles CJK/Unicode tags without quoting","duration":0.5906999999999698,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter strips quotes from string scalars","status":"passed","title":"strips quotes from string scalars","duration":0.34780000000000655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter handles a frontmatter-only file with no body","status":"passed","title":"handles a frontmatter-only file with no body","duration":0.36599999999998545,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter handles CRLF line endings","status":"passed","title":"handles CRLF line endings","duration":0.3406999999999698,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter returns null frontmatter when YAML is malformed but still strips the fence block","status":"passed","title":"returns null frontmatter when YAML is malformed but still strips the fence block","duration":1.0104000000000042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter matches the real Accumulibacter frontmatter (block array of paths)","status":"passed","title":"matches the real Accumulibacter frontmatter (block array of paths)","duration":0.5706000000000131,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter matches the real Nitrospira frontmatter (all inline arrays, CJK tags)","status":"passed","title":"matches the real Nitrospira frontmatter (all inline arrays, CJK tags)","duration":0.47919999999999163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter locates a frontmatter block wrapped in ```yaml … ``` and strips the closing fence","status":"passed","title":"locates a frontmatter block wrapped in ```yaml … ``` and strips the closing fence","duration":0.5951999999999771,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter strips the closing fence even when the closing ``` is on the line directly after ---","status":"passed","title":"strips the closing fence even when the closing ``` is on the line directly after ---","duration":0.4977000000000089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter locates a frontmatter block prefixed by a `frontmatter:` key (LLM corruption)","status":"passed","title":"locates a frontmatter block prefixed by a `frontmatter:` key (LLM corruption)","duration":0.2504999999999882,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter does not pick up a `---` horizontal rule deep in the body as frontmatter","status":"passed","title":"does not pick up a `---` horizontal rule deep in the body as frontmatter","duration":0.1523000000000252,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter repairs `key: [[a]], [[b]]` (LLM-emitted invalid wikilink list) via retry","status":"passed","title":"repairs `key: [[a]], [[b]]` (LLM-emitted invalid wikilink list) via retry","duration":0.6646999999999821,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter doesn't mangle a legitimate nested-array YAML value during repair","status":"passed","title":"doesn't mangle a legitimate nested-array YAML value during repair","duration":0.41109999999997626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatter"],"fullName":"parseFrontmatter matches the real BOD entity frontmatter (quoted wikilink items in block array)","status":"passed","title":"matches the real BOD entity frontmatter (quoted wikilink items in block array)","duration":0.439899999999966,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700684,"endTime":1781738700699.44,"status":"passed","message":"","name":"E:/QMAI/src/lib/frontmatter.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱区域中文界面"],"fullName":"图谱区域中文界面 详情面板与预览兜底文案使用中文","status":"passed","title":"详情面板与预览兜底文案使用中文","duration":3.464400000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱区域中文界面"],"fullName":"图谱区域中文界面 图谱文档编辑入口位于节点文档内部而不是顶部集中按钮区","status":"passed","title":"图谱文档编辑入口位于节点文档内部而不是顶部集中按钮区","duration":1.250099999999975,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703304,"endTime":1781738703308.25,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-chinese-ui.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱视图模式标签"],"fullName":"图谱视图模式标签 使用 i18n 键而非硬编码中文标签","status":"passed","title":"使用 i18n 键而非硬编码中文标签","duration":2.677099999999996,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704582,"endTime":1781738704584.677,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-display-mode-labels.test.ts"},{"assertionResults":[{"ancestorTitles":["graph filters"],"fullName":"graph filters detects structural graph nodes by id, type, and path","status":"passed","title":"detects structural graph nodes by id, type, and path","duration":2.468399999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters hides structural nodes and their connected edges by default","status":"passed","title":"hides structural nodes and their connected edges by default","duration":2.234800000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters hides selected node types","status":"passed","title":"hides selected node types","duration":0.4876000000000431,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters hides manually selected nodes","status":"passed","title":"hides manually selected nodes","duration":0.4703999999999837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters hides hub nodes above the max link threshold","status":"passed","title":"hides hub nodes above the max link threshold","duration":0.5505999999999744,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters hides isolated nodes when requested","status":"passed","title":"hides isolated nodes when requested","duration":0.34749999999996817,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters keeps isolated nodes visible when hideIsolated would blank the whole graph","status":"passed","title":"keeps isolated nodes visible when hideIsolated would blank the whole graph","duration":0.3254999999999768,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters keeps allowed isolated novel nodes visible when only disallowed types would remain hidden","status":"passed","title":"keeps allowed isolated novel nodes visible when only disallowed types would remain hidden","duration":0.46589999999997644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters reports whether filters are active","status":"passed","title":"reports whether filters are active","duration":0.43100000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph filters"],"fullName":"graph filters minimumEdgeWeight 过滤弱关系边,且 allowedNodeTypes 过滤非白名单节点","status":"passed","title":"minimumEdgeWeight 过滤弱关系边,且 allowedNodeTypes 过滤非白名单节点","duration":0.5253999999999905,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701950,"endTime":1781738701958.5254,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-filters.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱洞察中文文案"],"fullName":"图谱洞察中文文案 意外连接原因使用中文","status":"passed","title":"意外连接原因使用中文","duration":2.7194000000000074,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱洞察中文文案"],"fullName":"图谱洞察中文文案 知识缺口标题、描述和建议使用中文","status":"passed","title":"知识缺口标题、描述和建议使用中文","duration":0.9928999999999917,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704082,"endTime":1781738704085.993,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-insights.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱布局参数"],"fullName":"图谱布局参数 使用更分散的布局与更克制的视觉参数","status":"passed","title":"使用更分散的布局与更克制的视觉参数","duration":1.4935000000000116,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704774,"endTime":1781738704775.4934,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-layout.test.ts"},{"assertionResults":[{"ancestorTitles":["小说图谱模式配置"],"fullName":"小说图谱模式配置 为首批专题模式提供中文标签","status":"passed","title":"为首批专题模式提供中文标签","duration":2.5629000000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说图谱模式配置"],"fullName":"小说图谱模式配置 总览模式默认启用强力减法","status":"passed","title":"总览模式默认启用强力减法","duration":0.4885999999999626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说图谱模式配置"],"fullName":"小说图谱模式配置 专题模式为不同任务提供不同节点白名单","status":"passed","title":"专题模式为不同任务提供不同节点白名单","duration":1.2882000000000176,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703854,"endTime":1781738703858.288,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-mode.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱节点档案页"],"fullName":"图谱节点档案页 为没有真实路径的节点生成实体页路径和默认内容","status":"passed","title":"为没有真实路径的节点生成实体页路径和默认内容","duration":3.9780000000000086,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱节点档案页"],"fullName":"图谱节点档案页 优先使用节点已有真实路径","status":"passed","title":"优先使用节点已有真实路径","duration":0.39280000000002246,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701713,"endTime":1781738701717.3928,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-node-page.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 将图谱转换为分层清晰的 Markdown 文档","status":"passed","title":"将图谱转换为分层清晰的 Markdown 文档","duration":3.3251000000000204,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 将图谱转换为脑图层级","status":"passed","title":"将图谱转换为脑图层级","duration":0.7062000000000239,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 提供文档内编辑所需的结构化分组与关系辅助函数","status":"passed","title":"提供文档内编辑所需的结构化分组与关系辅助函数","duration":1.6444999999999936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持按文档分组和节点类型筛选节点","status":"passed","title":"支持按文档分组和节点类型筛选节点","duration":0.48900000000003274,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持按关键词搜索节点标题和来源路径","status":"passed","title":"支持按关键词搜索节点标题和来源路径","duration":0.46980000000002065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持隐藏没有关系的节点","status":"passed","title":"支持隐藏没有关系的节点","duration":0.34319999999996753,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持统计和筛选孤立节点","status":"passed","title":"支持统计和筛选孤立节点","duration":0.4013999999999669,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 为节点生成小说化关系摘要","status":"passed","title":"为节点生成小说化关系摘要","duration":0.44010000000002947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持图谱文档节点排序","status":"passed","title":"支持图谱文档节点排序","duration":13.341800000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 为特定节点类型提供风险标签","status":"passed","title":"为特定节点类型提供风险标签","duration":0.5573999999999728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 为特定节点类型提供状态化风险标签","status":"passed","title":"为特定节点类型提供状态化风险标签","duration":0.3824999999999932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持状态标签选项与循环切换","status":"passed","title":"支持状态标签选项与循环切换","duration":0.5760000000000218,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持将状态字段写回节点档案内容","status":"passed","title":"支持将状态字段写回节点档案内容","duration":0.40610000000003765,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持风险状态筛选选项与节点过滤","status":"passed","title":"支持风险状态筛选选项与节点过滤","duration":0.5382000000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 提供风险快捷筛选配置","status":"passed","title":"提供风险快捷筛选配置","duration":0.290300000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 构建风险统计面板数据","status":"passed","title":"构建风险统计面板数据","duration":0.38850000000002183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持按当前分组统计风险面板数据","status":"passed","title":"支持按当前分组统计风险面板数据","duration":0.41360000000003083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 支持过滤零计数风险统计项","status":"passed","title":"支持过滤零计数风险统计项","duration":0.240099999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 为风险统计项提供颜色分级映射","status":"passed","title":"为风险统计项提供颜色分级映射","duration":0.38179999999999836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 为状态标签提供颜色分级映射","status":"passed","title":"为状态标签提供颜色分级映射","duration":0.39339999999998554,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 计算风险统计面板汇总计数","status":"passed","title":"计算风险统计面板汇总计数","duration":0.217899999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 生成风险排查报告 Markdown","status":"passed","title":"生成风险排查报告 Markdown","duration":1.3589000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可读化转换"],"fullName":"图谱可读化转换 生成风险排查报告时反映覆盖状态","status":"passed","title":"生成风险排查报告时反映覆盖状态","duration":0.19110000000000582,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700062,"endTime":1781738700090.359,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-readable.test.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"shares in-flight graph builds for the same project and data version","status":"passed","title":"shares in-flight graph builds for the same project and data version","duration":4.451300000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":[],"fullName":"does not reuse graph cache across projects with the same data version","status":"passed","title":"does not reuse graph cache across projects with the same data version","duration":0.8833000000000197,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703349,"endTime":1781738703354.8833,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-relevance.spec.ts"},{"assertionResults":[{"ancestorTitles":["graph visibility helpers"],"fullName":"graph visibility helpers hides nodes whose type is selected in the legend","status":"passed","title":"hides nodes whose type is selected in the legend","duration":2.3424000000000262,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph visibility helpers"],"fullName":"graph visibility helpers does not hide nodes with missing type metadata","status":"passed","title":"does not hide nodes with missing type metadata","duration":0.2891999999999939,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph visibility helpers"],"fullName":"graph visibility helpers hides edges connected to any hidden node type","status":"passed","title":"hides edges connected to any hidden node type","duration":0.4174000000000433,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph visibility helpers"],"fullName":"graph visibility helpers keeps edges visible when endpoint types are unknown","status":"passed","title":"keeps edges visible when endpoint types are unknown","duration":0.30009999999998627,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704012,"endTime":1781738704015.4175,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-visibility.test.ts"},{"assertionResults":[{"ancestorTitles":["图谱可视控制"],"fullName":"图谱可视控制 提供标签、线条颜色和关系文字可读性控制","status":"passed","title":"提供标签、线条颜色和关系文字可读性控制","duration":2.876800000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可视控制"],"fullName":"图谱可视控制 supports node dragging and opening the real profile page in the outline editing area","status":"passed","title":"supports node dragging and opening the real profile page in the outline editing area","duration":0.9158000000000186,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["图谱可视控制"],"fullName":"图谱可视控制 offers curved and node-avoiding edge styles","status":"passed","title":"offers curved and node-avoiding edge styles","duration":0.8785000000000025,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702045,"endTime":1781738702049.8784,"status":"passed","message":"","name":"E:/QMAI/src/lib/graph-visual-controls.test.ts"},{"assertionResults":[{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hi\" as a greeting","status":"passed","title":"treats \"hi\" as a greeting","duration":2.457299999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"Hi\" as a greeting","status":"passed","title":"treats \"Hi\" as a greeting","duration":0.410699999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"HELLO\" as a greeting","status":"passed","title":"treats \"HELLO\" as a greeting","duration":0.21390000000002374,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hello!\" as a greeting","status":"passed","title":"treats \"hello!\" as a greeting","duration":0.16059999999998809,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hey\" as a greeting","status":"passed","title":"treats \"hey\" as a greeting","duration":0.18819999999999482,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hey there\" as a greeting","status":"passed","title":"treats \"hey there\" as a greeting","duration":0.21059999999999945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hi there!\" as a greeting","status":"passed","title":"treats \"hi there!\" as a greeting","duration":0.17430000000001655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"yo\" as a greeting","status":"passed","title":"treats \"yo\" as a greeting","duration":0.18690000000003693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"sup\" as a greeting","status":"passed","title":"treats \"sup\" as a greeting","duration":0.21999999999997044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"howdy\" as a greeting","status":"passed","title":"treats \"howdy\" as a greeting","duration":0.25569999999999027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"good morning\" as a greeting","status":"passed","title":"treats \"good morning\" as a greeting","duration":0.1896000000000413,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"Good Morning!\" as a greeting","status":"passed","title":"treats \"Good Morning!\" as a greeting","duration":0.16650000000004184,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"good afternoon\" as a greeting","status":"passed","title":"treats \"good afternoon\" as a greeting","duration":0.11230000000000473,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"good evening\" as a greeting","status":"passed","title":"treats \"good evening\" as a greeting","duration":0.10070000000001755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"good night\" as a greeting","status":"passed","title":"treats \"good night\" as a greeting","duration":0.16140000000001464,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"what's up\" as a greeting","status":"passed","title":"treats \"what's up\" as a greeting","duration":0.15680000000003247,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"whats up\" as a greeting","status":"passed","title":"treats \"whats up\" as a greeting","duration":0.13280000000003156,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"wassup\" as a greeting","status":"passed","title":"treats \"wassup\" as a greeting","duration":0.0928000000000111,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"你好\" as a greeting","status":"passed","title":"treats \"你好\" as a greeting","duration":0.29410000000001446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"你好!\" as a greeting","status":"passed","title":"treats \"你好!\" as a greeting","duration":0.1675999999999931,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"你好。\" as a greeting","status":"passed","title":"treats \"你好。\" as a greeting","duration":0.09140000000002146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"您好\" as a greeting","status":"passed","title":"treats \"您好\" as a greeting","duration":0.11950000000001637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"嗨\" as a greeting","status":"passed","title":"treats \"嗨\" as a greeting","duration":0.10050000000001091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"嗨~\" as a greeting","status":"passed","title":"treats \"嗨~\" as a greeting","duration":0.14159999999998263,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"哈喽\" as a greeting","status":"passed","title":"treats \"哈喽\" as a greeting","duration":0.10059999999998581,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"哈啰\" as a greeting","status":"passed","title":"treats \"哈啰\" as a greeting","duration":0.10329999999999018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"哈囉\" as a greeting","status":"passed","title":"treats \"哈囉\" as a greeting","duration":0.10769999999996571,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"喂\" as a greeting","status":"passed","title":"treats \"喂\" as a greeting","duration":0.10090000000002419,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"早\" as a greeting","status":"passed","title":"treats \"早\" as a greeting","duration":0.19950000000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"早啊\" as a greeting","status":"passed","title":"treats \"早啊\" as a greeting","duration":0.15950000000003683,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"早上好\" as a greeting","status":"passed","title":"treats \"早上好\" as a greeting","duration":0.0997000000000412,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"中午好\" as a greeting","status":"passed","title":"treats \"中午好\" as a greeting","duration":0.09250000000002956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"下午好\" as a greeting","status":"passed","title":"treats \"下午好\" as a greeting","duration":0.09759999999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"晚上好\" as a greeting","status":"passed","title":"treats \"晚上好\" as a greeting","duration":0.09919999999999618,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"晚安\" as a greeting","status":"passed","title":"treats \"晚安\" as a greeting","duration":0.10849999999999227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"在吗\" as a greeting","status":"passed","title":"treats \"在吗\" as a greeting","duration":0.12049999999999272,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"在吗?\" as a greeting","status":"passed","title":"treats \"在吗?\" as a greeting","duration":0.1310999999999467,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"在不在\" as a greeting","status":"passed","title":"treats \"在不在\" as a greeting","duration":0.09380000000004429,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"有人吗\" as a greeting","status":"passed","title":"treats \"有人吗\" as a greeting","duration":0.10320000000001528,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"こんにちは\" as a greeting","status":"passed","title":"treats \"こんにちは\" as a greeting","duration":0.13880000000000337,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"こんばんは\" as a greeting","status":"passed","title":"treats \"こんばんは\" as a greeting","duration":0.136099999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"おはよう\" as a greeting","status":"passed","title":"treats \"おはよう\" as a greeting","duration":0.09050000000002001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"やあ\" as a greeting","status":"passed","title":"treats \"やあ\" as a greeting","duration":0.09180000000003474,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"どうも\" as a greeting","status":"passed","title":"treats \"どうも\" as a greeting","duration":0.09089999999997644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"안녕\" as a greeting","status":"passed","title":"treats \"안녕\" as a greeting","duration":0.11690000000004375,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"안녕하세요\" as a greeting","status":"passed","title":"treats \"안녕하세요\" as a greeting","duration":0.1308999999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hola\" as a greeting","status":"passed","title":"treats \"hola\" as a greeting","duration":0.23449999999996862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"bonjour\" as a greeting","status":"passed","title":"treats \"bonjour\" as a greeting","duration":0.1383000000000152,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"salut\" as a greeting","status":"passed","title":"treats \"salut\" as a greeting","duration":0.10069999999996071,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"hallo\" as a greeting","status":"passed","title":"treats \"hallo\" as a greeting","duration":0.070699999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \"ciao\" as a greeting","status":"passed","title":"treats \"ciao\" as a greeting","duration":0.07550000000003365,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \" hello \" as a greeting","status":"passed","title":"treats \" hello \" as a greeting","duration":0.07510000000002037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — positive cases"],"fullName":"isGreeting — positive cases treats \" 你好 \" as a greeting","status":"passed","title":"treats \" 你好 \" as a greeting","duration":0.07949999999999591,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"\"","status":"passed","title":"rejects \"\"","duration":0.11939999999998463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \" \"","status":"passed","title":"rejects \" \"","duration":0.077699999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"hello, how do I train a transformer?\"","status":"passed","title":"rejects \"hello, how do I train a transformer?\"","duration":0.07269999999999754,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"你好,请问怎么写 LangGraph 的 node?\"","status":"passed","title":"rejects \"你好,请问怎么写 LangGraph 的 node?\"","duration":0.07230000000004111,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"hi, could you summarize the page on attention mechanisms\"","status":"passed","title":"rejects \"hi, could you summarize the page on attention mechanisms\"","duration":0.06860000000000355,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"嗨,帮我查一下向量检索的那几篇\"","status":"passed","title":"rejects \"嗨,帮我查一下向量检索的那几篇\"","duration":0.1268999999999778,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"what is attention\"","status":"passed","title":"rejects \"what is attention\"","duration":0.06919999999996662,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"解释一下 MoE\"","status":"passed","title":"rejects \"解释一下 MoE\"","duration":0.06620000000003756,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"summarize the purpose page\"","status":"passed","title":"rejects \"summarize the purpose page\"","duration":0.06729999999998881,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"hibernation\"","status":"passed","title":"rejects \"hibernation\"","duration":0.07630000000000337,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"hello world program\"","status":"passed","title":"rejects \"hello world program\"","duration":0.06569999999999254,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"heyday\"","status":"passed","title":"rejects \"heyday\"","duration":0.06480000000004793,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"hi everyone I wanted to ask about the new retrieval pipeline design\"","status":"passed","title":"rejects \"hi everyone I wanted to ask about the new retrieval pipeline design\"","duration":0.06090000000000373,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"ok\"","status":"passed","title":"rejects \"ok\"","duration":0.06069999999999709,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"thanks\"","status":"passed","title":"rejects \"thanks\"","duration":0.06909999999999172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"bye\"","status":"passed","title":"rejects \"bye\"","duration":0.09070000000002665,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isGreeting — negative cases"],"fullName":"isGreeting — negative cases rejects \"再见\"","status":"passed","title":"rejects \"再见\"","duration":0.0905999999999949,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701020,"endTime":1781738701032.0908,"status":"passed","message":"","name":"E:/QMAI/src/lib/greeting-detector.test.ts"},{"assertionResults":[{"ancestorTitles":["user-facing hardcoded chinese strings"],"fullName":"user-facing hardcoded chinese strings moves selected visible UI copy into i18n","status":"passed","title":"moves selected visible UI copy into i18n","duration":13.618200000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["user-facing hardcoded chinese strings"],"fullName":"user-facing hardcoded chinese strings keeps visible trash and chapter header labels readable when i18n misses keys","status":"passed","title":"keeps visible trash and chapter header labels readable when i18n misses keys","duration":4.0301000000000045,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700426,"endTime":1781738700444.03,"status":"passed","message":"","name":"E:/QMAI/src/lib/hardcoded-ui-chinese-i18n.test.ts"},{"assertionResults":[{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for ollama with no API key","status":"passed","title":"returns true for ollama with no API key","duration":2.4033000000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for custom with no API key","status":"passed","title":"returns true for custom with no API key","duration":0.291499999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for claude-code with no API key","status":"passed","title":"returns true for claude-code with no API key","duration":0.29869999999999663,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for codex-cli with no API key","status":"passed","title":"returns true for codex-cli with no API key","duration":0.2839000000000169,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns false for openai with no API key","status":"passed","title":"returns false for openai with no API key","duration":0.24430000000000973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns false for openai with no model","status":"passed","title":"returns false for openai with no model","duration":0.19669999999996435,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for openai with an API key and model","status":"passed","title":"returns true for openai with an API key and model","duration":0.20570000000003574,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns false for anthropic with empty key","status":"passed","title":"returns false for anthropic with empty key","duration":0.19130000000001246,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm returns true for anthropic with a key and model","status":"passed","title":"returns true for anthropic with a key and model","duration":0.21170000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm treats whitespace-only key as missing for key-required providers","status":"passed","title":"treats whitespace-only key as missing for key-required providers","duration":0.2687000000000239,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm treats whitespace-only model as missing for key-required providers","status":"passed","title":"treats whitespace-only model as missing for key-required providers","duration":0.16980000000000928,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm PROVIDERS_WITHOUT_KEY covers the locally-running / CLI-auth providers","status":"passed","title":"PROVIDERS_WITHOUT_KEY covers the locally-running / CLI-auth providers","duration":0.2874999999999659,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm PROVIDERS_WITHOUT_KEY does not include hosted-API providers","status":"passed","title":"PROVIDERS_WITHOUT_KEY does not include hosted-API providers","duration":0.27930000000003474,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["hasUsableLlm"],"fullName":"hasUsableLlm classifies every LlmProvider into exactly one bucket","status":"passed","title":"classifies every LlmProvider into exactly one bucket","duration":0.48080000000004475,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702478,"endTime":1781738702483.4807,"status":"passed","message":"","name":"E:/QMAI/src/lib/has-usable-llm.test.ts"},{"assertionResults":[{"ancestorTitles":["findImageReferences (helper)"],"fullName":"findImageReferences (helper) captures markdown image syntax with position info","status":"passed","title":"captures markdown image syntax with position info","duration":3.3362000000000194,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findImageReferences (helper)"],"fullName":"findImageReferences (helper) ignores links and HTML img","status":"passed","title":"ignores links and HTML img","duration":1.425799999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages returns input unchanged when there are no image references","status":"passed","title":"returns input unchanged when there are no image references","duration":0.9878999999999678,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages captions a fresh image, rewrites alt text, persists cache","status":"passed","title":"captions a fresh image, rewrites alt text, persists cache","duration":16.7081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages dedupes by SHA-256: two refs to the same bytes → one LLM call, both rewritten","status":"passed","title":"dedupes by SHA-256: two refs to the same bytes → one LLM call, both rewritten","duration":1.7946000000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages uses cached caption when SHA-256 hash matches","status":"passed","title":"uses cached caption when SHA-256 hash matches","duration":1.0510999999999626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages sanitizes captions: strips newlines and replaces ] with )","status":"passed","title":"sanitizes captions: strips newlines and replaces ] with )","duration":1.4936999999999898,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages continues batch when one caption call fails, reporting failed count","status":"passed","title":"continues batch when one caption call fails, reporting failed count","duration":2.6012000000000057,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages respects shouldCaption filter: skips URLs that don't match","status":"passed","title":"respects shouldCaption filter: skips URLs that don't match","duration":0.9175000000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages uses urlToAbsPath hook when provided","status":"passed","title":"uses urlToAbsPath hook when provided","duration":0.936300000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages forwards AbortSignal to captionImage","status":"passed","title":"forwards AbortSignal to captionImage","duration":1.850500000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages recovers from corrupt cache JSON (logs and starts fresh)","status":"passed","title":"recovers from corrupt cache JSON (logs and starts fresh)","duration":0.8251999999999953,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages respects concurrency limit — three caption calls dispatch in parallel when concurrency=3","status":"passed","title":"respects concurrency limit — three caption calls dispatch in parallel when concurrency=3","duration":11.762699999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages calls onProgress after each image with running counts","status":"passed","title":"calls onProgress after each image with running counts","duration":0.887800000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages passes surrounding text as contextBefore / contextAfter to captionImage","status":"passed","title":"passes surrounding text as contextBefore / contextAfter to captionImage","duration":0.7221999999999866,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionMarkdownImages"],"fullName":"captionMarkdownImages clamps context windows at document boundaries (no out-of-range read)","status":"passed","title":"clamps context windows at document boundaries (no out-of-range read)","duration":0.7167999999999779,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699737,"endTime":1781738699785.7168,"status":"passed","message":"","name":"E:/QMAI/src/lib/image-caption-pipeline.test.ts"},{"assertionResults":[{"ancestorTitles":["ingest-cache — checkIngestCache"],"fullName":"ingest-cache — checkIngestCache returns null when no entry exists","status":"passed","title":"returns null when no entry exists","duration":2.7824999999999704,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-cache — checkIngestCache"],"fullName":"ingest-cache — checkIngestCache returns cached filesWritten when hash matches AND all files exist","status":"passed","title":"returns cached filesWritten when hash matches AND all files exist","duration":15.920200000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-cache — checkIngestCache"],"fullName":"ingest-cache — checkIngestCache returns null when hash matches but a cached file no longer exists on disk","status":"passed","title":"returns null when hash matches but a cached file no longer exists on disk","duration":1.9426000000000272,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-cache — checkIngestCache"],"fullName":"ingest-cache — checkIngestCache returns null when the content hash no longer matches (cache stale on content change)","status":"passed","title":"returns null when the content hash no longer matches (cache stale on content change)","duration":1.9660000000000082,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-cache — checkIngestCache"],"fullName":"ingest-cache — checkIngestCache returns null if fileExists itself throws (safer to re-ingest than to trust)","status":"passed","title":"returns null if fileExists itself throws (safer to re-ingest than to trust)","duration":1.2316000000000145,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700898,"endTime":1781738700922.2317,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest-cache.test.ts"},{"assertionResults":[{"ancestorTitles":["parseFileBlocks — canonical shapes"],"fullName":"parseFileBlocks — canonical shapes extracts a single well-formed block","status":"passed","title":"extracts a single well-formed block","duration":3.891300000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — canonical shapes"],"fullName":"parseFileBlocks — canonical shapes extracts multiple consecutive blocks","status":"passed","title":"extracts multiple consecutive blocks","duration":1.2823999999999387,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — canonical shapes"],"fullName":"parseFileBlocks — canonical shapes accepts hyphenated paths","status":"passed","title":"accepts hyphenated paths","duration":0.3572999999998956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — canonical shapes"],"fullName":"parseFileBlocks — canonical shapes ignores preamble prose before the first block","status":"passed","title":"ignores preamble prose before the first block","duration":0.39830000000006294,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H1: CRLF line endings"],"fullName":"parseFileBlocks — H1: CRLF line endings extracts all blocks when input uses Windows CRLF","status":"passed","title":"extracts all blocks when input uses Windows CRLF","duration":0.8994999999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H1: CRLF line endings"],"fullName":"parseFileBlocks — H1: CRLF line endings handles mixed CRLF body with LF markers","status":"passed","title":"handles mixed CRLF body with LF markers","duration":0.3409000000000333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H2: truncated streams (surface, don't hide)"],"fullName":"parseFileBlocks — H2: truncated streams (surface, don't hide) emits a warning when the final block has no closer","status":"passed","title":"emits a warning when the final block has no closer","duration":1.601099999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H2: truncated streams (surface, don't hide)"],"fullName":"parseFileBlocks — H2: truncated streams (surface, don't hide) warns when the only block is unclosed","status":"passed","title":"warns when the only block is unclosed","duration":0.6145000000000209,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H3: tolerant marker matching"],"fullName":"parseFileBlocks — H3: tolerant marker matching accepts `--- END FILE ---` (inner spaces)","status":"passed","title":"accepts `--- END FILE ---` (inner spaces)","duration":0.2979000000000269,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H3: tolerant marker matching"],"fullName":"parseFileBlocks — H3: tolerant marker matching accepts `---end file---` (lowercase)","status":"passed","title":"accepts `---end file---` (lowercase)","duration":0.3268000000000484,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H3: tolerant marker matching"],"fullName":"parseFileBlocks — H3: tolerant marker matching accepts `--- FILE: path ---` (spaces after leading dashes)","status":"passed","title":"accepts `--- FILE: path ---` (spaces after leading dashes)","duration":0.27750000000003183,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H3: tolerant marker matching"],"fullName":"parseFileBlocks — H3: tolerant marker matching tolerates trailing whitespace on the opener line","status":"passed","title":"tolerates trailing whitespace on the opener line","duration":0.23230000000000928,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H3: tolerant marker matching"],"fullName":"parseFileBlocks — H3: tolerant marker matching rejects marker variants embedded in prose / list items","status":"passed","title":"rejects marker variants embedded in prose / list items","duration":0.26290000000005875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H5: code-fence awareness"],"fullName":"parseFileBlocks — H5: code-fence awareness treats `---END FILE---` inside a fenced code block as body text","status":"passed","title":"treats `---END FILE---` inside a fenced code block as body text","duration":0.42399999999997817,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H5: code-fence awareness"],"fullName":"parseFileBlocks — H5: code-fence awareness handles multiple fenced blocks in one page","status":"passed","title":"handles multiple fenced blocks in one page","duration":0.2618999999999687,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H5: code-fence awareness"],"fullName":"parseFileBlocks — H5: code-fence awareness handles nested-length fences per CommonMark (outer 4-tick, inner 3-tick)","status":"passed","title":"handles nested-length fences per CommonMark (outer 4-tick, inner 3-tick)","duration":0.2459999999999809,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H5: code-fence awareness"],"fullName":"parseFileBlocks — H5: code-fence awareness a 3-tick fence does NOT close a 4-tick opener (CommonMark rule)","status":"passed","title":"a 3-tick fence does NOT close a 4-tick opener (CommonMark rule)","duration":0.324799999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — H6: empty-path blocks"],"fullName":"parseFileBlocks — H6: empty-path blocks surfaces a warning instead of silently dropping empty-path blocks","status":"passed","title":"surfaces a warning instead of silently dropping empty-path blocks","duration":0.55600000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects accepts canonical wiki paths","status":"passed","title":"accepts canonical wiki paths","duration":0.3652999999999338,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects empty / whitespace-only paths","status":"passed","title":"rejects empty / whitespace-only paths","duration":0.266399999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects paths outside wiki/ (no leading wiki/ prefix)","status":"passed","title":"rejects paths outside wiki/ (no leading wiki/ prefix)","duration":0.2337999999999738,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects absolute POSIX paths","status":"passed","title":"rejects absolute POSIX paths","duration":0.3770999999999276,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects Windows absolute paths and drive letters","status":"passed","title":"rejects Windows absolute paths and drive letters","duration":0.2155999999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects any segment exactly equal to .. (every position)","status":"passed","title":"rejects any segment exactly equal to .. (every position)","duration":0.25689999999997326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects does NOT reject filenames that merely CONTAIN double dots (e.g. version suffix)","status":"passed","title":"does NOT reject filenames that merely CONTAIN double dots (e.g. version suffix)","duration":0.18009999999992488,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects NUL bytes and control characters","status":"passed","title":"rejects NUL bytes and control characters","duration":0.20309999999994943,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects Windows-invalid characters in generated filenames","status":"passed","title":"rejects Windows-invalid characters in generated filenames","duration":0.29149999999992815,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects Windows reserved device names even with extensions","status":"passed","title":"rejects Windows reserved device names even with extensions","duration":0.252399999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSafeIngestPath — what the validator accepts and rejects"],"fullName":"isSafeIngestPath — what the validator accepts and rejects rejects segments ending in a space or dot for Windows compatibility","status":"passed","title":"rejects segments ending in a space or dot for Windows compatibility","duration":0.3187000000000353,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — path-traversal guard end-to-end"],"fullName":"parseFileBlocks — path-traversal guard end-to-end drops blocks with ../ paths and surfaces a warning","status":"passed","title":"drops blocks with ../ paths and surfaces a warning","duration":1.015700000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — path-traversal guard end-to-end"],"fullName":"parseFileBlocks — path-traversal guard end-to-end drops blocks with absolute paths","status":"passed","title":"drops blocks with absolute paths","duration":2.138900000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — path-traversal guard end-to-end"],"fullName":"parseFileBlocks — path-traversal guard end-to-end drops blocks not under wiki/","status":"passed","title":"drops blocks not under wiki/","duration":0.4516999999999598,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFileBlocks — path-traversal guard end-to-end"],"fullName":"parseFileBlocks — path-traversal guard end-to-end an LLM mixing safe + unsafe paths writes only the safe ones","status":"passed","title":"an LLM mixing safe + unsafe paths writes only the safe ones","duration":0.47080000000005384,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700427,"endTime":1781738700447.4707,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest-parse.test.ts"},{"assertionResults":[{"ancestorTitles":["ingest-queue persistence — write"],"fullName":"ingest-queue persistence — write writes .qmai/ingest-queue.json after enqueue","status":"passed","title":"writes .qmai/ingest-queue.json after enqueue","duration":16.162999999999954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — write"],"fullName":"ingest-queue persistence — write deduplicates repeated pending tasks for the same source","status":"passed","title":"deduplicates repeated pending tasks for the same source","duration":13.853599999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — write"],"fullName":"ingest-queue persistence — write persists Unicode source paths without corruption","status":"passed","title":"persists Unicode source paths without corruption","duration":8.840800000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — write"],"fullName":"ingest-queue persistence — write auto-creates .qmai/ directory when it doesn't exist","status":"passed","title":"auto-creates .qmai/ directory when it doesn't exist","duration":7.380800000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — write"],"fullName":"ingest-queue persistence — write each enqueue updates the persisted JSON in-place","status":"passed","title":"each enqueue updates the persisted JSON in-place","duration":9.213700000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip restoreQueue reads back exactly what enqueue wrote","status":"passed","title":"restoreQueue reads back exactly what enqueue wrote","duration":7.581200000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip converts 'processing' back to 'pending' when restoring (interrupted app close)","status":"passed","title":"converts 'processing' back to 'pending' when restoring (interrupted app close)","duration":12.066700000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip preserves 'failed' status on restore (does NOT auto-retry)","status":"passed","title":"preserves 'failed' status on restore (does NOT auto-retry)","duration":10.138199999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip returns empty queue when the file doesn't exist","status":"passed","title":"returns empty queue when the file doesn't exist","duration":2.437599999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip returns empty queue when the file is corrupted JSON","status":"passed","title":"returns empty queue when the file is corrupted JSON","duration":8.239499999999907,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — restore round-trip"],"fullName":"ingest-queue persistence — restore round-trip round-trips Unicode + folder context correctly","status":"passed","title":"round-trips Unicode + folder context correctly","duration":14.118399999999951,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue persistence — cross-project isolation"],"fullName":"ingest-queue persistence — cross-project isolation restoreQueue from project A does not leak into project B","status":"passed","title":"restoreQueue from project A does not leak into project B","duration":14.958300000000008,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699029,"endTime":1781738699154.9583,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest-queue.integration.test.ts"},{"assertionResults":[{"ancestorTitles":["ingest-queue — enqueue & basic processing"],"fullName":"ingest-queue — enqueue & basic processing enqueueIngest adds a pending task and triggers processing","status":"passed","title":"enqueueIngest adds a pending task and triggers processing","duration":7.072300000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — enqueue & basic processing"],"fullName":"ingest-queue — enqueue & basic processing persists queue to disk on enqueue","status":"passed","title":"persists queue to disk on enqueue","duration":1.181699999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — enqueue & basic processing"],"fullName":"ingest-queue — enqueue & basic processing enqueueBatch queues multiple tasks and processes them serially","status":"passed","title":"enqueueBatch queues multiple tasks and processes them serially","duration":2.26400000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — retry & failure"],"fullName":"ingest-queue — retry & failure retries a failing task up to MAX_RETRIES=3 then marks failed","status":"passed","title":"retries a failing task up to MAX_RETRIES=3 then marks failed","duration":1.59050000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — retry & failure"],"fullName":"ingest-queue — retry & failure succeeds on retry after transient failure","status":"passed","title":"succeeds on retry after transient failure","duration":1.5552999999999884,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — retry & failure"],"fullName":"ingest-queue — retry & failure treats autoIngest resolving to an empty array as a failure (not silent success)","status":"passed","title":"treats autoIngest resolving to an empty array as a failure (not silent success)","duration":1.108600000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — retry & failure"],"fullName":"ingest-queue — retry & failure retryTask resets a failed task to pending and reprocesses it","status":"passed","title":"retryTask resets a failed task to pending and reprocesses it","duration":1.4925000000000068,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — cancel"],"fullName":"ingest-queue — cancel cancelTask removes a pending task without calling autoIngest","status":"passed","title":"cancelTask removes a pending task without calling autoIngest","duration":1.019800000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — cancelAllTasks"],"fullName":"ingest-queue — cancelAllTasks drops all pending and processing tasks but keeps failed ones","status":"passed","title":"drops all pending and processing tasks but keeps failed ones","duration":1.1247999999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — cancelAllTasks"],"fullName":"ingest-queue — cancelAllTasks returns 0 when the queue is empty","status":"passed","title":"returns 0 when the queue is empty","duration":0.5778000000000247,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — cancelAllTasks"],"fullName":"ingest-queue — cancelAllTasks is safe to call after it has already cleared the queue","status":"passed","title":"is safe to call after it has already cleared the queue","duration":0.611500000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — clearCompletedTasks & summary"],"fullName":"ingest-queue — clearCompletedTasks & summary getQueueSummary returns accurate counts","status":"passed","title":"getQueueSummary returns accurate counts","duration":1.0166999999999575,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — clearCompletedTasks & summary"],"fullName":"ingest-queue — clearCompletedTasks & summary clearCompletedTasks drops failed tasks","status":"passed","title":"clearCompletedTasks drops failed tasks","duration":1.2644999999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — queue-drain triggers review sweep"],"fullName":"ingest-queue — queue-drain triggers review sweep calls sweepResolvedReviews once after a successful task drains the queue","status":"passed","title":"calls sweepResolvedReviews once after a successful task drains the queue","duration":2.6742999999999597,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — queue-drain triggers review sweep"],"fullName":"ingest-queue — queue-drain triggers review sweep does NOT trigger sweep when no task has been processed since the last drain","status":"passed","title":"does NOT trigger sweep when no task has been processed since the last drain","duration":0.7361000000000217,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — queue-drain triggers review sweep"],"fullName":"ingest-queue — queue-drain triggers review sweep does NOT trigger sweep when all tasks fail (nothing was successfully ingested)","status":"passed","title":"does NOT trigger sweep when all tasks fail (nothing was successfully ingested)","duration":1.124400000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — clearQueueState"],"fullName":"ingest-queue — clearQueueState clears pending tasks and resets processing flag","status":"passed","title":"clears pending tasks and resets processing flag","duration":0.7993999999999915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — clearQueueState"],"fullName":"ingest-queue — clearQueueState processedSinceDrain flag resets so a post-switch no-op won't trigger sweep","status":"passed","title":"processedSinceDrain flag resets so a post-switch no-op won't trigger sweep","duration":0.6634000000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — restoreQueue"],"fullName":"ingest-queue — restoreQueue resets in-memory state before loading, preventing cross-project bleed","status":"passed","title":"resets in-memory state before loading, preventing cross-project bleed","duration":0.6711999999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — restoreQueue"],"fullName":"ingest-queue — restoreQueue converts 'processing' tasks back to 'pending' on restore (interrupted by app close)","status":"passed","title":"converts 'processing' tasks back to 'pending' on restore (interrupted by app close)","duration":1.0629000000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — restoreQueue"],"fullName":"ingest-queue — restoreQueue leaves 'failed' tasks as failed on restore","status":"passed","title":"leaves 'failed' tasks as failed on restore","duration":0.5874000000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — restoreQueue"],"fullName":"ingest-queue — restoreQueue backfills projectId on older task files that predate the field","status":"passed","title":"backfills projectId on older task files that predate the field","duration":0.7603000000000293,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — pauseQueue & switch-project survival"],"fullName":"ingest-queue — pauseQueue & switch-project survival pauseQueue persists pending/processing tasks to the paused project's disk","status":"passed","title":"pauseQueue persists pending/processing tasks to the paused project's disk","duration":0.9157000000000153,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — pauseQueue & switch-project survival"],"fullName":"ingest-queue — pauseQueue & switch-project survival pauseQueue then restoreQueue of SAME project brings tasks back","status":"passed","title":"pauseQueue then restoreQueue of SAME project brings tasks back","duration":0.8470999999999549,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest-queue — pauseQueue & switch-project survival"],"fullName":"ingest-queue — pauseQueue & switch-project survival processNext bails if currentProjectId changes mid-ingest (no cross-project writes)","status":"passed","title":"processNext bails if currentProjectId changes mid-ingest (no cross-project writes)","duration":0.7461000000000126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanupWrittenFiles — embedding cascade"],"fullName":"cleanupWrittenFiles — embedding cascade deletes each file AND drops its embedding chunks (relative paths)","status":"passed","title":"deletes each file AND drops its embedding chunks (relative paths)","duration":30.2423,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanupWrittenFiles — embedding cascade"],"fullName":"cleanupWrittenFiles — embedding cascade uses absolute paths verbatim (doesn't double-prefix the project path)","status":"passed","title":"uses absolute paths verbatim (doesn't double-prefix the project path)","duration":0.7171000000000163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanupWrittenFiles — embedding cascade"],"fullName":"cleanupWrittenFiles — embedding cascade continues to subsequent files when one delete throws","status":"passed","title":"continues to subsequent files when one delete throws","duration":0.5611000000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanupWrittenFiles — embedding cascade"],"fullName":"cleanupWrittenFiles — embedding cascade swallows removePageEmbedding errors so a LanceDB issue doesn't abort cleanup","status":"passed","title":"swallows removePageEmbedding errors so a LanceDB issue doesn't abort cleanup","duration":0.4882000000000062,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanupWrittenFiles — embedding cascade"],"fullName":"cleanupWrittenFiles — embedding cascade handles Windows backslash paths via getFileStem","status":"passed","title":"handles Windows backslash paths via getFileStem","duration":0.509399999999971,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697474,"endTime":1781738697540.5095,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest-queue.test.ts"},{"assertionResults":[{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent returns clean content unchanged","status":"passed","title":"returns clean content unchanged","duration":2.8168000000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent strips a ```yaml-wrapped document and leaves the frontmatter block standard","status":"passed","title":"strips a ```yaml-wrapped document and leaves the frontmatter block standard","duration":0.4486999999999739,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent strips a ```md-wrapped document","status":"passed","title":"strips a ```md-wrapped document","duration":0.2932999999999879,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent strips a ```markdown-wrapped document","status":"passed","title":"strips a ```markdown-wrapped document","duration":0.25319999999999254,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent strips a bare ```-wrapped document (no lang)","status":"passed","title":"strips a bare ```-wrapped document (no lang)","duration":0.2569000000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent does NOT strip a non-fence-wrapped document containing a fenced code block in the body","status":"passed","title":"does NOT strip a non-fence-wrapped document containing a fenced code block in the body","duration":0.2254000000000076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent does NOT strip a partially-fenced document (open fence but no matching close)","status":"passed","title":"does NOT strip a partially-fenced document (open fence but no matching close)","duration":0.2254000000000076,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent strips a leading `frontmatter:` key prefix when followed by a real --- block","status":"passed","title":"strips a leading `frontmatter:` key prefix when followed by a real --- block","duration":0.22350000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent does NOT strip the word `frontmatter:` when it appears mid-document (in prose)","status":"passed","title":"does NOT strip the word `frontmatter:` when it appears mid-document (in prose)","duration":0.2569000000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent repairs an invalid `key: [[a]], [[b]]` wikilink list inside frontmatter","status":"passed","title":"repairs an invalid `key: [[a]], [[b]]` wikilink list inside frontmatter","duration":0.36299999999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent doesn't touch a single `key: [[a]]` (not a list — leave the user's intent alone)","status":"passed","title":"doesn't touch a single `key: [[a]]` (not a list — leave the user's intent alone)","duration":0.18330000000000268,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent doesn't touch wikilink-style text that appears in the body","status":"passed","title":"doesn't touch wikilink-style text that appears in the body","duration":0.4806000000000097,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sanitizeIngestedFileContent"],"fullName":"sanitizeIngestedFileContent composes all three repairs on a real-corpus-shaped input","status":"passed","title":"composes all three repairs on a real-corpus-shaped input","duration":0.180499999999995,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702767,"endTime":1781738702773.1804,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest-sanitize.test.ts"},{"assertionResults":[{"ancestorTitles":["buildAnalysisPrompt language directive"],"fullName":"buildAnalysisPrompt language directive injects the user's explicit language setting","status":"passed","title":"injects the user's explicit language setting","duration":3.511199999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnalysisPrompt language directive"],"fullName":"buildAnalysisPrompt language directive uses user setting even when source is in a different language","status":"passed","title":"uses user setting even when source is in a different language","duration":0.714999999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnalysisPrompt language directive"],"fullName":"buildAnalysisPrompt language directive auto mode falls back to detecting source content language","status":"passed","title":"auto mode falls back to detecting source content language","duration":0.6465999999999781,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnalysisPrompt language directive"],"fullName":"buildAnalysisPrompt language directive auto mode with empty source defaults to English","status":"passed","title":"auto mode with empty source defaults to English","duration":1.3741999999999734,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnalysisPrompt language directive"],"fullName":"buildAnalysisPrompt language directive contains structural analysis sections","status":"passed","title":"contains structural analysis sections","duration":0.9960000000000377,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildGenerationPrompt language directive"],"fullName":"buildGenerationPrompt language directive injects the user's explicit language setting","status":"passed","title":"injects the user's explicit language setting","duration":0.6647000000000389,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildGenerationPrompt language directive"],"fullName":"buildGenerationPrompt language directive honors Vietnamese setting","status":"passed","title":"honors Vietnamese setting","duration":0.4228999999999701,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildGenerationPrompt language directive"],"fullName":"buildGenerationPrompt language directive auto mode detects from source content","status":"passed","title":"auto mode detects from source content","duration":0.4515999999999849,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildGenerationPrompt language directive"],"fullName":"buildGenerationPrompt language directive includes the source filename in output instructions","status":"passed","title":"includes the source filename in output instructions","duration":0.40999999999996817,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildGenerationPrompt language directive"],"fullName":"buildGenerationPrompt language directive respects user setting regardless of source content language","status":"passed","title":"respects user setting regardless of source content language","duration":0.4674999999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["analysis + generation prompt consistency"],"fullName":"analysis + generation prompt consistency both stages declare the same language for a given setting","status":"passed","title":"both stages declare the same language for a given setting","duration":0.611500000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["analysis + generation prompt consistency"],"fullName":"analysis + generation prompt consistency both stages in auto mode agree on detected language from source","status":"passed","title":"both stages in auto mode agree on detected language from source","duration":0.41900000000003956,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701422,"endTime":1781738701433.6116,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest.prompt.test.ts"},{"assertionResults":[{"ancestorTitles":["ingest scenarios (fixture-driven)"],"fullName":"ingest scenarios (fixture-driven) basic-new-source","status":"passed","title":"basic-new-source","duration":46.3134,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest scenarios (fixture-driven)"],"fullName":"ingest scenarios (fixture-driven) generates-review-items","status":"passed","title":"generates-review-items","duration":16.763599999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest scenarios (fixture-driven)"],"fullName":"ingest scenarios (fixture-driven) references-existing-wikilinks","status":"passed","title":"references-existing-wikilinks","duration":51.88460000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ingest scenarios (fixture-driven)"],"fullName":"ingest scenarios (fixture-driven) chinese-source","status":"passed","title":"chinese-source","duration":19.363399999999956,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697403,"endTime":1781738697538.3633,"status":"passed","message":"","name":"E:/QMAI/src/lib/ingest.scenarios.test.ts"},{"assertionResults":[{"ancestorTitles":["isImeComposing"],"fullName":"isImeComposing is false for a plain Enter press with no IME activity","status":"passed","title":"is false for a plain Enter press with no IME activity","duration":2.3055999999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isImeComposing"],"fullName":"isImeComposing is true when the W3C `isComposing` flag is set","status":"passed","title":"is true when the W3C `isComposing` flag is set","duration":0.3153000000000077,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isImeComposing"],"fullName":"isImeComposing is true when keyCode === 229 even after isComposing has flipped back","status":"passed","title":"is true when keyCode === 229 even after isComposing has flipped back","duration":0.2664999999999793,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isImeComposing"],"fullName":"isImeComposing is true for non-Enter keys during composition","status":"passed","title":"is true for non-Enter keys during composition","duration":0.2587000000000046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isImeComposing"],"fullName":"isImeComposing is false for Shift+Enter (no IME)","status":"passed","title":"is false for Shift+Enter (no IME)","duration":0.26590000000001623,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704179,"endTime":1781738704182.2659,"status":"passed","message":"","name":"E:/QMAI/src/lib/keyboard-utils.test.ts"},{"assertionResults":[{"ancestorTitles":["language metadata"],"fullName":"language metadata marks Persian as RTL Farsi for rendering and prompts","status":"passed","title":"marks Persian as RTL Farsi for rendering and prompts","duration":2.603499999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["language metadata"],"fullName":"language metadata keeps Persian and Arabic in the same script family","status":"passed","title":"keeps Persian and Arabic in the same script family","duration":0.3036000000000172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["language metadata"],"fullName":"language metadata defaults unknown languages to LTR with the original prompt name","status":"passed","title":"defaults unknown languages to LTR with the original prompt name","duration":0.30459999999999354,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703895,"endTime":1781738703898.3047,"status":"passed","message":"","name":"E:/QMAI/src/lib/language-metadata.test.ts"},{"assertionResults":[{"ancestorTitles":["lint scenarios (fixture-driven)"],"fullName":"lint scenarios (fixture-driven) structural/clean-wiki","status":"passed","title":"structural/clean-wiki","duration":22.588700000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint scenarios (fixture-driven)"],"fullName":"lint scenarios (fixture-driven) structural/orphan-page","status":"passed","title":"structural/orphan-page","duration":34.25800000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint scenarios (fixture-driven)"],"fullName":"lint scenarios (fixture-driven) structural/broken-link","status":"passed","title":"structural/broken-link","duration":14.943900000000212,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint scenarios (fixture-driven)"],"fullName":"lint scenarios (fixture-driven) structural/no-outlinks","status":"passed","title":"structural/no-outlinks","duration":15.729399999999941,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint scenarios (fixture-driven)"],"fullName":"lint scenarios (fixture-driven) semantic/contradiction-found","status":"passed","title":"semantic/contradiction-found","duration":45.65219999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699297,"endTime":1781738699429.652,"status":"passed","message":"","name":"E:/QMAI/src/lib/lint.scenarios.test.ts"},{"assertionResults":[{"ancestorTitles":["runSemanticLint — language directive"],"fullName":"runSemanticLint — language directive uses explicit user setting","status":"passed","title":"uses explicit user setting","duration":3.9913999999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — language directive"],"fullName":"runSemanticLint — language directive auto mode detects from the concatenated page summaries","status":"passed","title":"auto mode detects from the concatenated page summaries","duration":1.1779999999999973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — language directive"],"fullName":"runSemanticLint — language directive explicit setting wins over source language","status":"passed","title":"explicit setting wins over source language","duration":0.9070000000000391,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — activity & early returns"],"fullName":"runSemanticLint — activity & early returns uses Chinese activity text when semantic lint completes","status":"passed","title":"uses Chinese activity text when semantic lint completes","duration":2.8689999999999714,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — activity & early returns"],"fullName":"runSemanticLint — activity & early returns returns empty and marks done when wiki has no pages","status":"passed","title":"returns empty and marks done when wiki has no pages","duration":1.5086999999999762,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — activity & early returns"],"fullName":"runSemanticLint — activity & early returns marks error status when wiki directory read fails","status":"passed","title":"marks error status when wiki directory read fails","duration":0.8675999999999817,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runSemanticLint — novel mode"],"fullName":"runSemanticLint — novel mode builds a novel consistency prompt from context pack and current chapter","status":"passed","title":"builds a novel consistency prompt from context pack and current chapter","duration":1.5678000000000338,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701198,"endTime":1781738701211.5679,"status":"passed","message":"","name":"E:/QMAI/src/lib/lint.test.ts"},{"assertionResults":[{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures recognises WebKit's Load failed error","status":"passed","title":"recognises WebKit's Load failed error","duration":2.7864000000000146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures recognises Chromium/Edge TypeError fetch failures","status":"passed","title":"recognises Chromium/Edge TypeError fetch failures","duration":0.352800000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures recognises any TypeError as a Chromium fetch failure class","status":"passed","title":"recognises any TypeError as a Chromium fetch failure class","duration":0.30040000000002465,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures recognises messages containing network error","status":"passed","title":"recognises messages containing network error","duration":0.362700000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures recognises Tauri reqwest send failures","status":"passed","title":"recognises Tauri reqwest send failures","duration":0.32689999999996644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures rejects AbortError because it means the user cancelled","status":"passed","title":"rejects AbortError because it means the user cancelled","duration":0.25619999999997844,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures rejects plain application errors","status":"passed","title":"rejects plain application errors","duration":0.23559999999997672,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isFetchNetworkError cross-webview fetch failures"],"fullName":"isFetchNetworkError cross-webview fetch failures rejects non-Error values","status":"passed","title":"rejects non-Error values","duration":0.39980000000002747,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamChat network retry"],"fullName":"streamChat network retry waits and retries transient request-send failures before reporting an error","status":"passed","title":"waits and retries transient request-send failures before reporting an error","duration":43.4708,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamChat network retry"],"fullName":"streamChat network retry reports a clear Chinese message after transient request-send failures are exhausted","status":"passed","title":"reports a clear Chinese message after transient request-send failures are exhausted","duration":3.0570000000000164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamChat network retry"],"fullName":"streamChat network retry keeps request-send retries alive until the five-minute retry window is exhausted","status":"passed","title":"keeps request-send retries alive until the five-minute retry window is exhausted","duration":5.259700000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamChat network retry"],"fullName":"streamChat network retry does not keep retrying after the user cancels during the retry wait","status":"passed","title":"does not keep retrying after the user cancels during the retry wait","duration":1.1673000000000116,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699546,"endTime":1781738699605.1672,"status":"passed","message":"","name":"E:/QMAI/src/lib/llm-client.test.ts"},{"assertionResults":[{"ancestorTitles":["llm provider reasoning options"],"fullName":"llm provider reasoning options sends reasoning_effort for explicit custom OpenAI-compatible reasoning mode","status":"passed","title":"sends reasoning_effort for explicit custom OpenAI-compatible reasoning mode","duration":3.722499999999968,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llm provider reasoning options"],"fullName":"llm provider reasoning options enables Qwen3 thinking when explicit reasoning is enabled","status":"passed","title":"enables Qwen3 thinking when explicit reasoning is enabled","duration":1.344600000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llm provider reasoning options"],"fullName":"llm provider reasoning options keeps Qwen3 thinking disabled when reasoning is off","status":"passed","title":"keeps Qwen3 thinking disabled when reasoning is off","duration":0.9734999999999445,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llm provider reasoning options"],"fullName":"llm provider reasoning options maps Responses API max reasoning to high effort","status":"passed","title":"maps Responses API max reasoning to high effort","duration":0.5600000000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llm provider reasoning options"],"fullName":"llm provider reasoning options maps Responses API custom reasoning to high effort","status":"passed","title":"maps Responses API custom reasoning to high effort","duration":0.406599999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers clears Origin for remote custom gateways","status":"passed","title":"clears Origin for remote custom gateways","duration":0.8334999999999582,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers keeps localhost Origin only for local endpoints","status":"passed","title":"keeps localhost Origin only for local endpoints","duration":0.31560000000001764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers preserves existing auth headers when clearing Origin","status":"passed","title":"preserves existing auth headers when clearing Origin","duration":0.3099000000000274,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers clears Origin for actual custom OpenAI-compatible chat requests","status":"passed","title":"clears Origin for actual custom OpenAI-compatible chat requests","duration":0.3442000000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers clears Origin for actual custom Responses API requests","status":"passed","title":"clears Origin for actual custom Responses API requests","duration":0.362700000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["custom provider headers"],"fullName":"custom provider headers clears Origin for actual custom Anthropic-compatible requests","status":"passed","title":"clears Origin for actual custom Anthropic-compatible requests","duration":0.4097000000000435,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701469,"endTime":1781738701479.4097,"status":"passed","message":"","name":"E:/QMAI/src/lib/llm-providers.spec.ts"},{"assertionResults":[{"ancestorTitles":["OpenAI buildBody — vision content"],"fullName":"OpenAI buildBody — vision content emits image_url block with data: URL framing","status":"passed","title":"emits image_url block with data: URL framing","duration":4.842300000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI buildBody — vision content"],"fullName":"OpenAI buildBody — vision content flattens single-text-block array back to a string (avoids accidentally regressing text-only callers)","status":"passed","title":"flattens single-text-block array back to a string (avoids accidentally regressing text-only callers)","duration":0.5896999999999935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["OpenAI buildBody — vision content"],"fullName":"OpenAI buildBody — vision content string content stays a string (byte-identical to pre-vision wire)","status":"passed","title":"string content stays a string (byte-identical to pre-vision wire)","duration":0.2746999999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Anthropic buildBody — vision content"],"fullName":"Anthropic buildBody — vision content emits image block with media_type/data inside source","status":"passed","title":"emits image block with media_type/data inside source","duration":1.0323000000000206,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Anthropic buildBody — vision content"],"fullName":"Anthropic buildBody — vision content flattens system content with images by dropping image blocks (anthropic doesn't accept system images)","status":"passed","title":"flattens system content with images by dropping image blocks (anthropic doesn't accept system images)","duration":0.44720000000000937,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Google buildBody — vision content"],"fullName":"Google buildBody — vision content emits parts with inline_data for images","status":"passed","title":"emits parts with inline_data for images","duration":0.7452000000000112,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Ollama / custom (chat_completions) — vision content"],"fullName":"Ollama / custom (chat_completions) — vision content ollama uses OpenAI-shaped image_url block (works on /v1/chat/completions for vision-capable models)","status":"passed","title":"ollama uses OpenAI-shaped image_url block (works on /v1/chat/completions for vision-capable models)","duration":0.9961000000000126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Ollama / custom (chat_completions) — vision content"],"fullName":"Ollama / custom (chat_completions) — vision content custom endpoint in chat_completions mode emits the same image_url block","status":"passed","title":"custom endpoint in chat_completions mode emits the same image_url block","duration":1.334600000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls maps DeepSeek-compatible custom endpoints to thinking disabled for structured tasks","status":"passed","title":"maps DeepSeek-compatible custom endpoints to thinking disabled for structured tasks","duration":0.6666999999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls maps DeepSeek high reasoning to thinking enabled plus reasoning_effort","status":"passed","title":"maps DeepSeek high reasoning to thinking enabled plus reasoning_effort","duration":0.4470999999999776,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls does not send an undocumented DeepSeek max_reasoning_tokens field","status":"passed","title":"does not send an undocumented DeepSeek max_reasoning_tokens field","duration":0.33780000000001564,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls disables Qwen3 thinking on OpenAI-compatible local endpoints","status":"passed","title":"disables Qwen3 thinking on OpenAI-compatible local endpoints","duration":0.2939000000000078,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls strips temperature for Kimi/Moonshot OpenAI-compatible endpoints","status":"passed","title":"strips temperature for Kimi/Moonshot OpenAI-compatible endpoints","duration":0.28879999999998063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls maps Anthropic reasoning budget to extended thinking and removes sampling knobs","status":"passed","title":"maps Anthropic reasoning budget to extended thinking and removes sampling knobs","duration":0.25279999999997926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning controls"],"fullName":"reasoning controls maps Gemini reasoning off to thinkingBudget 0","status":"passed","title":"maps Gemini reasoning off to thinkingBudget 0","duration":0.19350000000002865,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700899,"endTime":1781738700912.2527,"status":"passed","message":"","name":"E:/QMAI/src/lib/llm-providers.test.ts"},{"assertionResults":[{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc passes http(s) URLs through unchanged","status":"passed","title":"passes http(s) URLs through unchanged","duration":2.5790000000000077,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc passes data: URIs through unchanged (inline base64)","status":"passed","title":"passes data: URIs through unchanged (inline base64)","duration":0.6435000000000173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc passes blob: and tauri: URIs through unchanged","status":"passed","title":"passes blob: and tauri: URIs through unchanged","duration":0.4393000000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc treats a wiki-rooted relative path as media under <project>/wiki/","status":"passed","title":"treats a wiki-rooted relative path as media under <project>/wiki/","duration":0.4011000000000422,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc strips a leading ./ for cleanliness","status":"passed","title":"strips a leading ./ for cleanliness","duration":0.3251000000000204,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc resolves nested paths (e.g. user-organized subfolders) under wiki/ root","status":"passed","title":"resolves nested paths (e.g. user-organized subfolders) under wiki/ root","duration":0.26149999999995543,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc treats an absolute POSIX path as a literal filesystem path","status":"passed","title":"treats an absolute POSIX path as a literal filesystem path","duration":0.27379999999999427,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc treats a Windows drive-letter path as absolute","status":"passed","title":"treats a Windows drive-letter path as absolute","duration":0.26480000000003656,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc treats a UNC path as absolute","status":"passed","title":"treats a UNC path as absolute","duration":0.3049000000000319,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc returns the raw src unchanged when no project is loaded","status":"passed","title":"returns the raw src unchanged when no project is loaded","duration":0.30169999999998254,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc normalizes Windows backslashes in projectPath via path-utils","status":"passed","title":"normalizes Windows backslashes in projectPath via path-utils","duration":0.23220000000003438,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveMarkdownImageSrc"],"fullName":"resolveMarkdownImageSrc returns empty string verbatim for empty src","status":"passed","title":"returns empty string verbatim for empty src","duration":0.19640000000003965,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702838,"endTime":1781738702844.2322,"status":"passed","message":"","name":"E:/QMAI/src/lib/markdown-image-resolver.test.ts"},{"assertionResults":[{"ancestorTitles":["novel review action items"],"fullName":"novel review action items keeps a stable target and issue id for AI review result actions","status":"passed","title":"keeps a stable target and issue id for AI review result actions","duration":3.0757999999999583,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novel review action items"],"fullName":"novel review action items filters ignored AI review results with the same persisted issue id","status":"passed","title":"filters ignored AI review results with the same persisted issue id","duration":0.6141999999999825,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novel review action items"],"fullName":"novel review action items builds actionable items for a selected six-dimension review bucket","status":"passed","title":"builds actionable items for a selected six-dimension review bucket","duration":1.768100000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novel review action items"],"fullName":"novel review action items builds actionable items directly from independent six-dimension results","status":"passed","title":"builds actionable items directly from independent six-dimension results","duration":0.5944000000000074,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702588,"endTime":1781738702594.5945,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel-review-action-items.spec.ts"},{"assertionResults":[{"ancestorTitles":["outline save draft"],"fullName":"outline save draft ignores frontmatter when deriving an outline title","status":"passed","title":"ignores frontmatter when deriving an outline title","duration":5.729000000000042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline save draft"],"fullName":"outline save draft changes the title when it already exists in the outline library","status":"passed","title":"changes the title when it already exists in the outline library","duration":1.226699999999994,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702537,"endTime":1781738702544.2268,"status":"passed","message":"","name":"E:/QMAI/src/lib/outline-save.spec.ts"},{"assertionResults":[{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage uses the explicit user setting verbatim (Chinese)","status":"passed","title":"uses the explicit user setting verbatim (Chinese)","duration":2.7981000000000336,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage explicit user setting beats fallback detection (source is English, setting is Japanese)","status":"passed","title":"explicit user setting beats fallback detection (source is English, setting is Japanese)","duration":0.46230000000002747,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage auto mode falls back to detectLanguage on the fallback text","status":"passed","title":"auto mode falls back to detectLanguage on the fallback text","duration":0.6092000000000439,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage auto mode detects Persian separately from Arabic","status":"passed","title":"auto mode detects Persian separately from Arabic","duration":1.3822999999999865,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage auto mode with empty fallback defaults to English","status":"passed","title":"auto mode with empty fallback defaults to English","duration":1.3455000000000155,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getOutputLanguage"],"fullName":"getOutputLanguage auto mode with no fallback arg defaults to English","status":"passed","title":"auto mode with no fallback arg defaults to English","duration":0.787399999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective contains the MANDATORY OUTPUT LANGUAGE header","status":"passed","title":"contains the MANDATORY OUTPUT LANGUAGE header","duration":0.540300000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective names the language multiple times for emphasis","status":"passed","title":"names the language multiple times for emphasis","duration":0.34159999999997126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective follows the explicit setting even when fallback text is in another language","status":"passed","title":"follows the explicit setting even when fallback text is in another language","duration":0.46690000000000964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective uses detected language in auto mode","status":"passed","title":"uses detected language in auto mode","duration":0.4362000000000421,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective uses an explicit Persian/Farsi prompt name","status":"passed","title":"uses an explicit Persian/Farsi prompt name","duration":0.27719999999999345,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageDirective"],"fullName":"buildLanguageDirective explicitly overrides source content language","status":"passed","title":"explicitly overrides source content language","duration":0.26360000000005357,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageReminder"],"fullName":"buildLanguageReminder is a concise reminder, not a full directive","status":"passed","title":"is a concise reminder, not a full directive","duration":0.39859999999998763,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageReminder"],"fullName":"buildLanguageReminder uses the explicit setting","status":"passed","title":"uses the explicit setting","duration":0.2907999999999902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageReminder"],"fullName":"buildLanguageReminder uses detected language in auto mode","status":"passed","title":"uses detected language in auto mode","duration":0.2907000000000153,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildLanguageReminder"],"fullName":"buildLanguageReminder reminds Persian as Persian/Farsi","status":"passed","title":"reminds Persian as Persian/Farsi","duration":0.23619999999999663,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700829,"endTime":1781738700840.2908,"status":"passed","message":"","name":"E:/QMAI/src/lib/output-language.test.ts"},{"assertionResults":[{"ancestorTitles":["mergePageContent — fast paths"],"fullName":"mergePageContent — fast paths returns newContent when existingContent is null (new page)","status":"passed","title":"returns newContent when existingContent is null (new page)","duration":4.091799999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — fast paths"],"fullName":"mergePageContent — fast paths returns existingContent when both contents are byte-identical","status":"passed","title":"returns existingContent when both contents are byte-identical","duration":0.47919999999999163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — fast paths"],"fullName":"mergePageContent — fast paths skips LLM when bodies are identical (only sources differ)","status":"passed","title":"skips LLM when bodies are identical (only sources differ)","duration":4.594699999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM merge"],"fullName":"mergePageContent — LLM merge calls the merger when bodies differ and uses the merged output","status":"passed","title":"calls the merger when bodies differ and uses the merged output","duration":2.5564000000000533,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM merge"],"fullName":"mergePageContent — LLM merge preserves locked title even if LLM rewrote it","status":"passed","title":"preserves locked title even if LLM rewrote it","duration":0.6637000000000057,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM merge"],"fullName":"mergePageContent — LLM merge preserves locked type even if LLM changed it","status":"passed","title":"preserves locked type even if LLM changed it","duration":0.6879999999999882,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback falls back to array-merged incoming when LLM throws","status":"passed","title":"falls back to array-merged incoming when LLM throws","duration":2.0037000000000376,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback rejects LLM output that shrinks body below 70% of max(old, new)","status":"passed","title":"rejects LLM output that shrinks body below 70% of max(old, new)","duration":0.6656000000000404,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback rejects LLM output that has no frontmatter at all","status":"passed","title":"rejects LLM output that has no frontmatter at all","duration":0.6014000000000124,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback calls the optional backup callback when falling back","status":"passed","title":"calls the optional backup callback when falling back","duration":2.0434000000000196,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback does not call backup when LLM merge succeeds","status":"passed","title":"does not call backup when LLM merge succeeds","duration":0.5264000000000237,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergePageContent — LLM failure fallback"],"fullName":"mergePageContent — LLM failure fallback backup failure is swallowed (best-effort, never blocks the write)","status":"passed","title":"backup failure is swallowed (best-effort, never blocks the write)","duration":0.7425999999999817,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700440,"endTime":1781738700460.7427,"status":"passed","message":"","name":"E:/QMAI/src/lib/page-merge.test.ts"},{"assertionResults":[{"ancestorTitles":["normalizePath — properties"],"fullName":"normalizePath — properties is idempotent","status":"passed","title":"is idempotent","duration":13.929599999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath — properties"],"fullName":"normalizePath — properties output contains no backslashes","status":"passed","title":"output contains no backslashes","duration":5.224400000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath — properties"],"fullName":"normalizePath — properties only changes backslashes — length is preserved","status":"passed","title":"only changes backslashes — length is preserved","duration":4.426899999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName / getFileStem — properties"],"fullName":"getFileName / getFileStem — properties getFileName of a path ending in /foo is 'foo'","status":"passed","title":"getFileName of a path ending in /foo is 'foo'","duration":8.141900000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName / getFileStem — properties"],"fullName":"getFileName / getFileStem — properties getFileStem strips at the LAST dot (if one exists past position 0)","status":"passed","title":"getFileStem strips at the LAST dot (if one exists past position 0)","duration":7.160500000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath — properties"],"fullName":"joinPath — properties contains no double-slashes in output","status":"passed","title":"contains no double-slashes in output","duration":6.166099999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath — properties"],"fullName":"joinPath — properties contains no backslashes in output","status":"passed","title":"contains no backslashes in output","duration":5.448299999999961,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699628,"endTime":1781738699679.4482,"status":"passed","message":"","name":"E:/QMAI/src/lib/path-utils.property.test.ts"},{"assertionResults":[{"ancestorTitles":["normalizePath"],"fullName":"normalizePath converts backslashes to forward slashes","status":"passed","title":"converts backslashes to forward slashes","duration":1.990700000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath"],"fullName":"normalizePath leaves forward slash paths unchanged","status":"passed","title":"leaves forward slash paths unchanged","duration":0.26399999999995316,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath"],"fullName":"normalizePath handles mixed separators","status":"passed","title":"handles mixed separators","duration":0.22710000000000719,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath"],"fullName":"normalizePath handles empty string","status":"passed","title":"handles empty string","duration":0.19680000000005293,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizePath"],"fullName":"normalizePath doesn't collapse consecutive slashes (by design)","status":"passed","title":"doesn't collapse consecutive slashes (by design)","duration":0.21170000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath"],"fullName":"joinPath joins simple segments with forward slashes","status":"passed","title":"joins simple segments with forward slashes","duration":0.265199999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath"],"fullName":"joinPath collapses duplicate slashes at the join boundary","status":"passed","title":"collapses duplicate slashes at the join boundary","duration":0.17250000000001364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath"],"fullName":"joinPath normalizes backslashes inside segments","status":"passed","title":"normalizes backslashes inside segments","duration":0.18439999999998236,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath"],"fullName":"joinPath preserves a leading slash","status":"passed","title":"preserves a leading slash","duration":0.20069999999998345,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["joinPath"],"fullName":"joinPath joins a single segment","status":"passed","title":"joins a single segment","duration":0.21820000000002437,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName"],"fullName":"getFileName returns the last segment of a POSIX path","status":"passed","title":"returns the last segment of a POSIX path","duration":0.1792000000000371,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName"],"fullName":"getFileName returns the last segment of a Windows path","status":"passed","title":"returns the last segment of a Windows path","duration":0.12730000000004793,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName"],"fullName":"getFileName returns the original string when no separators","status":"passed","title":"returns the original string when no separators","duration":0.13210000000003674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileName"],"fullName":"getFileName returns empty string when path ends with separator","status":"passed","title":"returns empty string when path ends with separator","duration":0.11239999999997963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileStem"],"fullName":"getFileStem strips a single extension","status":"passed","title":"strips a single extension","duration":0.151299999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileStem"],"fullName":"getFileStem strips only the LAST extension for double extensions","status":"passed","title":"strips only the LAST extension for double extensions","duration":0.11200000000002319,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileStem"],"fullName":"getFileStem returns the name unchanged for dotfiles (leading dot)","status":"passed","title":"returns the name unchanged for dotfiles (leading dot)","duration":0.10630000000003292,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileStem"],"fullName":"getFileStem works across separators","status":"passed","title":"works across separators","duration":0.1069999999999709,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getFileStem"],"fullName":"getFileStem handles names with no extension","status":"passed","title":"handles names with no extension","duration":0.11559999999997217,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getRelativePath"],"fullName":"getRelativePath strips the base prefix","status":"passed","title":"strips the base prefix","duration":0.1564999999999941,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getRelativePath"],"fullName":"getRelativePath handles base with trailing slash","status":"passed","title":"handles base with trailing slash","duration":0.11340000000001282,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getRelativePath"],"fullName":"getRelativePath normalizes separators on both sides","status":"passed","title":"normalizes separators on both sides","duration":0.16540000000003374,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getRelativePath"],"fullName":"getRelativePath returns the full path if base doesn't prefix it","status":"passed","title":"returns the full path if base doesn't prefix it","duration":0.11410000000000764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getRelativePath"],"fullName":"getRelativePath requires an exact segment boundary (not just prefix match)","status":"passed","title":"requires an exact segment boundary (not just prefix match)","duration":0.16270000000002938,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAbsolutePath"],"fullName":"isAbsolutePath recognizes Unix absolute paths","status":"passed","title":"recognizes Unix absolute paths","duration":0.206799999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAbsolutePath"],"fullName":"isAbsolutePath recognizes Windows drive-letter paths with both separators","status":"passed","title":"recognizes Windows drive-letter paths with both separators","duration":0.6116999999999848,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAbsolutePath"],"fullName":"isAbsolutePath recognizes Windows UNC paths","status":"passed","title":"recognizes Windows UNC paths","duration":0.17300000000000182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAbsolutePath"],"fullName":"isAbsolutePath rejects relative paths","status":"passed","title":"rejects relative paths","duration":0.2378999999999678,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAbsolutePath"],"fullName":"isAbsolutePath rejects drive-letter WITHOUT a separator (ambiguous)","status":"passed","title":"rejects drive-letter WITHOUT a separator (ambiguous)","duration":0.11259999999998627,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701911,"endTime":1781738701919.2378,"status":"passed","message":"","name":"E:/QMAI/src/lib/path-utils.test.ts"},{"assertionResults":[{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip save then load returns identical items","status":"passed","title":"save then load returns identical items","duration":13.479399999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip creates the .qmai directory on first save","status":"passed","title":"creates the .qmai directory on first save","duration":5.286299999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip returns empty array when the file is absent","status":"passed","title":"returns empty array when the file is absent","duration":1.477800000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip returns empty array when the file is corrupted JSON","status":"passed","title":"returns empty array when the file is corrupted JSON","duration":14.893500000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip preserves Unicode titles through JSON round-trip","status":"passed","title":"preserves Unicode titles through JSON round-trip","duration":14.910699999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip overwrites existing file on subsequent saves","status":"passed","title":"overwrites existing file on subsequent saves","duration":9.5034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review persistence — round-trip"],"fullName":"review persistence — round-trip normalizes Windows-style paths (backslashes) in projectPath","status":"passed","title":"normalizes Windows-style paths (backslashes) in projectPath","duration":5.061300000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) writes conversations.json + per-conversation chats/<id>.json","status":"passed","title":"writes conversations.json + per-conversation chats/<id>.json","duration":7.678399999999954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) round-trips conversations + messages","status":"passed","title":"round-trips conversations + messages","duration":13.956999999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) caps each conversation's persisted messages at 100 (oldest dropped)","status":"passed","title":"caps each conversation's persisted messages at 100 (oldest dropped)","duration":9.198300000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) returns empty data when no persistence file exists","status":"passed","title":"returns empty data when no persistence file exists","duration":1.855799999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) skips missing per-conversation files without throwing","status":"passed","title":"skips missing per-conversation files without throwing","duration":5.599100000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — round-trip (new format)"],"fullName":"chat persistence — round-trip (new format) preserves Unicode content through round-trip","status":"passed","title":"preserves Unicode content through round-trip","duration":5.274000000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — .qmai legacy file-format fallback"],"fullName":"chat persistence — .qmai legacy file-format fallback falls back to chat-history.json flat-array format","status":"passed","title":"falls back to chat-history.json flat-array format","duration":3.783799999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — .qmai legacy file-format fallback"],"fullName":"chat persistence — .qmai legacy file-format fallback falls back to chat-history.json combined-object format","status":"passed","title":"falls back to chat-history.json combined-object format","duration":3.3555000000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat persistence — .qmai legacy file-format fallback"],"fullName":"chat persistence — .qmai legacy file-format fallback new format wins over legacy when both exist","status":"passed","title":"new format wins over legacy when both exist","duration":5.871899999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697964,"endTime":1781738698085.8718,"status":"passed","message":"","name":"E:/QMAI/src/lib/persist.integration.test.ts"},{"assertionResults":[{"ancestorTitles":["project file sync"],"fullName":"project file sync does not apply a stale start result after the active project changes","status":"passed","title":"does not apply a stale start result after the active project changes","duration":551.809,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync enqueues created and modified raw source files for ingest","status":"passed","title":"enqueues created and modified raw source files for ingest","duration":73.11009999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync does not ingest preprocessed cache files from raw/sources/.cache","status":"passed","title":"does not ingest preprocessed cache files from raw/sources/.cache","duration":68.40200000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync manual rescan uses the same source ingest flow when the watcher is stopped","status":"passed","title":"manual rescan uses the same source ingest flow when the watcher is stopped","duration":5.549299999999903,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync manual rescan uses returned changed tasks while the watcher is running","status":"passed","title":"manual rescan uses returned changed tasks while the watcher is running","duration":53.268100000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync manual rescan ignores changed tasks after project switch","status":"passed","title":"manual rescan ignores changed tasks after project switch","duration":0.9144999999998618,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync manual rescan refreshes the file tree when no files changed","status":"passed","title":"manual rescan refreshes the file tree when no files changed","duration":0.7579000000000633,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync manual rescan respects auto ingest disabled","status":"passed","title":"manual rescan respects auto ingest disabled","duration":0.8247999999998683,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync removes an externally deleted raw source from every wiki page sources field","status":"passed","title":"removes an externally deleted raw source from every wiki page sources field","duration":63.990600000000086,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync cascades delete for wiki pages whose only source was externally deleted","status":"passed","title":"cascades delete for wiki pages whose only source was externally deleted","duration":60.587200000000166,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["project file sync"],"fullName":"project file sync processes external batch source deletion with one wiki scan and one cascade","status":"passed","title":"processes external batch source deletion with one wiki scan and one cascade","duration":62.93939999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738696627,"endTime":1781738697569.9395,"status":"passed","message":"","name":"E:/QMAI/src/lib/project-file-sync.test.ts"},{"assertionResults":[{"ancestorTitles":["withProjectLock — same-project serialization"],"fullName":"withProjectLock — same-project serialization two calls for the same project run in order, second waits for first","status":"passed","title":"two calls for the same project run in order, second waits for first","duration":4.2003999999999735,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withProjectLock — same-project serialization"],"fullName":"withProjectLock — same-project serialization propagates exceptions from fn AND still releases the lock","status":"passed","title":"propagates exceptions from fn AND still releases the lock","duration":2.300700000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withProjectLock — same-project serialization"],"fullName":"withProjectLock — same-project serialization a third caller chained onto a still-pending second waits behind both","status":"passed","title":"a third caller chained onto a still-pending second waits behind both","duration":0.6994000000000256,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withProjectLock — cross-project parallelism"],"fullName":"withProjectLock — cross-project parallelism calls for different projectPaths run concurrently (no shared lock)","status":"passed","title":"calls for different projectPaths run concurrently (no shared lock)","duration":1.7314999999999827,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701815,"endTime":1781738701824.7314,"status":"passed","message":"","name":"E:/QMAI/src/lib/project-mutex.test.ts"},{"assertionResults":[{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence round-trip save then load returns identical config","status":"passed","title":"round-trip save then load returns identical config","duration":14.72040000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence persists to .qmai/novel-config.json","status":"passed","title":"persists to .qmai/novel-config.json","duration":6.651299999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence returns null when neither file nor store has data","status":"passed","title":"returns null when neither file nor store has data","duration":2.394999999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence reinstall recovery: loads from project dir when global store is empty","status":"passed","title":"reinstall recovery: loads from project dir when global store is empty","duration":6.1358000000000175,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence project dir file wins over global store (reinstall scenario)","status":"passed","title":"project dir file wins over global store (reinstall scenario)","duration":4.610700000000065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence migrates: when loaded from global store, writes to project dir","status":"passed","title":"migrates: when loaded from global store, writes to project dir","duration":4.740000000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelConfig — project-directory persistence"],"fullName":"novelConfig — project-directory persistence fills in autoExtractOnImport when loading legacy novel config","status":"passed","title":"fills in autoExtractOnImport when loading legacy novel config","duration":6.703399999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence round-trip save then load returns identical config","status":"passed","title":"round-trip save then load returns identical config","duration":4.791899999999941,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence persists to .qmai/revision-feedback-config.json","status":"passed","title":"persists to .qmai/revision-feedback-config.json","duration":3.129599999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence returns defaults when neither file nor store has data","status":"passed","title":"returns defaults when neither file nor store has data","duration":3.511500000000069,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence reinstall recovery: loads from project dir when global store is empty","status":"passed","title":"reinstall recovery: loads from project dir when global store is empty","duration":4.7473999999999705,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence project dir file wins over global store","status":"passed","title":"project dir file wins over global store","duration":3.466099999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["revisionFeedbackWindowConfig — project-directory persistence"],"fullName":"revisionFeedbackWindowConfig — project-directory persistence migrates: when loaded from global store, writes to project dir","status":"passed","title":"migrates: when loaded from global store, writes to project dir","duration":5.214500000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence round-trip save then load returns identical config","status":"passed","title":"round-trip save then load returns identical config","duration":5.926600000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence persists to .qmai/source-watch-config.json","status":"passed","title":"persists to .qmai/source-watch-config.json","duration":3.6013000000000375,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence returns defaults when neither file nor store has data","status":"passed","title":"returns defaults when neither file nor store has data","duration":3.2117999999999256,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence reinstall recovery: loads from project dir when global store is empty","status":"passed","title":"reinstall recovery: loads from project dir when global store is empty","duration":3.2364000000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence project dir file wins over global store","status":"passed","title":"project dir file wins over global store","duration":3.3210000000000264,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sourceWatchConfig — project-directory persistence"],"fullName":"sourceWatchConfig — project-directory persistence migrates: when loaded from global store, writes to project dir","status":"passed","title":"migrates: when loaded from global store, writes to project dir","duration":5.3554000000000315,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerankConfig persistence"],"fullName":"rerankConfig persistence round-trip save then load returns identical config","status":"passed","title":"round-trip save then load returns identical config","duration":4.516300000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerankConfig persistence"],"fullName":"rerankConfig persistence persists to .qmai/rerank-config.json","status":"passed","title":"persists to .qmai/rerank-config.json","duration":4.290300000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerankConfig persistence"],"fullName":"rerankConfig persistence reinstall recovery: loads from project dir when global store is empty","status":"passed","title":"reinstall recovery: loads from project dir when global store is empty","duration":4.766899999999964,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738696982,"endTime":1781738697091.7668,"status":"passed","message":"","name":"E:/QMAI/src/lib/project-store.integration.test.ts"},{"assertionResults":[{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl accepts http:// URLs","status":"passed","title":"accepts http:// URLs","duration":2.939900000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl accepts https:// URLs","status":"passed","title":"accepts https:// URLs","duration":0.31929999999999836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl accepts URLs with embedded auth","status":"passed","title":"accepts URLs with embedded auth","duration":0.328899999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl rejects URLs with no scheme","status":"passed","title":"rejects URLs with no scheme","duration":0.4341000000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl rejects unsupported schemes (v1: HTTP/HTTPS only)","status":"passed","title":"rejects unsupported schemes (v1: HTTP/HTTPS only)","duration":0.9910999999999603,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl rejects empty / whitespace input","status":"passed","title":"rejects empty / whitespace input","duration":0.3562999999999761,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl rejects malformed URLs","status":"passed","title":"rejects malformed URLs","duration":0.38769999999999527,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateProxyUrl"],"fullName":"validateProxyUrl rejects URLs missing a host","status":"passed","title":"rejects URLs missing a host","duration":0.281599999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildNoProxyValue"],"fullName":"buildNoProxyValue returns the default bypass list when bypassLocal is true","status":"passed","title":"returns the default bypass list when bypassLocal is true","duration":0.28460000000001173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildNoProxyValue"],"fullName":"buildNoProxyValue returns null when bypassLocal is false (no NO_PROXY env var should be set)","status":"passed","title":"returns null when bypassLocal is false (no NO_PROXY env var should be set)","duration":0.2995000000000232,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildNoProxyValue"],"fullName":"buildNoProxyValue default list covers the standard private networks and localhost","status":"passed","title":"default list covers the standard private networks and localhost","duration":0.4479000000000042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isProxyActive"],"fullName":"isProxyActive is false when disabled","status":"passed","title":"is false when disabled","duration":0.2056999999999789,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isProxyActive"],"fullName":"isProxyActive is false when enabled but URL is empty / whitespace","status":"passed","title":"is false when enabled but URL is empty / whitespace","duration":0.18319999999999936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isProxyActive"],"fullName":"isProxyActive is false when enabled and URL is malformed","status":"passed","title":"is false when enabled and URL is malformed","duration":0.23380000000003065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isProxyActive"],"fullName":"isProxyActive is true when enabled with a valid http(s) URL","status":"passed","title":"is true when enabled with a valid http(s) URL","duration":0.2004000000000019,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702031,"endTime":1781738702039.234,"status":"passed","message":"","name":"E:/QMAI/src/lib/proxy-config.test.ts"},{"assertionResults":[{"ancestorTitles":["reasoning detector"],"fullName":"reasoning detector extracts OpenAI Responses reasoning summary deltas","status":"passed","title":"extracts OpenAI Responses reasoning summary deltas","duration":3.244799999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["reasoning detector"],"fullName":"reasoning detector extracts OpenAI Responses reasoning text deltas","status":"passed","title":"extracts OpenAI Responses reasoning text deltas","duration":0.37870000000000914,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703980,"endTime":1781738703983.3787,"status":"passed","message":"","name":"E:/QMAI/src/lib/reasoning-detector.spec.ts"},{"assertionResults":[{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine returns 0 for empty / non-stream lines","status":"passed","title":"returns 0 for empty / non-stream lines","duration":3.0983999999999696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine returns 0 when only `content` is present (the normal happy path)","status":"passed","title":"returns 0 when only `content` is present (the normal happy path)","duration":0.3189000000000419,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine counts chars in `reasoning_content` (DeepSeek-R1 / Kimi K2.6 convention)","status":"passed","title":"counts chars in `reasoning_content` (DeepSeek-R1 / Kimi K2.6 convention)","duration":0.26839999999998554,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine counts chars in `reasoning` (the user's Qwen3.5-122B endpoint)","status":"passed","title":"counts chars in `reasoning` (the user's Qwen3.5-122B endpoint)","duration":0.22399999999998954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine doesn't double-count `reasoning_content` as `reasoning`","status":"passed","title":"doesn't double-count `reasoning_content` as `reasoning`","duration":0.303100000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine ignores other fields whose names contain `reasoning` substring","status":"passed","title":"ignores other fields whose names contain `reasoning` substring","duration":0.22919999999999163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine handles JSON string escapes in the reasoning text","status":"passed","title":"handles JSON string escapes in the reasoning text","duration":0.26720000000000255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine returns 0 for malformed JSON without crashing","status":"passed","title":"returns 0 for malformed JSON without crashing","duration":0.5246999999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine works on a real Kimi K2.6 chunk (reasoning_content path)","status":"passed","title":"works on a real Kimi K2.6 chunk (reasoning_content path)","duration":0.2958000000000425,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["countReasoningCharsInLine"],"fullName":"countReasoningCharsInLine works on a real Qwen3.5 chunk (the user's reported case)","status":"passed","title":"works on a real Qwen3.5 chunk (the user's reported case)","duration":0.45769999999998845,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractReasoningTextFromLine"],"fullName":"extractReasoningTextFromLine extracts OpenAI-compatible reasoning_content chunks","status":"passed","title":"extracts OpenAI-compatible reasoning_content chunks","duration":1.0072999999999865,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractReasoningTextFromLine"],"fullName":"extractReasoningTextFromLine extracts Qwen-style reasoning chunks","status":"passed","title":"extracts Qwen-style reasoning chunks","duration":0.2418999999999869,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractReasoningTextFromLine"],"fullName":"extractReasoningTextFromLine extracts Anthropic thinking deltas","status":"passed","title":"extracts Anthropic thinking deltas","duration":0.2366000000000099,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractReasoningTextFromLine"],"fullName":"extractReasoningTextFromLine extracts Gemini thought parts","status":"passed","title":"extracts Gemini thought parts","duration":0.20920000000000982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractReasoningTextFromLine"],"fullName":"extractReasoningTextFromLine ignores visible content","status":"passed","title":"ignores visible content","duration":0.1642999999999688,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702567,"endTime":1781738702575.242,"status":"passed","message":"","name":"E:/QMAI/src/lib/reasoning-detector.test.ts"},{"assertionResults":[{"ancestorTitles":["rerank-api"],"fullName":"rerank-api recognizes direct rerank endpoints","status":"passed","title":"recognizes direct rerank endpoints","duration":2.79159999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerank-api"],"fullName":"rerank-api posts to the direct rerank endpoint and returns ordered indices","status":"passed","title":"posts to the direct rerank endpoint and returns ordered indices","duration":4.449999999999989,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702210,"endTime":1781738702217.45,"status":"passed","message":"","name":"E:/QMAI/src/lib/rerank-api.test.ts"},{"assertionResults":[{"ancestorTitles":["rerankCandidates"],"fullName":"rerankCandidates returns original order when rerank is disabled","status":"passed","title":"returns original order when rerank is disabled","duration":4.330299999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerankCandidates"],"fullName":"rerankCandidates reorders candidates according to model JSON output","status":"passed","title":"reorders candidates according to model JSON output","duration":1.33239999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["rerankCandidates"],"fullName":"rerankCandidates uses the direct rerank endpoint when configured","status":"passed","title":"uses the direct rerank endpoint when configured","duration":0.9352000000000089,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702787,"endTime":1781738702793.9353,"status":"passed","message":"","name":"E:/QMAI/src/lib/rerank.test.ts"},{"assertionResults":[{"ancestorTitles":["resetProjectState — Zustand stores"],"fullName":"resetProjectState — Zustand stores clears chat store conversations and messages","status":"passed","title":"clears chat store conversations and messages","duration":275.226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — Zustand stores"],"fullName":"resetProjectState — Zustand stores clears review store items","status":"passed","title":"clears review store items","duration":0.8215000000000146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — Zustand stores"],"fullName":"resetProjectState — Zustand stores clears activity store items","status":"passed","title":"clears activity store items","duration":0.6003000000000611,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — module-level caches are awaited"],"fullName":"resetProjectState — module-level caches are awaited calls pauseQueue before the returned promise resolves","status":"passed","title":"calls pauseQueue before the returned promise resolves","duration":0.6177999999999884,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — module-level caches are awaited"],"fullName":"resetProjectState — module-level caches are awaited calls clearGraphCache before the returned promise resolves","status":"passed","title":"calls clearGraphCache before the returned promise resolves","duration":0.47309999999993124,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — module-level caches are awaited"],"fullName":"resetProjectState — module-level caches are awaited ordering: when resolve() fires, BOTH module caches are already cleared","status":"passed","title":"ordering: when resolve() fires, BOTH module caches are already cleared","duration":0.46850000000006276,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — module-level caches are awaited"],"fullName":"resetProjectState — module-level caches are awaited does not throw when pauseQueue itself throws — logs and continues","status":"passed","title":"does not throw when pauseQueue itself throws — logs and continues","duration":1.9573999999998932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — module-level caches are awaited"],"fullName":"resetProjectState — module-level caches are awaited does not throw when clearGraphCache itself throws","status":"passed","title":"does not throw when clearGraphCache itself throws","duration":0.6949000000000751,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resetProjectState — leaves unrelated store keys alone"],"fullName":"resetProjectState — leaves unrelated store keys alone preserves maxHistoryMessages on chat store (config, not project data)","status":"passed","title":"preserves maxHistoryMessages on chat store (config, not project data)","duration":0.515700000000038,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697610,"endTime":1781738697891.5156,"status":"passed","message":"","name":"E:/QMAI/src/lib/reset-project-state.test.ts"},{"assertionResults":[{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan parses multiple original/replacement pairs from model JSON","status":"passed","title":"parses multiple original/replacement pairs from model JSON","duration":4.8817000000000235,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan applies multiple edits only when each original text can be located","status":"passed","title":"applies multiple edits only when each original text can be located","duration":5.627499999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan reports unapplied edits instead of silently changing the wrong text","status":"passed","title":"reports unapplied edits instead of silently changing the wrong text","duration":0.627900000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan does not replace a partial evidence fragment when the full original text is missing","status":"passed","title":"does not replace a partial evidence fragment when the full original text is missing","duration":0.39620000000002165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan locates original text when line breaks or spacing differ between the model output and markdown","status":"passed","title":"locates original text when line breaks or spacing differ between the model output and markdown","duration":0.424000000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review rewrite plan"],"fullName":"review rewrite plan does not guess when the same original text appears more than once","status":"passed","title":"does not guess when the same original text appears more than once","duration":0.26389999999997826,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701112,"endTime":1781738701124.424,"status":"passed","message":"","name":"E:/QMAI/src/lib/review-rewrite-plan.spec.ts"},{"assertionResults":[{"ancestorTitles":["normalizeReviewTitle — properties"],"fullName":"normalizeReviewTitle — properties is idempotent: normalize(normalize(x)) === normalize(x)","status":"passed","title":"is idempotent: normalize(normalize(x)) === normalize(x)","duration":14.828199999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle — properties"],"fullName":"normalizeReviewTitle — properties is lowercase: result never contains uppercase ASCII letters","status":"passed","title":"is lowercase: result never contains uppercase ASCII letters","duration":4.791499999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle — properties"],"fullName":"normalizeReviewTitle — properties collapses any run of whitespace to a single space","status":"passed","title":"collapses any run of whitespace to a single space","duration":5.706299999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle — properties"],"fullName":"normalizeReviewTitle — properties is monotonic in prefix-stripping: stripping a recognized prefix yields same result as no prefix","status":"passed","title":"is monotonic in prefix-stripping: stripping a recognized prefix yields same result as no prefix","duration":6.301300000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle — properties"],"fullName":"normalizeReviewTitle — properties merges variant-prefix titles to the same key","status":"passed","title":"merges variant-prefix titles to the same key","duration":7.954899999999952,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699761,"endTime":1781738699800.9548,"status":"passed","message":"","name":"E:/QMAI/src/lib/review-utils.property.test.ts"},{"assertionResults":[{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle returns the title lowercased when no prefix","status":"passed","title":"returns the title lowercased when no prefix","duration":2.649400000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips English 'Missing page:' prefix","status":"passed","title":"strips English 'Missing page:' prefix","duration":0.392599999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips hyphenated 'Missing-Page:' prefix","status":"passed","title":"strips hyphenated 'Missing-Page:' prefix","duration":0.23490000000003874,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips Chinese '缺失页面:' prefix (full-width colon)","status":"passed","title":"strips Chinese '缺失页面:' prefix (full-width colon)","duration":0.37880000000001246,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips Chinese '缺失页面:' prefix (half-width colon)","status":"passed","title":"strips Chinese '缺失页面:' prefix (half-width colon)","duration":0.24130000000002383,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips alternative '缺少页面:' prefix","status":"passed","title":"strips alternative '缺少页面:' prefix","duration":0.19580000000001974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips English 'Duplicate page:' prefix","status":"passed","title":"strips English 'Duplicate page:' prefix","duration":0.2437999999999647,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips Chinese '重复页面:' prefix","status":"passed","title":"strips Chinese '重复页面:' prefix","duration":0.1963000000000079,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle strips 'Possible duplicate:' prefix","status":"passed","title":"strips 'Possible duplicate:' prefix","duration":0.21629999999998972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle collapses internal whitespace","status":"passed","title":"collapses internal whitespace","duration":0.2615999999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle is case-insensitive on the prefix match","status":"passed","title":"is case-insensitive on the prefix match","duration":0.2547000000000139,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle considers two variant-prefixed titles equal after normalization","status":"passed","title":"considers two variant-prefixed titles equal after normalization","duration":0.158299999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle handles empty string","status":"passed","title":"handles empty string","duration":0.14239999999995234,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle handles only-prefix input","status":"passed","title":"handles only-prefix input","duration":0.12250000000000227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle preserves colons inside the title body (not as prefix)","status":"passed","title":"preserves colons inside the title body (not as prefix)","duration":0.12250000000000227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeReviewTitle"],"fullName":"normalizeReviewTitle only strips ONE prefix occurrence (no recursive stripping)","status":"passed","title":"only strips ONE prefix occurrence (no recursive stripping)","duration":0.12229999999999563,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702269,"endTime":1781738702275.2546,"status":"passed","message":"","name":"E:/QMAI/src/lib/review-utils.test.ts"},{"assertionResults":[{"ancestorTitles":["scheduled import path handling"],"fullName":"scheduled import path handling resolves relative paths from the project root","status":"passed","title":"resolves relative paths from the project root","duration":3.0002999999999815,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scheduled import path handling"],"fullName":"scheduled import path handling preserves nested relative paths for external directories","status":"passed","title":"preserves nested relative paths for external directories","duration":0.7064000000000306,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scheduled import path handling"],"fullName":"scheduled import path handling does not copy files that are already under raw/sources","status":"passed","title":"does not copy files that are already under raw/sources","duration":0.31579999999996744,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scheduled import path handling"],"fullName":"scheduled import path handling sanitizes Windows-unsafe destination path segments with a stable suffix","status":"passed","title":"sanitizes Windows-unsafe destination path segments with a stable suffix","duration":0.472500000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scheduled import path handling"],"fullName":"scheduled import path handling skips project internals and generated wiki/cache files","status":"passed","title":"skips project internals and generated wiki/cache files","duration":0.4608000000000061,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scanAndImport failure handling"],"fullName":"scanAndImport failure handling does not mark changed files imported when enqueue fails","status":"passed","title":"does not mark changed files imported when enqueue fails","duration":7.242799999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scanAndImport failure handling"],"fullName":"scanAndImport failure handling continues scanning when one file is locked or unreadable","status":"passed","title":"continues scanning when one file is locked or unreadable","duration":3.64609999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["scanAndImport failure handling"],"fullName":"scanAndImport failure handling skips large files before hashing or copying","status":"passed","title":"skips large files before hashing or copying","duration":0.7975000000000136,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700669,"endTime":1781738700686.7976,"status":"passed","message":"","name":"E:/QMAI/src/lib/scheduled-import.test.ts"},{"assertionResults":[{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists a vector-only match (no keyword overlap) surfaces ahead of a token-only weak match","status":"passed","title":"a vector-only match (no keyword overlap) surfaces ahead of a token-only weak match","duration":24.80709999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists a page in BOTH lists outranks a page in only one (combined contribution)","status":"passed","title":"a page in BOTH lists outranks a page in only one (combined contribution)","duration":27.021299999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists vector list is empty (embedding disabled) → behaves like pure token rank","status":"passed","title":"vector list is empty (embedding disabled) → behaves like pure token rank","duration":7.956900000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists skips vector retrieval entirely when includeVector is false","status":"passed","title":"skips vector retrieval entirely when includeVector is false","duration":6.669899999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists token list empty (no keyword match anywhere) → vector ranks alone determine order","status":"passed","title":"token list empty (no keyword match anywhere) → vector ranks alone determine order","duration":10.455099999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists vector contribution is rank-based, NOT score-magnitude — fixes the score-dwarfing bug","status":"passed","title":"vector contribution is rank-based, NOT score-magnitude — fixes the score-dwarfing bug","duration":8.820100000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["searchWiki — RRF fusion of token + vector lists"],"fullName":"searchWiki — RRF fusion of token + vector lists vector results referencing nonexistent page ids are silently dropped (post-source-delete safety)","status":"passed","title":"vector results referencing nonexistent page ids are silently dropped (post-source-delete safety)","duration":12.18769999999995,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698007,"endTime":1781738698105.1877,"status":"passed","message":"","name":"E:/QMAI/src/lib/search-rrf.test.ts"},{"assertionResults":[{"ancestorTitles":["search-window helper"],"fullName":"search-window helper declares the standalone search window label and bridge events","status":"passed","title":"declares the standalone search window label and bridge events","duration":2.33239999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search-window helper"],"fullName":"search-window helper reuses an existing search window before creating a new one","status":"passed","title":"reuses an existing search window before creating a new one","duration":0.393100000000004,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704629,"endTime":1781738704631.393,"status":"passed","message":"","name":"E:/QMAI/src/lib/search-window.test.ts"},{"assertionResults":[{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) title-exact-match","status":"passed","title":"title-exact-match","duration":17.654899999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) content-match","status":"passed","title":"content-match","duration":7.332100000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) cjk-bigram","status":"passed","title":"cjk-bigram","duration":6.982200000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) multi-token-ranking","status":"passed","title":"multi-token-ranking","duration":7.073599999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) stop-word-filtered","status":"passed","title":"stop-word-filtered","duration":9.642499999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) filename-exact-match-wins","status":"passed","title":"filename-exact-match-wins","duration":9.915899999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) phrase-in-content-beats-scattered-tokens","status":"passed","title":"phrase-in-content-beats-scattered-tokens","duration":6.304700000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["search scenarios (fixture-driven)"],"fullName":"search scenarios (fixture-driven) trailing-punct-phrase-bonus","status":"passed","title":"trailing-punct-phrase-bonus","duration":7.4809999999999945,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698608,"endTime":1781738698680.481,"status":"passed","message":"","name":"E:/QMAI/src/lib/search.scenarios.test.ts"},{"assertionResults":[{"ancestorTitles":["settings model list"],"fullName":"settings model list fetches custom OpenAI-compatible models from the normalized /models endpoint","status":"passed","title":"fetches custom OpenAI-compatible models from the normalized /models endpoint","duration":153.1563,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model list"],"fullName":"settings model list retries model list 403 responses with browser-compatible OpenAI headers","status":"passed","title":"retries model list 403 responses with browser-compatible OpenAI headers","duration":1.8926000000000158,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model list"],"fullName":"settings model list keeps the original 403 diagnostic when the compatibility retry cannot be sent","status":"passed","title":"keeps the original 403 diagnostic when the compatibility retry cannot be sent","duration":2.425200000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model list"],"fullName":"settings model list reads the configured local Claude CLI model from Tauri detection","status":"passed","title":"reads the configured local Claude CLI model from Tauri detection","duration":0.6861000000000104,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model list"],"fullName":"settings model list reads the configured local Codex CLI model from Tauri detection","status":"passed","title":"reads the configured local Codex CLI model from Tauri detection","duration":0.5588000000000193,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698500,"endTime":1781738698659.5588,"status":"passed","message":"","name":"E:/QMAI/src/lib/settings-model-list.spec.ts"},{"assertionResults":[{"ancestorTitles":["settings-model-list"],"fullName":"settings-model-list awaits the fetch factory before requesting llm models","status":"passed","title":"awaits the fetch factory before requesting llm models","duration":20.11239999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings-model-list"],"fullName":"settings-model-list fetches embedding models from an embeddings endpoint","status":"passed","title":"fetches embedding models from an embeddings endpoint","duration":1.4670999999999594,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings-model-list"],"fullName":"settings-model-list parses google model lists from the models field","status":"passed","title":"parses google model lists from the models field","duration":1.1094000000000506,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings-model-list"],"fullName":"settings-model-list fetches rerank models from a direct rerank endpoint","status":"passed","title":"fetches rerank models from a direct rerank endpoint","duration":1.0113999999999805,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700068,"endTime":1781738700092.0115,"status":"passed","message":"","name":"E:/QMAI/src/lib/settings-model-list.test.ts"},{"assertionResults":[{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers tests llm models with a real chat request","status":"passed","title":"tests llm models with a real chat request","duration":4.3034000000000106,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers tests embedding models with a real embedding request","status":"passed","title":"tests embedding models with a real embedding request","duration":0.9150999999999954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers tests rerank models with structured JSON output","status":"passed","title":"tests rerank models with structured JSON output","duration":1.1521999999999935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers reuses the main model when rerank is configured to use it","status":"passed","title":"reuses the main model when rerank is configured to use it","duration":0.6326999999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers rejects embedding models for rerank tests before sending the request","status":"passed","title":"rejects embedding models for rerank tests before sending the request","duration":2.504099999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model test helpers"],"fullName":"settings model test helpers tests direct rerank endpoints without using chat completions","status":"passed","title":"tests direct rerank endpoints without using chat completions","duration":0.619700000000023,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701681,"endTime":1781738701691.6196,"status":"passed","message":"","name":"E:/QMAI/src/lib/settings-model-test.test.ts"},{"assertionResults":[{"ancestorTitles":["decidePageFate — single-source page"],"fullName":"decidePageFate — single-source page deletes when the sole source is the one being removed","status":"passed","title":"deletes when the sole source is the one being removed","duration":2.813600000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — single-source page"],"fullName":"decidePageFate — single-source page SKIPS when the sole source is NOT the one being removed (Bug #1 fix)","status":"passed","title":"SKIPS when the sole source is NOT the one being removed (Bug #1 fix)","duration":0.3772000000000162,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — single-source page"],"fullName":"decidePageFate — single-source page matches case-insensitively when deciding to delete","status":"passed","title":"matches case-insensitively when deciding to delete","duration":0.3439999999999941,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — multi-source page"],"fullName":"decidePageFate — multi-source page keeps and drops the deleted source from the list","status":"passed","title":"keeps and drops the deleted source from the list","duration":0.2699000000000069,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — multi-source page"],"fullName":"decidePageFate — multi-source page preserves the order of surviving sources","status":"passed","title":"preserves the order of surviving sources","duration":0.26650000000000773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — multi-source page"],"fullName":"decidePageFate — multi-source page SKIPS when the deleted source is not in the multi-source list","status":"passed","title":"SKIPS when the deleted source is not in the multi-source list","duration":0.19409999999999172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — multi-source page"],"fullName":"decidePageFate — multi-source page matches case-insensitively AND strips all case variants on keep","status":"passed","title":"matches case-insensitively AND strips all case variants on keep","duration":0.24729999999999563,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — empty / edge inputs"],"fullName":"decidePageFate — empty / edge inputs skips a page with an empty sources list (no claim on this deletion)","status":"passed","title":"skips a page with an empty sources list (no claim on this deletion)","duration":0.20639999999997372,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — empty / edge inputs"],"fullName":"decidePageFate — empty / edge inputs skips when the deleting source is itself empty","status":"passed","title":"skips when the deleting source is itself empty","duration":0.269800000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — empty / edge inputs"],"fullName":"decidePageFate — empty / edge inputs handles a single-source list where the source is some other casing","status":"passed","title":"handles a single-source list where the source is some other casing","duration":0.2979000000000269,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decidePageFate — the full lifecycle of a shared page"],"fullName":"decidePageFate — the full lifecycle of a shared page walks from multi-source → multi-source → single-source → deleted across source deletions","status":"passed","title":"walks from multi-source → multi-source → single-source → deleted across source deletions","duration":0.3575000000000159,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702703,"endTime":1781738702709.3574,"status":"passed","message":"","name":"E:/QMAI/src/lib/source-delete-decision.test.ts"},{"assertionResults":[{"ancestorTitles":["source-lifecycle path helpers"],"fullName":"source-lifecycle path helpers does not treat preprocessed cache files as ingestable sources","status":"passed","title":"does not treat preprocessed cache files as ingestable sources","duration":3.082499999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source-lifecycle path helpers"],"fullName":"source-lifecycle path helpers derives folder context from absolute raw/sources paths without leaking the project prefix","status":"passed","title":"derives folder context from absolute raw/sources paths without leaking the project prefix","duration":0.4561999999999671,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source-lifecycle path helpers"],"fullName":"source-lifecycle path helpers 删除仅由某个原始来源支撑的大纲页时,会同步删除该来源文件并清理原始来源列表","status":"passed","title":"删除仅由某个原始来源支撑的大纲页时,会同步删除该来源文件并清理原始来源列表","duration":6.276099999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source-lifecycle path helpers"],"fullName":"source-lifecycle path helpers 导入文件时返回按路径映射的提取任务 ID","status":"passed","title":"导入文件时返回按路径映射的提取任务 ID","duration":1.2608000000000175,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source-lifecycle path helpers"],"fullName":"source-lifecycle path helpers 关闭自动提取时导入文件夹不会入队提取","status":"passed","title":"关闭自动提取时导入文件夹不会入队提取","duration":0.7700999999999567,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701152,"endTime":1781738701163.77,"status":"passed","message":"","name":"E:/QMAI/src/lib/source-lifecycle.test.ts"},{"assertionResults":[{"ancestorTitles":["source watch config"],"fullName":"source watch config uses the shared default fixture","status":"passed","title":"uses the shared default fixture","duration":2.4788999999999533,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source watch config"],"fullName":"source watch config allows document types by default and rejects config/media/binaries","status":"passed","title":"allows document types by default and rejects config/media/binaries","duration":1.953200000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["source watch config"],"fullName":"source watch config applies directory and glob exclusions","status":"passed","title":"applies directory and glob exclusions","duration":0.5720999999999776,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702953,"endTime":1781738702957.572,"status":"passed","message":"","name":"E:/QMAI/src/lib/source-watch-config.test.ts"},{"assertionResults":[{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` extracts a single-entry array","status":"passed","title":"extracts a single-entry array","duration":2.939700000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` extracts a multi-entry array","status":"passed","title":"extracts a multi-entry array","duration":0.44310000000001537,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` handles single quotes","status":"passed","title":"handles single quotes","duration":0.27099999999995816,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` handles bare (unquoted) entries","status":"passed","title":"handles bare (unquoted) entries","duration":0.2907000000000153,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` handles no-space-after-comma","status":"passed","title":"handles no-space-after-comma","duration":0.23279999999999745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` returns empty array for empty brackets","status":"passed","title":"returns empty array for empty brackets","duration":0.19559999999995625,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` returns empty array when frontmatter has no sources field","status":"passed","title":"returns empty array when frontmatter has no sources field","duration":0.21410000000003038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` returns empty array when no frontmatter at all","status":"passed","title":"returns empty array when no frontmatter at all","duration":0.1922999999999888,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — inline `sources: [...]`"],"fullName":"parseSources — inline `sources: [...]` handles CJK-named source files","status":"passed","title":"handles CJK-named source files","duration":0.45650000000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — multi-line YAML list form"],"fullName":"parseSources — multi-line YAML list form extracts a multi-line list","status":"passed","title":"extracts a multi-line list","duration":0.4146999999999821,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseSources — multi-line YAML list form"],"fullName":"parseSources — multi-line YAML list form handles quoted multi-line entries","status":"passed","title":"handles quoted multi-line entries","duration":0.2255999999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources replaces an existing inline sources array","status":"passed","title":"replaces an existing inline sources array","duration":0.5836999999999648,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources preserves field order (sources stays in its original position)","status":"passed","title":"preserves field order (sources stays in its original position)","duration":0.24080000000003565,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources inserts a sources field when none exists","status":"passed","title":"inserts a sources field when none exists","duration":0.22550000000001091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources collapses multi-line YAML list form into inline form","status":"passed","title":"collapses multi-line YAML list form into inline form","duration":0.3926000000000158,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources returns content unchanged when there is no frontmatter","status":"passed","title":"returns content unchanged when there is no frontmatter","duration":0.22430000000002792,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSources"],"fullName":"writeSources preserves the body of the document","status":"passed","title":"preserves the body of the document","duration":0.26959999999996853,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists unions disjoint lists in order","status":"passed","title":"unions disjoint lists in order","duration":0.23050000000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists dedupes exact duplicates","status":"passed","title":"dedupes exact duplicates","duration":0.14560000000000173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists dedupes case-insensitively, keeping the existing casing","status":"passed","title":"dedupes case-insensitively, keeping the existing casing","duration":0.14350000000001728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists preserves order of existing entries","status":"passed","title":"preserves order of existing entries","duration":0.13510000000002265,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists is a no-op for empty+empty","status":"passed","title":"is a no-op for empty+empty","duration":0.13709999999997535,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists preserves existing when incoming is empty","status":"passed","title":"preserves existing when incoming is empty","duration":0.14599999999995816,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesLists"],"fullName":"mergeSourcesLists preserves incoming when existing is empty","status":"passed","title":"preserves incoming when existing is empty","duration":0.14359999999999218,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths returns newContent unchanged when page is new (no existingContent)","status":"passed","title":"returns newContent unchanged when page is new (no existingContent)","duration":0.21189999999995734,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths merges sources when the page already exists with a different source","status":"passed","title":"merges sources when the page already exists with a different source","duration":0.269800000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths idempotent: merging the same current source is a no-op on sources","status":"passed","title":"idempotent: merging the same current source is a no-op on sources","duration":0.206799999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths short-circuits (returns newContent verbatim) when old is a subset of new","status":"passed","title":"short-circuits (returns newContent verbatim) when old is a subset of new","duration":0.16390000000001237,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths handles case-insensitive source filenames (keeps original casing)","status":"passed","title":"handles case-insensitive source filenames (keeps original casing)","duration":0.169399999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — happy paths"],"fullName":"mergeSourcesIntoContent — happy paths appends the new source to an already multi-source page","status":"passed","title":"appends the new source to an already multi-source page","duration":0.18819999999999482,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — degenerate inputs"],"fullName":"mergeSourcesIntoContent — degenerate inputs is a no-op when existingContent has no sources field at all","status":"passed","title":"is a no-op when existingContent has no sources field at all","duration":0.17279999999999518,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — degenerate inputs"],"fullName":"mergeSourcesIntoContent — degenerate inputs is a no-op when existingContent is empty string","status":"passed","title":"is a no-op when existingContent is empty string","duration":0.12110000000001264,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — degenerate inputs"],"fullName":"mergeSourcesIntoContent — degenerate inputs tolerates newContent missing a sources field (LLM slipup)","status":"passed","title":"tolerates newContent missing a sources field (LLM slipup)","duration":0.19700000000000273,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeSourcesIntoContent — degenerate inputs"],"fullName":"mergeSourcesIntoContent — degenerate inputs tolerates existingContent with no frontmatter at all","status":"passed","title":"tolerates existingContent with no frontmatter at all","duration":0.15260000000000673,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Regression: the data-loss path in the user's diagnosis"],"fullName":"Regression: the data-loss path in the user's diagnosis two ingests against the same page result in union, not last-wins","status":"passed","title":"two ingests against the same page result in union, not last-wins","duration":0.35290000000003374,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Regression: the data-loss path in the user's diagnosis"],"fullName":"Regression: the data-loss path in the user's diagnosis three-way re-ingest: each ingest contributes its own source to the union","status":"passed","title":"three-way re-ingest: each ingest contributes its own source to the union","duration":0.35840000000001737,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray parses an inline-form array for any field name","status":"passed","title":"parses an inline-form array for any field name","duration":0.3242999999999938,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray parses block-form array (key: + - item lines) for any field","status":"passed","title":"parses block-form array (key: + - item lines) for any field","duration":0.2400000000000091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray strips quotes around items","status":"passed","title":"strips quotes around items","duration":0.277499999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray returns [] when the field doesn't exist","status":"passed","title":"returns [] when the field doesn't exist","duration":0.1459000000000401,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray returns [] when content has no frontmatter","status":"passed","title":"returns [] when content has no frontmatter","duration":0.1442000000000121,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseFrontmatterArray"],"fullName":"parseFrontmatterArray doesn't mistake one field for another with similar prefix","status":"passed","title":"doesn't mistake one field for another with similar prefix","duration":0.40629999999998745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent merges sources / tags / related as a union of old and new","status":"passed","title":"merges sources / tags / related as a union of old and new","duration":0.48910000000000764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent dedupes case-insensitively across all fields","status":"passed","title":"dedupes case-insensitively across all fields","duration":0.1624999999999659,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent preserves a field that exists in old but is missing from new","status":"passed","title":"preserves a field that exists in old but is missing from new","duration":0.16190000000000282,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent is a no-op when neither old nor new have any of the requested fields","status":"passed","title":"is a no-op when neither old nor new have any of the requested fields","duration":0.1330000000000382,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent returns newContent unchanged when existingContent is null (new page)","status":"passed","title":"returns newContent unchanged when existingContent is null (new page)","duration":0.13350000000002638,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent merges only the fields the caller asked for","status":"passed","title":"merges only the fields the caller asked for","duration":0.20920000000000982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["mergeArrayFieldsIntoContent"],"fullName":"mergeArrayFieldsIntoContent preserves source-merge backward compatibility when called for `sources` only","status":"passed","title":"preserves source-merge backward compatibility when called for `sources` only","duration":0.22469999999998436,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700589,"endTime":1781738700604.2246,"status":"passed","message":"","name":"E:/QMAI/src/lib/sources-merge.test.ts"},{"assertionResults":[{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot returns empty list for an empty folder","status":"passed","title":"returns empty list for an empty folder","duration":2.738400000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot returns all leaf files of a single-level folder","status":"passed","title":"returns all leaf files of a single-level folder","duration":0.4046000000000163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot recurses into subdirectories of arbitrary depth","status":"passed","title":"recurses into subdirectories of arbitrary depth","duration":0.4796999999999798,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot does not include directory nodes themselves in the result","status":"passed","title":"does not include directory nodes themselves in the result","duration":1.5004000000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot handles a directory with no children property (defensive)","status":"passed","title":"handles a directory with no children property (defensive)","duration":0.267599999999959,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["collectAllFilesIncludingDot"],"fullName":"collectAllFilesIncludingDot walks deeply-nested folder containing a single file 10 levels down","status":"passed","title":"walks deeply-nested folder containing a single file 10 levels down","duration":0.5628000000000384,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick arms the button on first click (no pending state)","status":"passed","title":"arms the button on first click (no pending state)","duration":1.7642999999999915,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick arms a different node when the pending path doesn't match","status":"passed","title":"arms a different node when the pending path doesn't match","duration":0.31330000000002656,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick fires file delete on second click of the same FILE node","status":"passed","title":"fires file delete on second click of the same FILE node","duration":0.297300000000007,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick fires folder delete on second click of the same FOLDER node","status":"passed","title":"fires folder delete on second click of the same FOLDER node","duration":0.3043999999999869,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick treats node identity by path, not object identity","status":"passed","title":"treats node identity by path, not object identity","duration":0.2411000000000172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideDeleteClick"],"fullName":"decideDeleteClick never returns a no-op — every click produces an actionable result","status":"passed","title":"never returns a no-op — every click produces an actionable result","duration":1.1091000000000122,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701959,"endTime":1781738701969.1091,"status":"passed","message":"","name":"E:/QMAI/src/lib/sources-tree-delete.test.ts"},{"assertionResults":[{"ancestorTitles":["extractJsonObject — properties"],"fullName":"extractJsonObject — properties extracts a bare JSON object unchanged (round-trip parses to same value)","status":"passed","title":"extracts a bare JSON object unchanged (round-trip parses to same value)","duration":56.817999999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject — properties"],"fullName":"extractJsonObject — properties extracts a fenced JSON object","status":"passed","title":"extracts a fenced JSON object","duration":39.308700000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject — properties"],"fullName":"extractJsonObject — properties finds a JSON object trailing after prose (no-brace prose)","status":"passed","title":"finds a JSON object trailing after prose (no-brace prose)","duration":32.66579999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject — properties"],"fullName":"extractJsonObject — properties returns a balanced {...} substring — depth returns to 0","status":"passed","title":"returns a balanced {...} substring — depth returns to 0","duration":23.718599999999924,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject — properties"],"fullName":"extractJsonObject — properties never throws, even on random garbage","status":"passed","title":"never throws, even on random garbage","duration":9.285300000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697224,"endTime":1781738697386.2854,"status":"passed","message":"","name":"E:/QMAI/src/lib/sweep-reviews.property.test.ts"},{"assertionResults":[{"ancestorTitles":["sweep race — project-identity guard"],"fullName":"sweep race — project-identity guard bails immediately if projectPath does not match current project","status":"passed","title":"bails immediately if projectPath does not match current project","duration":4.050799999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep race — project-identity guard"],"fullName":"sweep race — project-identity guard does not apply resolveItem if user switches projects mid-sweep (after buildWikiIndex)","status":"passed","title":"does not apply resolveItem if user switches projects mid-sweep (after buildWikiIndex)","duration":1.0688000000000102,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep race — project-identity guard"],"fullName":"sweep race — project-identity guard bails before LLM stage if project path mismatches when entering stage 2","status":"passed","title":"bails before LLM stage if project path mismatches when entering stage 2","duration":0.8768999999999778,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep race — abort signal"],"fullName":"sweep race — abort signal honors an abort signal that fires before the sweep starts","status":"passed","title":"honors an abort signal that fires before the sweep starts","duration":0.7869000000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep race — abort signal"],"fullName":"sweep race — abort signal does not invoke streamChat if aborted before stage 2","status":"passed","title":"does not invoke streamChat if aborted before stage 2","duration":0.5813999999999737,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep race — abort signal"],"fullName":"sweep race — abort signal signal aborted during streamChat — harness sees abort, sweep returns 0 LLM resolutions","status":"passed","title":"signal aborted during streamChat — harness sees abort, sweep returns 0 LLM resolutions","duration":1.8874000000000137,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — rule-based auto-resolution"],"fullName":"sweep — rule-based auto-resolution auto-resolves a missing-page review when the page now exists by filename","status":"passed","title":"auto-resolves a missing-page review when the page now exists by filename","duration":1.6447000000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — rule-based auto-resolution"],"fullName":"sweep — rule-based auto-resolution does not resolve when the page doesn't exist","status":"passed","title":"does not resolve when the page doesn't exist","duration":0.663599999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — LLM batch loop"],"fullName":"sweep — LLM batch loop processes pending items in batches of JUDGE_BATCH_SIZE=40 and breaks when a batch resolves nothing","status":"passed","title":"processes pending items in batches of JUDGE_BATCH_SIZE=40 and breaks when a batch resolves nothing","duration":1.7851,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — LLM batch loop"],"fullName":"sweep — LLM batch loop continues across batches when each resolves something","status":"passed","title":"continues across batches when each resolves something","duration":1.3704000000000178,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — LLM batch loop"],"fullName":"sweep — LLM batch loop caps at MAX_JUDGE_BATCHES=5 even if more batches would have content","status":"passed","title":"caps at MAX_JUDGE_BATCHES=5 even if more batches would have content","duration":2.277499999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — resolveItem safety after project switch"],"fullName":"sweep — resolveItem safety after project switch does not apply LLM results if project switched while LLM was running","status":"passed","title":"does not apply LLM results if project switched while LLM was running","duration":1.0027000000000044,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — empty / no-op cases"],"fullName":"sweep — empty / no-op cases returns 0 and does not touch the LLM when there are no pending items","status":"passed","title":"returns 0 and does not touch the LLM when there are no pending items","duration":0.4124999999999659,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep — empty / no-op cases"],"fullName":"sweep — empty / no-op cases returns 0 when LLM is not configured","status":"passed","title":"returns 0 when LLM is not configured","duration":0.42600000000004457,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700318,"endTime":1781738700337.426,"status":"passed","message":"","name":"E:/QMAI/src/lib/sweep-reviews.race.test.ts"},{"assertionResults":[{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/filename-match","status":"passed","title":"missing-page/filename-match","duration":18.50520000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/title-match","status":"passed","title":"missing-page/title-match","duration":8.157100000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/chinese-prefix-variant","status":"passed","title":"missing-page/chinese-prefix-variant","duration":8.426699999999983,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/not-resolved","status":"passed","title":"missing-page/not-resolved","duration":9.706999999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/case-insensitive","status":"passed","title":"missing-page/case-insensitive","duration":9.457099999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) missing-page/kebab-vs-space","status":"passed","title":"missing-page/kebab-vs-space","duration":14.31840000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) duplicate/one-page-deleted","status":"passed","title":"duplicate/one-page-deleted","duration":8.241599999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) duplicate/both-present","status":"passed","title":"duplicate/both-present","duration":19.331099999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) contradiction/never-auto-resolves","status":"passed","title":"contradiction/never-auto-resolves","duration":18.293000000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) suggestion/stays-pending-by-default","status":"passed","title":"suggestion/stays-pending-by-default","duration":14.133199999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) llm-judged/semantic-match","status":"passed","title":"llm-judged/semantic-match","duration":20.870000000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sweep scenarios (fixture-driven)"],"fullName":"sweep scenarios (fixture-driven) mixed-batch/partial-resolution","status":"passed","title":"mixed-batch/partial-resolution","duration":10.996899999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697169,"endTime":1781738697329.9968,"status":"passed","message":"","name":"E:/QMAI/src/lib/sweep-reviews.scenarios.test.ts"},{"assertionResults":[{"ancestorTitles":["extractJsonObject","bare JSON"],"fullName":"extractJsonObject bare JSON extracts a simple object","status":"passed","title":"extracts a simple object","duration":2.356300000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","bare JSON"],"fullName":"extractJsonObject bare JSON extracts an empty object","status":"passed","title":"extracts an empty object","duration":0.30819999999999936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","bare JSON"],"fullName":"extractJsonObject bare JSON extracts JSON with empty array","status":"passed","title":"extracts JSON with empty array","duration":0.19079999999996744,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","bare JSON"],"fullName":"extractJsonObject bare JSON preserves whitespace inside the object","status":"passed","title":"preserves whitespace inside the object","duration":0.2259000000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","markdown fences"],"fullName":"extractJsonObject markdown fences strips ```json ... ``` multi-line fence","status":"passed","title":"strips ```json ... ``` multi-line fence","duration":0.224899999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","markdown fences"],"fullName":"extractJsonObject markdown fences strips bare ``` ... ``` multi-line fence","status":"passed","title":"strips bare ``` ... ``` multi-line fence","duration":0.19490000000001828,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","markdown fences"],"fullName":"extractJsonObject markdown fences strips single-line ```json {...}``` fence","status":"passed","title":"strips single-line ```json {...}``` fence","duration":0.1815000000000282,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","markdown fences"],"fullName":"extractJsonObject markdown fences handles fences with surrounding whitespace","status":"passed","title":"handles fences with surrounding whitespace","duration":0.18700000000001182,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","markdown fences"],"fullName":"extractJsonObject markdown fences is case-insensitive on the 'json' language tag","status":"passed","title":"is case-insensitive on the 'json' language tag","duration":0.24279999999998836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","prose-wrapped JSON"],"fullName":"extractJsonObject prose-wrapped JSON finds JSON at the end of prose","status":"passed","title":"finds JSON at the end of prose","duration":0.3301000000000158,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","prose-wrapped JSON"],"fullName":"extractJsonObject prose-wrapped JSON returns the FIRST balanced object when prose has other braces before","status":"passed","title":"returns the FIRST balanced object when prose has other braces before","duration":0.2610000000000241,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","prose-wrapped JSON"],"fullName":"extractJsonObject prose-wrapped JSON handles JSON with nested objects","status":"passed","title":"handles JSON with nested objects","duration":0.27539999999999054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","string / escape handling"],"fullName":"extractJsonObject string / escape handling ignores braces inside string values","status":"passed","title":"ignores braces inside string values","duration":0.13880000000000337,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","string / escape handling"],"fullName":"extractJsonObject string / escape handling handles escaped quotes inside strings","status":"passed","title":"handles escaped quotes inside strings","duration":0.13159999999999172,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","string / escape handling"],"fullName":"extractJsonObject string / escape handling handles escaped backslash","status":"passed","title":"handles escaped backslash","duration":0.11849999999998317,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input returns empty string for no JSON at all","status":"passed","title":"returns empty string for no JSON at all","duration":0.12740000000002283,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input returns empty string for empty input","status":"passed","title":"returns empty string for empty input","duration":0.11850000000004002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input returns empty string for whitespace-only input","status":"passed","title":"returns empty string for whitespace-only input","duration":0.11629999999996699,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input returns empty string for unclosed object","status":"passed","title":"returns empty string for unclosed object","duration":0.11829999999997654,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input returns empty string when only opening brace","status":"passed","title":"returns empty string when only opening brace","duration":0.12250000000000227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","malformed input"],"fullName":"extractJsonObject malformed input handles a fence with no inner JSON","status":"passed","title":"handles a fence with no inner JSON","duration":0.12299999999999045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","realistic LLM responses"],"fullName":"extractJsonObject realistic LLM responses parses the expected fenced output from our prompt","status":"passed","title":"parses the expected fenced output from our prompt","duration":2.474800000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","realistic LLM responses"],"fullName":"extractJsonObject realistic LLM responses parses a bare response with no fence","status":"passed","title":"parses a bare response with no fence","duration":0.30239999999997735,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractJsonObject","realistic LLM responses"],"fullName":"extractJsonObject realistic LLM responses survives a chatty preamble","status":"passed","title":"survives a chatty preamble","duration":0.4393000000000029,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701952,"endTime":1781738701962.4392,"status":"passed","message":"","name":"E:/QMAI/src/lib/sweep-reviews.test.ts"},{"assertionResults":[{"ancestorTitles":["system default settings"],"fullName":"system default settings defaults AI output language to Chinese","status":"passed","title":"defaults AI output language to Chinese","duration":2.3466999999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["system default settings"],"fullName":"system default settings defaults chat history length to 20 messages","status":"passed","title":"defaults chat history length to 20 messages","duration":0.3130999999999631,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["system default settings"],"fullName":"system default settings removes image captioning, web search, and output preferences from settings navigation","status":"passed","title":"removes image captioning, web search, and output preferences from settings navigation","duration":0.9500000000000455,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704175,"endTime":1781738704177.95,"status":"passed","message":"","name":"E:/QMAI/src/lib/system-default-settings.test.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"uses native fetch in a plain browser without Tauri internals","status":"passed","title":"uses native fetch in a plain browser without Tauri internals","duration":37.25020000000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699711,"endTime":1781738699748.2502,"status":"passed","message":"","name":"E:/QMAI/src/lib/tauri-fetch.test.ts"},{"assertionResults":[{"ancestorTitles":["stripFrontmatter"],"fullName":"stripFrontmatter removes a standard YAML frontmatter block","status":"passed","title":"removes a standard YAML frontmatter block","duration":2.7428999999999633,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripFrontmatter"],"fullName":"stripFrontmatter is a no-op when there is no frontmatter","status":"passed","title":"is a no-op when there is no frontmatter","duration":0.33799999999996544,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripFrontmatter"],"fullName":"stripFrontmatter leaves content alone when closing fence is missing","status":"passed","title":"leaves content alone when closing fence is missing","duration":0.28600000000000136,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripFrontmatter"],"fullName":"stripFrontmatter tolerates CRLF line endings in the fence","status":"passed","title":"tolerates CRLF line endings in the fence","duration":0.24430000000000973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — trivial inputs"],"fullName":"chunkMarkdown — trivial inputs returns [] for empty string","status":"passed","title":"returns [] for empty string","duration":0.7875000000000227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — trivial inputs"],"fullName":"chunkMarkdown — trivial inputs returns [] for whitespace-only input","status":"passed","title":"returns [] for whitespace-only input","duration":0.2137000000000171,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — trivial inputs"],"fullName":"chunkMarkdown — trivial inputs returns [] when only frontmatter (no body)","status":"passed","title":"returns [] when only frontmatter (no body)","duration":0.2780999999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — trivial inputs"],"fullName":"chunkMarkdown — trivial inputs emits a single chunk when content fits under targetChars","status":"passed","title":"emits a single chunk when content fits under targetChars","duration":1.5160000000000196,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — heading breadcrumbs"],"fullName":"chunkMarkdown — heading breadcrumbs emits empty headingPath for content before any heading","status":"passed","title":"emits empty headingPath for content before any heading","duration":0.32370000000003074,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — heading breadcrumbs"],"fullName":"chunkMarkdown — heading breadcrumbs captures a single-level heading","status":"passed","title":"captures a single-level heading","duration":0.30340000000001055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — heading breadcrumbs"],"fullName":"chunkMarkdown — heading breadcrumbs builds a multi-level breadcrumb","status":"passed","title":"builds a multi-level breadcrumb","duration":0.48099999999999454,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — heading breadcrumbs"],"fullName":"chunkMarkdown — heading breadcrumbs resets deeper headings when a shallower heading appears","status":"passed","title":"resets deeper headings when a shallower heading appears","duration":0.6512000000000171,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — heading breadcrumbs"],"fullName":"chunkMarkdown — heading breadcrumbs does NOT treat # inside fenced code as a heading","status":"passed","title":"does NOT treat # inside fenced code as a heading","duration":0.3957999999999515,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — fenced code blocks"],"fullName":"chunkMarkdown — fenced code blocks keeps a small code block intact","status":"passed","title":"keeps a small code block intact","duration":0.38990000000001146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — fenced code blocks"],"fullName":"chunkMarkdown — fenced code blocks does NOT tear a large code block","status":"passed","title":"does NOT tear a large code block","duration":1.1788999999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — fenced code blocks"],"fullName":"chunkMarkdown — fenced code blocks handles tilde-fence variant `~~~` too","status":"passed","title":"handles tilde-fence variant `~~~` too","duration":0.15049999999996544,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — markdown tables"],"fullName":"chunkMarkdown — markdown tables keeps a small table intact","status":"passed","title":"keeps a small table intact","duration":0.16700000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — markdown tables"],"fullName":"chunkMarkdown — markdown tables does NOT tear a large table","status":"passed","title":"does NOT tear a large table","duration":0.39740000000000464,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — markdown tables"],"fullName":"chunkMarkdown — markdown tables treats a single leading-pipe line as a normal paragraph","status":"passed","title":"treats a single leading-pipe line as a normal paragraph","duration":0.2518000000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — recursive split priority"],"fullName":"chunkMarkdown — recursive split priority splits on paragraph boundaries (\\n\\n) before lines","status":"passed","title":"splits on paragraph boundaries (\\n\\n) before lines","duration":0.3435000000000059,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — recursive split priority"],"fullName":"chunkMarkdown — recursive split priority descends to sentence boundaries when a single paragraph exceeds target (CJK)","status":"passed","title":"descends to sentence boundaries when a single paragraph exceeds target (CJK)","duration":0.97199999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — recursive split priority"],"fullName":"chunkMarkdown — recursive split priority descends to sentence boundaries for English paragraphs too","status":"passed","title":"descends to sentence boundaries for English paragraphs too","duration":0.35719999999997754,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — recursive split priority"],"fullName":"chunkMarkdown — recursive split priority hard-slices an unbreakable blob (no separators) as a last resort","status":"passed","title":"hard-slices an unbreakable blob (no separators) as a last resort","duration":0.4730999999999881,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — overlap between adjacent chunks"],"fullName":"chunkMarkdown — overlap between adjacent chunks zero overlap => disjoint text coverage","status":"passed","title":"zero overlap => disjoint text coverage","duration":0.3240999999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — overlap between adjacent chunks"],"fullName":"chunkMarkdown — overlap between adjacent chunks non-zero overlap injects some preceding content into the next chunk","status":"passed","title":"non-zero overlap injects some preceding content into the next chunk","duration":0.3306999999999789,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — overlap between adjacent chunks"],"fullName":"chunkMarkdown — overlap between adjacent chunks clamps overlap to half the target when misconfigured","status":"passed","title":"clamps overlap to half the target when misconfigured","duration":0.9972999999999956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — minChars merging"],"fullName":"chunkMarkdown — minChars merging merges a small fragment with its next neighbor","status":"passed","title":"merges a small fragment with its next neighbor","duration":0.3066000000000031,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — minChars merging"],"fullName":"chunkMarkdown — minChars merging doesn't merge when the combined size would exceed maxChars","status":"passed","title":"doesn't merge when the combined size would exceed maxChars","duration":0.3604000000000269,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — sections cut chunks even when small"],"fullName":"chunkMarkdown — sections cut chunks even when small emits separate chunks for separate sections","status":"passed","title":"emits separate chunks for separate sections","duration":0.3272000000000048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — charStart / charEnd offsets"],"fullName":"chunkMarkdown — charStart / charEnd offsets offsets point back into the original document","status":"passed","title":"offsets point back into the original document","duration":0.1774000000000342,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — charStart / charEnd offsets"],"fullName":"chunkMarkdown — charStart / charEnd offsets accounts for frontmatter offset in the original document","status":"passed","title":"accounts for frontmatter offset in the original document","duration":0.16800000000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — bookkeeping"],"fullName":"chunkMarkdown — bookkeeping index values are 0-based and sequential","status":"passed","title":"index values are 0-based and sequential","duration":0.27420000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — bookkeeping"],"fullName":"chunkMarkdown — bookkeeping is deterministic: same input ⇒ byte-identical output","status":"passed","title":"is deterministic: same input ⇒ byte-identical output","duration":0.3602999999999952,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — realistic wiki fixture"],"fullName":"chunkMarkdown — realistic wiki fixture produces the expected number of chunks under default options","status":"passed","title":"produces the expected number of chunks under default options","duration":0.29450000000002774,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — realistic wiki fixture"],"fullName":"chunkMarkdown — realistic wiki fixture each chunk is ≤ maxChars (or flagged oversized)","status":"passed","title":"each chunk is ≤ maxChars (or flagged oversized)","duration":0.2858999999999696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — realistic wiki fixture"],"fullName":"chunkMarkdown — realistic wiki fixture only the preamble chunk has empty headingPath; every heading-scoped chunk has one","status":"passed","title":"only the preamble chunk has empty headingPath; every heading-scoped chunk has one","duration":0.466700000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — realistic wiki fixture"],"fullName":"chunkMarkdown — realistic wiki fixture code block and its closing fence stay in the same chunk","status":"passed","title":"code block and its closing fence stay in the same chunk","duration":0.3768999999999778,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — realistic wiki fixture"],"fullName":"chunkMarkdown — realistic wiki fixture list items about variants are captured","status":"passed","title":"list items about variants are captured","duration":0.2545999999999822,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — CRLF in body"],"fullName":"chunkMarkdown — CRLF in body handles CRLF-delimited headings and fences correctly","status":"passed","title":"handles CRLF-delimited headings and fences correctly","duration":0.28419999999999845,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — nested fences"],"fullName":"chunkMarkdown — nested fences a 4-backtick fence keeps a 3-backtick inner block intact","status":"passed","title":"a 4-backtick fence keeps a 3-backtick inner block intact","duration":0.27420000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chunkMarkdown — table with no |---|---| separator"],"fullName":"chunkMarkdown — table with no |---|---| separator treats 2+ consecutive leading-pipe lines as a single indivisible table atom","status":"passed","title":"treats 2+ consecutive leading-pipe lines as a single indivisible table atom","duration":0.3206000000000131,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Chunk shape contract"],"fullName":"Chunk shape contract every chunk has all required fields","status":"passed","title":"every chunk has all required fields","duration":0.2906000000000404,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700243,"endTime":1781738700264.2905,"status":"passed","message":"","name":"E:/QMAI/src/lib/text-chunker.test.ts"},{"assertionResults":[{"ancestorTitles":["trash"],"fullName":"trash 删除文件时写入回收站文件和记录,并删除原文件","status":"passed","title":"删除文件时写入回收站文件和记录,并删除原文件","duration":5.818700000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["trash"],"fullName":"trash 恢复时原路径无冲突则恢复到原路径并移除回收站记录","status":"passed","title":"恢复时原路径无冲突则恢复到原路径并移除回收站记录","duration":1.3863999999999805,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["trash"],"fullName":"trash 恢复章节时原路径冲突则按章节标题改名且不覆盖现有文件","status":"passed","title":"恢复章节时原路径冲突则按章节标题改名且不覆盖现有文件","duration":2.2457999999999743,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["trash"],"fullName":"trash 清理过期记录时只永久删除超过30天的回收站文件","status":"passed","title":"清理过期记录时只永久删除超过30天的回收站文件","duration":0.8333000000000084,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701325,"endTime":1781738701334.8333,"status":"passed","message":"","name":"E:/QMAI/src/lib/trash.test.ts"},{"assertionResults":[{"ancestorTitles":["formatUpdateErrorMessage"],"fullName":"formatUpdateErrorMessage explains invalid remote release json in Chinese","status":"passed","title":"explains invalid remote release json in Chinese","duration":2.29079999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["formatUpdateErrorMessage"],"fullName":"formatUpdateErrorMessage keeps unknown update errors visible","status":"passed","title":"keeps unknown update errors visible","duration":0.29599999999999227,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703875,"endTime":1781738703877.296,"status":"passed","message":"","name":"E:/QMAI/src/lib/update-error-message.test.ts"},{"assertionResults":[{"ancestorTitles":["user visible reasoning"],"fullName":"user visible reasoning defaults auto reasoning to high for visible AI chat generation","status":"passed","title":"defaults auto reasoning to high for visible AI chat generation","duration":2.849700000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["user visible reasoning"],"fullName":"user visible reasoning keeps explicit reasoning settings unchanged","status":"passed","title":"keeps explicit reasoning settings unchanged","duration":0.4603000000000179,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["user visible reasoning"],"fullName":"user visible reasoning turns auto into an explicit thinking request on the OpenAI-compatible wire","status":"passed","title":"turns auto into an explicit thinking request on the OpenAI-compatible wire","duration":1.9057000000000244,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702998,"endTime":1781738703003.9058,"status":"passed","message":"","name":"E:/QMAI/src/lib/user-visible-reasoning.spec.ts"},{"assertionResults":[{"ancestorTitles":["captionImage"],"fullName":"captionImage sends one user message with text+image blocks and the pinned prompt","status":"passed","title":"sends one user message with text+image blocks and the pinned prompt","duration":5.1188999999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage joins multiple streamed tokens into one trimmed string","status":"passed","title":"joins multiple streamed tokens into one trimmed string","duration":0.42210000000000036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage rethrows when streamChat reports an error (no silent empty caption)","status":"passed","title":"rethrows when streamChat reports an error (no silent empty caption)","duration":1.7339000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage passes through temperature and maxTokens overrides to streamChat","status":"passed","title":"passes through temperature and maxTokens overrides to streamChat","duration":0.4371999999999616,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage uses defaults (temp=0, max_tokens=4096) when no options passed","status":"passed","title":"uses defaults (temp=0, max_tokens=4096) when no options passed","duration":0.33480000000002974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage forwards the AbortSignal to streamChat (lets callers cancel batch captioning)","status":"passed","title":"forwards the AbortSignal to streamChat (lets callers cancel batch captioning)","duration":0.2980999999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage CAPTION_PROMPT contains the verbatim factual-description directive (regression guard)","status":"passed","title":"CAPTION_PROMPT contains the verbatim factual-description directive (regression guard)","duration":0.38049999999998363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage uses the no-context prompt when context is empty / whitespace-only","status":"passed","title":"uses the no-context prompt when context is empty / whitespace-only","duration":0.2586000000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["captionImage"],"fullName":"captionImage switches to the context-aware prompt when EITHER side has content","status":"passed","title":"switches to the context-aware prompt when EITHER side has content","duration":0.7415000000000305,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701272,"endTime":1781738701281.7415,"status":"passed","message":"","name":"E:/QMAI/src/lib/vision-caption.test.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"lists parent nodes for nested empty directories created in web fs","status":"passed","title":"lists parent nodes for nested empty directories created in web fs","duration":4.073599999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703605,"endTime":1781738703609.0735,"status":"passed","message":"","name":"E:/QMAI/src/lib/web-fs.spec.ts"},{"assertionResults":[{"ancestorTitles":["WebFileSystem"],"fullName":"WebFileSystem treats created directories as existing paths","status":"passed","title":"treats created directories as existing paths","duration":7.724899999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WebFileSystem"],"fullName":"WebFileSystem initializes demo graph pages with Chinese example names","status":"passed","title":"initializes demo graph pages with Chinese example names","duration":2.7177999999999543,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701598,"endTime":1781738701608.7178,"status":"passed","message":"","name":"E:/QMAI/src/lib/web-fs.test.ts"},{"assertionResults":[{"ancestorTitles":["web research helpers"],"fullName":"web research helpers detects explicit web research requests and URLs","status":"passed","title":"detects explicit web research requests and URLs","duration":4.339800000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web research helpers"],"fullName":"web research helpers extracts http and https URLs without trailing punctuation","status":"passed","title":"extracts http and https URLs without trailing punctuation","duration":1.4266999999999825,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web research helpers"],"fullName":"web research helpers builds a bounded research context with sources","status":"passed","title":"builds a bounded research context with sources","duration":1.6521999999999935,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703248,"endTime":1781738703255.652,"status":"passed","message":"","name":"E:/QMAI/src/lib/web-research.spec.ts"},{"assertionResults":[{"ancestorTitles":["webSearch"],"fullName":"webSearch normalizes Tavily results","status":"passed","title":"normalizes Tavily results","duration":36.594899999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch calls SerpApi Google Search and normalizes organic results","status":"passed","title":"calls SerpApi Google Search and normalizes organic results","duration":1.7985000000000468,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch uses SerpApi provider-specific config and selected engine","status":"passed","title":"uses SerpApi provider-specific config and selected engine","duration":0.8822999999999865,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch calls SearXNG JSON search with the configured instance and categories","status":"passed","title":"calls SearXNG JSON search with the configured instance and categories","duration":1.330500000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch preserves SearXNG subpath instances when building the search endpoint","status":"passed","title":"preserves SearXNG subpath instances when building the search endpoint","duration":0.8870000000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch surfaces SerpApi JSON errors","status":"passed","title":"surfaces SerpApi JSON errors","duration":2.1872999999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["webSearch"],"fullName":"webSearch requires a configured search provider and key","status":"passed","title":"requires a configured search provider and key","duration":0.9504000000000019,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699453,"endTime":1781738699497.9504,"status":"passed","message":"","name":"E:/QMAI/src/lib/web-search.test.ts"},{"assertionResults":[{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle pulls a bare title value","status":"passed","title":"pulls a bare title value","duration":2.3109000000000037,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle strips surrounding double quotes","status":"passed","title":"strips surrounding double quotes","duration":0.2869000000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle strips surrounding single quotes","status":"passed","title":"strips surrounding single quotes","duration":0.21399999999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle tolerates extra whitespace","status":"passed","title":"tolerates extra whitespace","duration":0.23560000000003356,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle handles CJK titles","status":"passed","title":"handles CJK titles","duration":0.29759999999998854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractFrontmatterTitle"],"fullName":"extractFrontmatterTitle returns empty string when no title present","status":"passed","title":"returns empty string when no title present","duration":0.2144999999999868,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildDeletedKeys"],"fullName":"buildDeletedKeys includes both slug-form and title-form of every deleted page","status":"passed","title":"includes both slug-form and title-form of every deleted page","duration":0.459699999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildDeletedKeys"],"fullName":"buildDeletedKeys treats slug and title as synonyms when they normalise equal","status":"passed","title":"treats slug and title as synonyms when they normalise equal","duration":0.24360000000001492,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildDeletedKeys"],"fullName":"buildDeletedKeys handles missing title gracefully","status":"passed","title":"handles missing title gracefully","duration":0.29120000000000346,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildDeletedKeys"],"fullName":"buildDeletedKeys treats path and .md variants as the same deleted wiki target","status":"passed","title":"treats path and .md variants as the same deleted wiki target","duration":0.34739999999999327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildDeletedKeys"],"fullName":"buildDeletedKeys is empty when given no pages","status":"passed","title":"is empty when given no pages","duration":0.28350000000000364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — title/slug matching (Bug A)"],"fullName":"cleanIndexListing — title/slug matching (Bug A) drops a bullet whose primary wikilink is the title form `[[KV Cache]]`","status":"passed","title":"drops a bullet whose primary wikilink is the title form `[[KV Cache]]`","duration":0.7750000000000341,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — title/slug matching (Bug A)"],"fullName":"cleanIndexListing — title/slug matching (Bug A) drops a bullet whose primary wikilink is the slug form `[[kv-cache]]`","status":"passed","title":"drops a bullet whose primary wikilink is the slug form `[[kv-cache]]`","duration":0.17009999999999081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — title/slug matching (Bug A)"],"fullName":"cleanIndexListing — title/slug matching (Bug A) drops bullets for CJK title forms","status":"passed","title":"drops bullets for CJK title forms","duration":0.30110000000001946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — title/slug matching (Bug A)"],"fullName":"cleanIndexListing — title/slug matching (Bug A) drops bullets using a pipe-separated display form `[[target|display]]`","status":"passed","title":"drops bullets using a pipe-separated display form `[[target|display]]`","duration":0.15760000000000218,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — title/slug matching (Bug A)"],"fullName":"cleanIndexListing — title/slug matching (Bug A) drops bullets whose wikilink target includes a path or .md suffix","status":"passed","title":"drops bullets whose wikilink target includes a path or .md suffix","duration":0.2685000000000173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — substring safety (Bug B)"],"fullName":"cleanIndexListing — substring safety (Bug B) does NOT remove `[[OpenAI]]` when deleting `ai`","status":"passed","title":"does NOT remove `[[OpenAI]]` when deleting `ai`","duration":0.35110000000003083,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — substring safety (Bug B)"],"fullName":"cleanIndexListing — substring safety (Bug B) does NOT remove `[[Europe]]` when deleting `rope`","status":"passed","title":"does NOT remove `[[Europe]]` when deleting `rope`","duration":0.2418999999999869,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — substring safety (Bug B)"],"fullName":"cleanIndexListing — substring safety (Bug B) does NOT wipe every bullet when a 2-char slug appears in common words","status":"passed","title":"does NOT wipe every bullet when a 2-char slug appears in common words","duration":0.29719999999997526,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — shared-source scenario"],"fullName":"cleanIndexListing — shared-source scenario keeps the index line when a multi-source page stayed on disk (caller did not include it in deletedPageInfos)","status":"passed","title":"keeps the index line when a multi-source page stayed on disk (caller did not include it in deletedPageInfos)","duration":0.24959999999998672,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — shared-source scenario"],"fullName":"cleanIndexListing — shared-source scenario no-op when deletedPageInfos is empty (all deletions were source-only)","status":"passed","title":"no-op when deletedPageInfos is empty (all deletions were source-only)","duration":0.13460000000003447,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation preserves section headers even when the section goes empty","status":"passed","title":"preserves section headers even when the section goes empty","duration":0.2033999999999878,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation preserves non-list prose that mentions a deleted title (no bullet marker)","status":"passed","title":"preserves non-list prose that mentions a deleted title (no bullet marker)","duration":0.17289999999997008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation preserves frontmatter lines even when they contain 'title:'","status":"passed","title":"preserves frontmatter lines even when they contain 'title:'","duration":0.15399999999999636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation preserves blank lines around the removed entry","status":"passed","title":"preserves blank lines around the removed entry","duration":0.2108000000000061,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation handles asterisk bullets as well as hyphen bullets","status":"passed","title":"handles asterisk bullets as well as hyphen bullets","duration":0.137800000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanIndexListing — line-shape preservation"],"fullName":"cleanIndexListing — line-shape preservation ignores secondary wikilinks in the description when the primary survives","status":"passed","title":"ignores secondary wikilinks in the description when the primary survives","duration":0.1472999999999729,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks replaces `[[deleted]]` with its bare label","status":"passed","title":"replaces `[[deleted]]` with its bare label","duration":0.2440000000000282,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks replaces `[[deleted|display]]` with the display text","status":"passed","title":"replaces `[[deleted|display]]` with the display text","duration":0.14629999999999654,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks leaves wikilinks to surviving pages alone","status":"passed","title":"leaves wikilinks to surviving pages alone","duration":0.1348000000000411,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks handles mixed title/slug wikilinks pointing at the same deleted page","status":"passed","title":"handles mixed title/slug wikilinks pointing at the same deleted page","duration":0.13940000000002328,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks strips wikilinks whose target includes a path or .md suffix","status":"passed","title":"strips wikilinks whose target includes a path or .md suffix","duration":0.1530999999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks does NOT wipe `[[OpenAI]]` when deleting `ai` (Bug B — substring safety)","status":"passed","title":"does NOT wipe `[[OpenAI]]` when deleting `ai` (Bug B — substring safety)","duration":0.155399999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks strips every occurrence in a long document","status":"passed","title":"strips every occurrence in a long document","duration":0.27089999999998327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stripDeletedWikilinks"],"fullName":"stripDeletedWikilinks is a no-op when deletedKeys is empty","status":"passed","title":"is a no-op when deletedKeys is empty","duration":0.11829999999997654,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["End-to-end: delete a single-source page, keep a shared page"],"fullName":"End-to-end: delete a single-source page, keep a shared page strips references everywhere while leaving shared pages intact","status":"passed","title":"strips references everywhere while leaving shared pages intact","duration":0.5409000000000219,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["End-to-end: the user's reported regression (substring false-positive)"],"fullName":"End-to-end: the user's reported regression (substring false-positive) deleting `ai.md` doesn't collaterally wipe OpenAI / Constitutional AI / AI Safety","status":"passed","title":"deleting `ai.md` doesn't collaterally wipe OpenAI / Constitutional AI / AI Safety","duration":0.2832000000000221,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["End-to-end: the user's original bug (title-form wikilinks not matching slug)"],"fullName":"End-to-end: the user's original bug (title-form wikilinks not matching slug) stale `[[KV Cache]]` style entries get removed when page kv-cache.md is deleted","status":"passed","title":"stale `[[KV Cache]]` style entries get removed when page kv-cache.md is deleted","duration":0.3949000000000069,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701135,"endTime":1781738701148.395,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-cleanup.test.ts"},{"assertionResults":[{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug ASCII title is lowercased and hyphenated","status":"passed","title":"ASCII title is lowercased and hyphenated","duration":3.095300000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug strips ASCII punctuation but keeps letters and digits","status":"passed","title":"strips ASCII punctuation but keeps letters and digits","duration":0.31270000000000664,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug keeps CJK characters intact (the core bug fix)","status":"passed","title":"keeps CJK characters intact (the core bug fix)","duration":0.7461999999999875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug handles mixed CJK + ASCII cleanly","status":"passed","title":"handles mixed CJK + ASCII cleanly","duration":0.26219999999995025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug normalizes full-width digits / latin to half-width (NFKC)","status":"passed","title":"normalizes full-width digits / latin to half-width (NFKC)","duration":0.24799999999999045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug keeps Japanese kana and kanji","status":"passed","title":"keeps Japanese kana and kanji","duration":0.21750000000002956,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug falls back to 'query' when title yields nothing usable","status":"passed","title":"falls back to 'query' when title yields nothing usable","duration":0.358099999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug collapses runs of hyphens and trims leading/trailing ones","status":"passed","title":"collapses runs of hyphens and trims leading/trailing ones","duration":0.26420000000001664,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug truncates long slugs to 50 characters","status":"passed","title":"truncates long slugs to 50 characters","duration":1.1324999999999932,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQuerySlug"],"fullName":"makeQuerySlug preserves case-insensitive equivalence for wikilink matching","status":"passed","title":"preserves case-insensitive equivalence for wikilink matching","duration":0.3410999999999831,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQueryFileName"],"fullName":"makeQueryFileName produces slug-YYYY-MM-DD-HHMMSS.md shape","status":"passed","title":"produces slug-YYYY-MM-DD-HHMMSS.md shape","duration":1.4221000000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQueryFileName"],"fullName":"makeQueryFileName two saves of the SAME title within the same day produce DIFFERENT filenames (the reported bug)","status":"passed","title":"two saves of the SAME title within the same day produce DIFFERENT filenames (the reported bug)","duration":0.4870999999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQueryFileName"],"fullName":"makeQueryFileName emoji-only title falls back to 'query' but still produces distinct filenames per save","status":"passed","title":"emoji-only title falls back to 'query' but still produces distinct filenames per save","duration":1.0019999999999527,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeQueryFileName"],"fullName":"makeQueryFileName uses UTC so same timestamp from different timezones hashes identically","status":"passed","title":"uses UTC so same timestamp from different timezones hashes identically","duration":0.16090000000002647,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeChapterFileName"],"fullName":"makeChapterFileName uses chapter number plus title without timestamps","status":"passed","title":"uses chapter number plus title without timestamps","duration":1.2121000000000208,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeChapterFileName"],"fullName":"makeChapterFileName does not duplicate an existing chapter prefix from the title","status":"passed","title":"does not duplicate an existing chapter prefix from the title","duration":0.31289999999995644,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeChapterFileName"],"fullName":"makeChapterFileName replaces a stale chapter prefix with the target chapter number","status":"passed","title":"replaces a stale chapter prefix with the target chapter number","duration":0.31510000000002947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeChapterFileName"],"fullName":"makeChapterFileName uses only the chapter prefix when the title has no extra name","status":"passed","title":"uses only the chapter prefix when the title has no extra name","duration":0.22579999999999245,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["makeChapterFileName"],"fullName":"makeChapterFileName builds default chapter titles with a separator","status":"passed","title":"builds default chapter titles with a separator","duration":0.2469000000000392,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701143,"endTime":1781738701156.2468,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-filename.test.ts"},{"assertionResults":[{"ancestorTitles":["buildWikiGraph chapter type compatibility"],"fullName":"buildWikiGraph chapter type compatibility treats chapter files with chapter_number as chapter nodes even without explicit type","status":"passed","title":"treats chapter files with chapter_number as chapter nodes even without explicit type","duration":6.8980999999999995,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702334,"endTime":1781738702340.8982,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-graph.chapter-type.test.ts"},{"assertionResults":[{"ancestorTitles":["buildWikiGraph related frontmatter"],"fullName":"buildWikiGraph related frontmatter 将 frontmatter related 字段解析为图谱边并与正文链接去重","status":"passed","title":"将 frontmatter related 字段解析为图谱边并与正文链接去重","duration":10.238099999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildWikiGraph related frontmatter"],"fullName":"buildWikiGraph related frontmatter 将同一章节快照来源的孤立小说节点补连到章节节点","status":"passed","title":"将同一章节快照来源的孤立小说节点补连到章节节点","duration":3.341600000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildWikiGraph related frontmatter"],"fullName":"buildWikiGraph related frontmatter 从 Wiki 条目关系行派生具体关系边并保留条目来源","status":"passed","title":"从 Wiki 条目关系行派生具体关系边并保留条目来源","duration":1.7075999999999567,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildWikiGraph related frontmatter"],"fullName":"buildWikiGraph related frontmatter 保留同一对节点的不同关系和反向关系并追溯来源","status":"passed","title":"保留同一对节点的不同关系和反向关系并追溯来源","duration":1.007599999999968,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700764,"endTime":1781738700781.0076,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-graph.test.ts"},{"assertionResults":[{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage deletes the file, then drops the matching page's embedding chunks","status":"passed","title":"deletes the file, then drops the matching page's embedding chunks","duration":4.530399999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage calls deleteFile BEFORE removePageEmbedding (file is the source of truth)","status":"passed","title":"calls deleteFile BEFORE removePageEmbedding (file is the source of truth)","duration":0.6304999999999836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage does NOT call removePageEmbedding when deleteFile throws","status":"passed","title":"does NOT call removePageEmbedding when deleteFile throws","duration":2.6330999999999563,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage propagates removePageEmbedding errors to the caller (not silently swallowed)","status":"passed","title":"propagates removePageEmbedding errors to the caller (not silently swallowed)","duration":0.5583000000000311,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage derives slug from the path's basename, ignoring directory segments","status":"passed","title":"derives slug from the path's basename, ignoring directory segments","duration":0.38119999999997844,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage handles Windows backslash paths (project path normalization happens elsewhere)","status":"passed","title":"handles Windows backslash paths (project path normalization happens elsewhere)","duration":0.45439999999996417,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage preserves dotted page names (e.g. foo.bar.md) in the slug","status":"passed","title":"preserves dotted page names (e.g. foo.bar.md) in the slug","duration":0.3118999999999801,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage skips removePageEmbedding when slug derivation yields empty (defensive)","status":"passed","title":"skips removePageEmbedding when slug derivation yields empty (defensive)","duration":0.28010000000000446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage deleting wiki/sources/<slug>.md also deletes wiki/media/<slug>/","status":"passed","title":"deleting wiki/sources/<slug>.md also deletes wiki/media/<slug>/","duration":0.5386000000000308,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage does NOT cascade media when deleting a non-source page (concept / entity / queries)","status":"passed","title":"does NOT cascade media when deleting a non-source page (concept / entity / queries)","duration":0.6197999999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage media-cascade tolerates a missing media directory (no images were extracted)","status":"passed","title":"media-cascade tolerates a missing media directory (no images were extracted)","duration":0.852299999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage handles Windows backslash paths in the source-page detection","status":"passed","title":"handles Windows backslash paths in the source-page detection","duration":0.24559999999996762,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPage"],"fullName":"cascadeDeleteWikiPage does not attempt media deletion when slug is empty or hidden (defensive)","status":"passed","title":"does not attempt media deletion when slug is empty or hidden (defensive)","duration":0.38889999999997826,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs deletes the file, drops embeddings, and reports the path","status":"passed","title":"deletes the file, drops embeddings, and reports the path","duration":1.2457999999999743,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs strips [[deleted]] body wikilinks from sibling pages","status":"passed","title":"strips [[deleted]] body wikilinks from sibling pages","duration":1.0640999999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs strips title-form [[Alice Chen]] wikilinks too","status":"passed","title":"strips title-form [[Alice Chen]] wikilinks too","duration":0.45460000000002765,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs removes the deleted page's listing line from index.md","status":"passed","title":"removes the deleted page's listing line from index.md","duration":0.6394999999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs drops the deleted slug from `related:` frontmatter arrays","status":"passed","title":"drops the deleted slug from `related:` frontmatter arrays","duration":0.9044000000000096,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs drops path-style `related:` entries that point at deleted pages","status":"passed","title":"drops path-style `related:` entries that point at deleted pages","duration":0.49569999999999936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs does NOT touch a sibling whose slug merely contains the deleted slug as a substring","status":"passed","title":"does NOT touch a sibling whose slug merely contains the deleted slug as a substring","duration":0.432200000000023,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cascadeDeleteWikiPagesWithRefs"],"fullName":"cascadeDeleteWikiPagesWithRefs handles batch deletes (multiple targets) with one cleanup sweep","status":"passed","title":"handles batch deletes (multiple targets) with one cleanup sweep","duration":0.426400000000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700302,"endTime":1781738700320.4956,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-page-delete.test.ts"},{"assertionResults":[{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink unwraps a bare [[target]]","status":"passed","title":"unwraps a bare [[target]]","duration":3.7077999999999633,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink unwraps [[target|alias]] and uses alias as label","status":"passed","title":"unwraps [[target|alias]] and uses alias as label","duration":0.40800000000001546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink trims whitespace inside the wikilink","status":"passed","title":"trims whitespace inside the wikilink","duration":0.32330000000001746,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink returns input unchanged for non-wikilink strings","status":"passed","title":"returns input unchanged for non-wikilink strings","duration":0.3872000000000071,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink does not match partial brackets","status":"passed","title":"does not match partial brackets","duration":0.344099999999969,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["unwrapWikilink"],"fullName":"unwrapWikilink returns target when alias is empty pipe","status":"passed","title":"returns target when alias is empty pipe","duration":0.22739999999998872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findInTreeByName"],"fullName":"findInTreeByName returns null on an empty tree","status":"passed","title":"returns null on an empty tree","duration":0.37050000000004957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findInTreeByName"],"fullName":"findInTreeByName finds a file at the top of a subtree","status":"passed","title":"finds a file at the top of a subtree","duration":0.3634999999999877,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findInTreeByName"],"fullName":"findInTreeByName recurses into nested directories","status":"passed","title":"recurses into nested directories","duration":0.2940999999999576,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findInTreeByName"],"fullName":"findInTreeByName respects pathContains as a subtree filter","status":"passed","title":"respects pathContains as a subtree filter","duration":0.329400000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["findInTreeByName"],"fullName":"findInTreeByName returns null when nothing matches","status":"passed","title":"returns null when nothing matches","duration":0.17599999999998772,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug appends .md and finds entities by bare slug","status":"passed","title":"appends .md and finds entities by bare slug","duration":0.21330000000000382,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug finds queries with hyphenated slugs","status":"passed","title":"finds queries with hyphenated slugs","duration":0.1645000000000323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug accepts bare filename with .md extension","status":"passed","title":"accepts bare filename with .md extension","duration":0.14100000000001955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug accepts a project-relative wiki path","status":"passed","title":"accepts a project-relative wiki path","duration":0.24030000000004748,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug accepts a project-relative path under nested wiki subfolder","status":"passed","title":"accepts a project-relative path under nested wiki subfolder","duration":0.16629999999997835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug returns null when slug doesn't exist","status":"passed","title":"returns null when slug doesn't exist","duration":0.12620000000003984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug returns null when path-like ref doesn't exist","status":"passed","title":"returns null when path-like ref doesn't exist","duration":0.14670000000000982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug never returns a path under raw/sources, even if a matching .md exists there","status":"passed","title":"never returns a path under raw/sources, even if a matching .md exists there","duration":0.15899999999999181,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveRelatedSlug"],"fullName":"resolveRelatedSlug rejects path-like refs that point outside wiki/","status":"passed","title":"rejects path-like refs that point outside wiki/","duration":0.143100000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName finds a top-level source file","status":"passed","title":"finds a top-level source file","duration":0.2784000000000333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName finds a source nested in a subfolder","status":"passed","title":"finds a source nested in a subfolder","duration":0.16440000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName returns null when filename doesn't exist","status":"passed","title":"returns null when filename doesn't exist","duration":0.14330000000001064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName prefers wiki/sources/<name>.md over raw/sources for bare .md refs","status":"passed","title":"prefers wiki/sources/<name>.md over raw/sources for bare .md refs","duration":0.13220000000001164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName accepts a project-relative path","status":"passed","title":"accepts a project-relative path","duration":0.137800000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveSourceName"],"fullName":"resolveSourceName accepts a project-relative wiki/sources path","status":"passed","title":"accepts a project-relative wiki/sources path","duration":0.11209999999999809,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701761,"endTime":1781738701771.1433,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-page-resolver.test.ts"},{"assertionResults":[{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle returns the entity style for 'entity'","status":"passed","title":"returns the entity style for 'entity'","duration":2.988200000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle is case-insensitive","status":"passed","title":"is case-insensitive","duration":0.34050000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle trims surrounding whitespace","status":"passed","title":"trims surrounding whitespace","duration":0.32970000000000255,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle returns fallback for null","status":"passed","title":"returns fallback for null","duration":0.2857999999999947,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle returns fallback for undefined","status":"passed","title":"returns fallback for undefined","duration":0.22109999999997854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle returns fallback for empty string","status":"passed","title":"returns fallback for empty string","duration":0.16289999999997917,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle returns fallback for an unknown type","status":"passed","title":"returns fallback for an unknown type","duration":0.17430000000001655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["getWikiTypeStyle"],"fullName":"getWikiTypeStyle covers every documented page type","status":"passed","title":"covers every documented page type","duration":2.9150999999999954,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701854,"endTime":1781738701861.915,"status":"passed","message":"","name":"E:/QMAI/src/lib/wiki-type-style.test.ts"},{"assertionResults":[{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks returns input unchanged when there are no wikilinks","status":"passed","title":"returns input unchanged when there are no wikilinks","duration":2.2916999999999916,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks converts a bare [[slug]] to a standard markdown link","status":"passed","title":"converts a bare [[slug]] to a standard markdown link","duration":0.5559999999999832,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks uses the alias as label and target as href for [[slug|alias]]","status":"passed","title":"uses the alias as label and target as href for [[slug|alias]]","duration":0.31320000000005166,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks trims whitespace inside the wikilink","status":"passed","title":"trims whitespace inside the wikilink","duration":0.2537999999999556,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks converts multiple wikilinks on the same line","status":"passed","title":"converts multiple wikilinks on the same line","duration":0.21450000000004366,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks encodes special characters in the href","status":"passed","title":"encodes special characters in the href","duration":0.20769999999998845,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks does not touch wikilinks inside fenced code blocks","status":"passed","title":"does not touch wikilinks inside fenced code blocks","duration":0.21219999999999573,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks does not touch wikilinks inside inline code spans","status":"passed","title":"does not touch wikilinks inside inline code spans","duration":0.2388000000000261,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks handles multiple inline-code spans correctly","status":"passed","title":"handles multiple inline-code spans correctly","duration":0.25380000000001246,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks preserves [[empty alias|]] by falling back to target as label","status":"passed","title":"preserves [[empty alias|]] by falling back to target as label","duration":0.2559999999999718,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks matches the real DPAO body wikilink density","status":"passed","title":"matches the real DPAO body wikilink density","duration":0.5844000000000165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks does not mangle existing standard markdown links","status":"passed","title":"does not mangle existing standard markdown links","duration":0.18039999999996326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["transformWikilinks"],"fullName":"transformWikilinks leaves dangling brackets untouched","status":"passed","title":"leaves dangling brackets untouched","duration":0.15970000000004347,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702858,"endTime":1781738702864.1804,"status":"passed","message":"","name":"E:/QMAI/src/lib/wikilink-transform.test.ts"},{"assertionResults":[{"ancestorTitles":["workspace layout"],"fullName":"workspace layout uses the compact chapter toolbar when the preview header is narrow","status":"passed","title":"uses the compact chapter toolbar when the preview header is narrow","duration":2.192899999999952,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace layout"],"fullName":"workspace layout shows the full chapter toolbar when there is enough room","status":"passed","title":"shows the full chapter toolbar when there is enough room","duration":0.3295999999999708,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace layout"],"fullName":"workspace layout keeps the outer preview content from scrolling in immersive chapter writing","status":"passed","title":"keeps the outer preview content from scrolling in immersive chapter writing","duration":0.35880000000003065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace layout"],"fullName":"workspace layout keeps the outer preview content scrollable for normal files","status":"passed","title":"keeps the outer preview content scrollable for normal files","duration":0.23169999999998936,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703894,"endTime":1781738703897.359,"status":"passed","message":"","name":"E:/QMAI/src/lib/workspace-layout.spec.ts"},{"assertionResults":[{"ancestorTitles":["workspace-layout"],"fullName":"workspace-layout treats wiki as the writing workspace view","status":"passed","title":"treats wiki as the writing workspace view","duration":2.2554999999999836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace-layout"],"fullName":"workspace-layout clamps sidebar width into the supported range","status":"passed","title":"clamps sidebar width into the supported range","duration":0.41800000000000637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace-layout"],"fullName":"workspace-layout clamps chat panel height into the supported range","status":"passed","title":"clamps chat panel height into the supported range","duration":0.3423999999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace-layout"],"fullName":"workspace-layout formats conversation titles for horizontal tabs","status":"passed","title":"formats conversation titles for horizontal tabs","duration":0.28480000000001837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["workspace-layout"],"fullName":"workspace-layout sorts conversations by newest activity first","status":"passed","title":"sorts conversations by newest activity first","duration":0.9081000000000472,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703312,"endTime":1781738703315.9082,"status":"passed","message":"","name":"E:/QMAI/src/lib/workspace-layout.test.ts"},{"assertionResults":[{"ancestorTitles":["ChatDockControls"],"fullName":"ChatDockControls shows only the sidebar dock option when chat is docked at the bottom","status":"passed","title":"shows only the sidebar dock option when chat is docked at the bottom","duration":12.20580000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ChatDockControls"],"fullName":"ChatDockControls shows only the bottom dock option when chat is docked at the sidebar","status":"passed","title":"shows only the bottom dock option when chat is docked at the sidebar","duration":1.2864999999999895,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700933,"endTime":1781738700947.2866,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/chat-dock-controls.spec.tsx"},{"assertionResults":[{"ancestorTitles":["chat input resize bounds"],"fullName":"chat input resize bounds keeps the input height between the default height and half of the panel","status":"passed","title":"keeps the input height between the default height and half of the panel","duration":2.8017000000000394,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat input resize bounds"],"fullName":"chat input resize bounds uses half of the available panel height as the maximum","status":"passed","title":"uses half of the available panel height as the maximum","duration":0.39950000000004593,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704532,"endTime":1781738704535.3994,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/chat-input-resize.spec.ts"},{"assertionResults":[{"ancestorTitles":["ChatInput resize interaction"],"fullName":"ChatInput resize interaction captures the pointer and resizes while dragging the height handle","status":"passed","title":"captures the pointer and resizes while dragging the height handle","duration":132.62980000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ChatInput resize interaction"],"fullName":"ChatInput resize interaction uses the nearest usable panel height when the chat input is wrapped in a short footer","status":"passed","title":"uses the nearest usable panel height when the chat input is wrapped in a short footer","duration":22.409799999999905,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ChatInput resize interaction"],"fullName":"ChatInput resize interaction does not let a grown chat footer become its own resize limit","status":"passed","title":"does not let a grown chat footer become its own resize limit","duration":11.812599999999975,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699229,"endTime":1781738699395.8125,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/chat-input.resize-interaction.spec.tsx"},{"assertionResults":[{"ancestorTitles":["chat thinking display"],"fullName":"chat thinking display keeps completed thinking content in a fixed scrollable panel","status":"passed","title":"keeps completed thinking content in a fixed scrollable panel","duration":30.55749999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat thinking display"],"fullName":"chat thinking display keeps streaming thinking content in a fixed scrollable panel","status":"passed","title":"keeps streaming thinking content in a fixed scrollable panel","duration":2.7421999999996842,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deep chapter thinking toggle style"],"fullName":"deep chapter thinking toggle style uses a clear dark selected state when deep chapter generation is enabled","status":"passed","title":"uses a clear dark selected state when deep chapter generation is enabled","duration":0.34360000000015134,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter save preview sync regression"],"fullName":"chapter save preview sync regression always routes AI chapter saves to the next chapter instead of reusing the current chapter","status":"passed","title":"always routes AI chapter saves to the next chapter instead of reusing the current chapter","duration":0.6212000000000444,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter save preview sync regression"],"fullName":"chapter save preview sync regression no longer uses the pending chapter save dialog flow","status":"passed","title":"no longer uses the pending chapter save dialog flow","duration":0.6075999999998203,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deep chapter unfinished continuation action"],"fullName":"deep chapter unfinished continuation action shows a continuation button and explanation for failed deep chapter thinking","status":"passed","title":"shows a continuation button and explanation for failed deep chapter thinking","duration":0.5157999999996719,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deep chapter unfinished continuation action"],"fullName":"deep chapter unfinished continuation action wires the continuation action through chat panel without rerunning regenerate","status":"passed","title":"wires the continuation action through chat panel without rerunning regenerate","duration":0.6575000000002547,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deep chapter unfinished continuation action"],"fullName":"deep chapter unfinished continuation action keeps the ai chat footer labels as readable Chinese text","status":"failed","title":"keeps the ai chat footer labels as readable Chinese text","duration":12.157200000000103,"failureMessages":["AssertionError: expected 'import { useRef, useEffect, useCallba…' to contain '深度思考'\n at E:/QMAI/src/components/chat/chat-message.spec.tsx:94:20\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]}],"startTime":1781738699025,"endTime":1781738699073.1572,"status":"failed","message":"","name":"E:/QMAI/src/components/chat/chat-message.spec.tsx"},{"assertionResults":[{"ancestorTitles":["chat deep chapter resume"],"fullName":"chat deep chapter resume only allows continuation for failed deep chapter messages with thinking content","status":"passed","title":"only allows continuation for failed deep chapter messages with thinking content","duration":2.8092000000000326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat deep chapter resume"],"fullName":"chat deep chapter resume builds a continuation prompt that reuses previous thinking without restarting all stages","status":"passed","title":"builds a continuation prompt that reuses previous thinking without restarting all stages","duration":0.6416000000000395,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat deep chapter resume"],"fullName":"chat deep chapter resume persists the original deep request in a hidden resume context","status":"passed","title":"persists the original deep request in a hidden resume context","duration":0.6268000000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat deep chapter resume"],"fullName":"chat deep chapter resume uses the persisted original request instead of the visible continue command","status":"passed","title":"uses the persisted original request instead of the visible continue command","duration":0.344600000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat deep chapter resume"],"fullName":"chat deep chapter resume keeps the first unfinished chain as the primary resume context after later retries fail","status":"passed","title":"keeps the first unfinished chain as the primary resume context after later retries fail","duration":0.2717999999999847,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703138,"endTime":1781738703143.2717,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/chat-resume.spec.ts"},{"assertionResults":[{"ancestorTitles":["chat dismantling reference"],"fullName":"chat dismantling reference disables dismantling structure injection while the feature is hidden","status":"passed","title":"disables dismantling structure injection while the feature is hidden","duration":3.1738,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704360,"endTime":1781738704363.1738,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/dismantling-reference.spec.ts"},{"assertionResults":[{"ancestorTitles":["createStreamSessionGuard"],"fullName":"createStreamSessionGuard finalizes immediately on stop and ignores late stream callbacks","status":"passed","title":"finalizes immediately on stop and ignores late stream callbacks","duration":4.748700000000014,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703438,"endTime":1781738703442.7488,"status":"passed","message":"","name":"E:/QMAI/src/components/chat/stream-session.spec.ts"},{"assertionResults":[{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 支持点击结果后打开对应内容","status":"passed","title":"支持点击结果后打开对应内容","duration":2.494100000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 事实检查结果保留章节定位信息","status":"passed","title":"事实检查结果保留章节定位信息","duration":0.3310000000000173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 为卡片提供编辑、AI修改、忽略和恢复原文入口","status":"passed","title":"为卡片提供编辑、AI修改、忽略和恢复原文入口","duration":0.373299999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 章节路径失效时会回退到章节号或文件名搜索","status":"passed","title":"章节路径失效时会回退到章节号或文件名搜索","duration":0.277499999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 AI修改失败时会给出可见反馈,并在生成时禁用确认按钮","status":"passed","title":"AI修改失败时会给出可见反馈,并在生成时禁用确认按钮","duration":0.2892999999999688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dashboard-view 仪表盘动作"],"fullName":"dashboard-view 仪表盘动作 事实检查的AI修改会走章节级补写,而不是直接改写定位片段","status":"passed","title":"事实检查的AI修改会走章节级补写,而不是直接改写定位片段","duration":0.252900000000011,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703745,"endTime":1781738703749.2893,"status":"passed","message":"","name":"E:/QMAI/src/components/dashboard/dashboard-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["WikiEditor immersive writing"],"fullName":"WikiEditor immersive writing prevents the writing textarea from creating a second scrollbar","status":"passed","title":"prevents the writing textarea from creating a second scrollbar","duration":103.60560000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WikiEditor immersive writing"],"fullName":"WikiEditor immersive writing keeps typing on the inserted line after the parent normalizes chapter content","status":"passed","title":"keeps typing on the inserted line after the parent normalizes chapter content","duration":64.11010000000033,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700385,"endTime":1781738700553.11,"status":"passed","message":"","name":"E:/QMAI/src/components/editor/wiki-editor.immersive.spec.tsx"},{"assertionResults":[{"ancestorTitles":["正文划词工具栏"],"fullName":"正文划词工具栏 提供 AI润色 和 去AI味 两个动作","status":"passed","title":"提供 AI润色 和 去AI味 两个动作","duration":2.4028999999999883,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["正文划词工具栏"],"fullName":"正文划词工具栏 将选中的正文片段回传给上层处理","status":"passed","title":"将选中的正文片段回传给上层处理","duration":0.46769999999997935,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704565,"endTime":1781738704568.4678,"status":"passed","message":"","name":"E:/QMAI/src/components/editor/wiki-editor.selection-toolbar.test.tsx"},{"assertionResults":[{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 开始检查时生成 runId 并写入 store","status":"passed","title":"开始检查时生成 runId 并写入 store","duration":2.309100000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 检查完成后通过 finishLintRun 匹配 runId 写回结果","status":"passed","title":"检查完成后通过 finishLintRun 匹配 runId 写回结果","duration":0.2851999999999748,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 finally 块检查 runId 匹配后写回 running: false","status":"passed","title":"finally 块检查 runId 匹配后写回 running: false","duration":0.23439999999999372,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 错误时使用 finishLintRun 写回 error","status":"passed","title":"错误时使用 finishLintRun 写回 error","duration":0.2529999999999859,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 错误 UI 包含 AlertTriangle 和 error 显示","status":"passed","title":"错误 UI 包含 AlertTriangle 和 error 显示","duration":0.24639999999999418,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["lint-view 长任务状态持久化"],"fullName":"lint-view 长任务状态持久化 错误 UI 不为全屏空态,使用横幅样式","status":"passed","title":"错误 UI 不为全屏空态,使用横幅样式","duration":0.35779999999999745,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704223,"endTime":1781738704227.358,"status":"passed","message":"","name":"E:/QMAI/src/components/lint/lint-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["非章节视图不显示右侧正文预览"],"fullName":"非章节视图不显示右侧正文预览 关闭 secondary preview panel","status":"passed","title":"关闭 secondary preview panel","duration":2.4044000000000096,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704674,"endTime":1781738704676.4043,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/app-layout.preview-scope.test.tsx"},{"assertionResults":[{"ancestorTitles":["BookAnalysisSidebarPanel"],"fullName":"BookAnalysisSidebarPanel 整行点击触发 viewer 打开(不依赖眼睛按钮)","status":"passed","title":"整行点击触发 viewer 打开(不依赖眼睛按钮)","duration":249.12519999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BookAnalysisSidebarPanel"],"fullName":"BookAnalysisSidebarPanel 点击删除按钮不触发整行 click,并调用 store cleanup","status":"passed","title":"点击删除按钮不触发整行 click,并调用 store cleanup","duration":187.64030000000002,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698133,"endTime":1781738698569.6404,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/book-analysis-sidebar-panel.spec.tsx"},{"assertionResults":[{"ancestorTitles":["chat layout docking"],"fullName":"chat layout docking shows the writing chat in the bottom dock by default","status":"passed","title":"shows the writing chat in the bottom dock by default","duration":2.327699999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout docking"],"fullName":"chat layout docking moves the writing chat to the right dock when configured","status":"passed","title":"moves the writing chat to the right dock when configured","duration":0.35079999999999245,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout docking"],"fullName":"chat layout docking keeps every dock hidden when chat is collapsed","status":"passed","title":"keeps every dock hidden when chat is collapsed","duration":0.3503000000000043,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704121,"endTime":1781738704124.3508,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/chat-layout.spec.ts"},{"assertionResults":[{"ancestorTitles":["chat layout"],"fullName":"chat layout 收起 AI 会话时不显示底部聊天栏","status":"passed","title":"收起 AI 会话时不显示底部聊天栏","duration":2.200999999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 展开 AI 会话时显示原来的底部聊天窗口","status":"passed","title":"展开 AI 会话时显示原来的底部聊天窗口","duration":0.2717999999999847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 左侧主导航 AI会话按钮可以切换打开和关闭","status":"passed","title":"左侧主导航 AI会话按钮可以切换打开和关闭","duration":0.45400000000000773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 写作工作区收起 AI 会话时隐藏聊天面板","status":"passed","title":"写作工作区收起 AI 会话时隐藏聊天面板","duration":0.4732000000000198,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 章节工具栏顺序按 AI会话 → 去AI味 → 章节状态排列","status":"passed","title":"章节工具栏顺序按 AI会话 → 去AI味 → 章节状态排列","duration":0.8782000000000494,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 预览面板源碼包含 handleDeAiSaveDraft 表明另存草稿存在","status":"passed","title":"预览面板源碼包含 handleDeAiSaveDraft 表明另存草稿存在","duration":0.30110000000001946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 发送消息时接入去AI味默认模式","status":"passed","title":"发送消息时接入去AI味默认模式","duration":0.26890000000003056,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 章节生成类对话会接入 QM-QUAI skill","status":"passed","title":"章节生成类对话会接入 QM-QUAI skill","duration":0.3674000000000319,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 小说写作生成前会确认角色灵魂上下文","status":"passed","title":"小说写作生成前会确认角色灵魂上下文","duration":0.6897999999999911,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 预览面板中去AI味按钮出现在AI会话按钮后面","status":"passed","title":"预览面板中去AI味按钮出现在AI会话按钮后面","duration":0.5032999999999674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chat layout"],"fullName":"chat layout 去AI味按钮点击后直接处理,不再显示下拉菜单项","status":"passed","title":"去AI味按钮点击后直接处理,不再显示下拉菜单项","duration":0.27590000000003556,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702377,"endTime":1781738702383.5034,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/chat-layout.test.ts"},{"assertionResults":[{"ancestorTitles":["content-area search performance"],"fullName":"content-area search performance renders search in the main content area without reviving the old overlay state","status":"passed","title":"renders search in the main content area without reviving the old overlay state","duration":1.4244999999999948,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704750,"endTime":1781738704751.4246,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/content-area.search-performance.test.tsx"},{"assertionResults":[{"ancestorTitles":["content-area novel lint slot"],"fullName":"content-area novel lint slot routes the novel-mode lint slot to MemoryCenterView","status":"passed","title":"routes the novel-mode lint slot to MemoryCenterView","duration":2.31989999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-area novel lint slot"],"fullName":"content-area novel lint slot renames the novel nav label from continuity check to memory center","status":"passed","title":"renames the novel nav label from continuity check to memory center","duration":0.3930000000000291,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-area novel lint slot"],"fullName":"content-area novel lint slot renames dashboard navigation labels away from English in Chinese locale","status":"passed","title":"renames dashboard navigation labels away from English in Chinese locale","duration":0.3044999999999618,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704157,"endTime":1781738704160.393,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/content-area.test.tsx"},{"assertionResults":[{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar does not restore the old absolute title layout","status":"passed","title":"does not restore the old absolute title layout","duration":2.2504000000000133,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar keeps the soul entry wired into the content area route","status":"passed","title":"keeps the soul entry wired into the content area route","duration":0.43060000000002674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar clears the selected file when switching to outline sources","status":"passed","title":"clears the selected file when switching to outline sources","duration":0.31719999999995707,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar keeps the search entry above trash and outside the main nav list","status":"passed","title":"keeps the search entry above trash and outside the main nav list","duration":0.42739999999997735,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar opens search inside the main window instead of creating a separate window","status":"passed","title":"opens search inside the main window instead of creating a separate window","duration":0.2360999999999649,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar keeps the novel memory-center entry on the brain icon","status":"passed","title":"keeps the novel memory-center entry on the brain icon","duration":0.2115999999999758,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["icon-sidebar"],"fullName":"icon-sidebar keeps the review-center nav entry","status":"passed","title":"keeps the review-center nav entry","duration":0.2249999999999659,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703437,"endTime":1781738703441.225,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/icon-sidebar.test.tsx"},{"assertionResults":[{"ancestorTitles":["sidebar-panel usage guide entry"],"fullName":"sidebar-panel usage guide entry renders a usage guide entry above the extraction status area and opens the Feishu guide","status":"passed","title":"renders a usage guide entry above the extraction status area and opens the Feishu guide","duration":1.7305000000000064,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704748,"endTime":1781738704749.7305,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/icon-sidebar.usage-guide.test.ts"},{"assertionResults":[{"ancestorTitles":["knowledge-tree chapter format"],"fullName":"knowledge-tree chapter format renders chapter word count aligned to the right without showing chapter status","status":"passed","title":"renders chapter word count aligned to the right without showing chapter status","duration":3.0585000000000093,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704524,"endTime":1781738704527.0586,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/knowledge-tree.chapter-format.test.tsx"},{"assertionResults":[{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder opens a chapter on click","status":"passed","title":"opens a chapter on click","duration":147.8822,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder does not start dragging when the chapter is not selected","status":"passed","title":"does not start dragging when the chapter is not selected","duration":39.35839999999962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder inserts source chapter below the hover target on reorder drop","status":"passed","title":"inserts source chapter below the hover target on reorder drop","duration":88.4353000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder inserts source chapter above the hover target on reorder drop","status":"passed","title":"inserts source chapter above the hover target on reorder drop","duration":81.27190000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder still reorders when the mouse starts moving before the long-press timer completes","status":"passed","title":"still reorders when the mouse starts moving before the long-press timer completes","duration":45.10169999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder does not write when the dragged chapter lands at the same position","status":"passed","title":"does not write when the dragged chapter lands at the same position","duration":37.8163999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder rolls back written chapters if reorder persistence fails midway","status":"passed","title":"rolls back written chapters if reorder persistence fails midway","duration":33.60819999999967,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder prevents text selection from starting on chapter rows","status":"passed","title":"prevents text selection from starting on chapter rows","duration":18.908300000000054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder allows renaming from the desktop right-click menu","status":"passed","title":"allows renaming from the desktop right-click menu","duration":72.82639999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder rejects renaming a chapter to an existing chapter number","status":"passed","title":"rejects renaming a chapter to an existing chapter number","duration":46.17229999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree selected long press insert reorder"],"fullName":"knowledge-tree selected long press insert reorder keeps desktop right-click page menus working","status":"passed","title":"keeps desktop right-click page menus working","duration":34.570000000000164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["knowledge-tree outline folder delete"],"fullName":"knowledge-tree outline folder delete shows folder delete in the context menu and moves descendant outlines to trash before deleting the folder","status":"passed","title":"shows folder delete in the context menu and moves descendant outlines to trash before deleting the folder","duration":151.22899999999981,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698438,"endTime":1781738699236.229,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/knowledge-tree.long-press.test.tsx"},{"assertionResults":[{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state scopes saved state by projectPath and filePath","status":"passed","title":"scopes saved state by projectPath and filePath","duration":2.3494999999999777,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state writes final chapter save progress through the shared store","status":"passed","title":"writes final chapter save progress through the shared store","duration":0.39170000000001437,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state uses phase codes instead of translated labels for final save progress","status":"passed","title":"uses phase codes instead of translated labels for final save progress","duration":0.27180000000004156,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state flushes chapter filenames before leaving a chapter","status":"passed","title":"flushes chapter filenames before leaving a chapter","duration":0.291499999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state supports selected-text transform previews","status":"passed","title":"supports selected-text transform previews","duration":0.30279999999999063,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state uses a local draft for chapter title editing","status":"passed","title":"uses a local draft for chapter title editing","duration":0.2814999999999941,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state measures chapter title width for header metadata layout","status":"passed","title":"measures chapter title width for header metadata layout","duration":0.31970000000001164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state keeps the draft badge lightweight in the chapter header","status":"passed","title":"keeps the draft badge lightweight in the chapter header","duration":0.24359999999995807,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state falls back to the filename when the chapter heading is missing","status":"passed","title":"falls back to the filename when the chapter heading is missing","duration":0.2270000000000323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state does not render total chapter word count in the header","status":"passed","title":"does not render total chapter word count in the header","duration":0.4481000000000108,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state does not render the archived-draft action anymore","status":"passed","title":"does not render the archived-draft action anymore","duration":0.291499999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state does not mount a newly selected markdown file before it loads","status":"passed","title":"does not mount a newly selected markdown file before it loads","duration":0.20310000000000628,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state skips redundant chapter flushes when markdown is unchanged","status":"passed","title":"skips redundant chapter flushes when markdown is unchanged","duration":0.13949999999999818,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state persists outline ingest progress through the shared outline task flow","status":"passed","title":"persists outline ingest progress through the shared outline task flow","duration":0.18599999999997863,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["preview-panel final chapter save state"],"fullName":"preview-panel final chapter save state strips frontmatter from trash markdown previews","status":"passed","title":"strips frontmatter from trash markdown previews","duration":0.22129999999998518,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702907,"endTime":1781738702913.2212,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/preview-panel.test.tsx"},{"assertionResults":[{"ancestorTitles":["review-center-sidebar-panel"],"fullName":"review-center-sidebar-panel keeps the chapter target selector available","status":"passed","title":"keeps the chapter target selector available","duration":2.317800000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-center-sidebar-panel"],"fullName":"review-center-sidebar-panel builds labels from chapter content without re-prefixing chapter numbers","status":"passed","title":"builds labels from chapter content without re-prefixing chapter numbers","duration":0.4134000000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-center-sidebar-panel"],"fullName":"review-center-sidebar-panel keeps per-dimension navigation buttons","status":"passed","title":"keeps per-dimension navigation buttons","duration":0.25790000000000646,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-center-sidebar-panel"],"fullName":"review-center-sidebar-panel reuses the shared review start helpers","status":"passed","title":"reuses the shared review start helpers","duration":0.2674999999999841,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-center-sidebar-panel"],"fullName":"review-center-sidebar-panel switches dimension when clicking a dimension row","status":"passed","title":"switches dimension when clicking a dimension row","duration":0.20479999999997744,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703497,"endTime":1781738703500.2676,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/review-center-sidebar-panel.test.tsx"},{"assertionResults":[{"ancestorTitles":["sidebar-panel memory center"],"fullName":"sidebar-panel memory center renders a memory-center list in the middle column for novel lint view","status":"passed","title":"renders a memory-center list in the middle column for novel lint view","duration":2.4716000000000236,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["sidebar-panel memory center"],"fullName":"sidebar-panel memory center does not keep the chapter tree active while memory center is open","status":"passed","title":"does not keep the chapter tree active while memory center is open","duration":0.4270999999999958,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704262,"endTime":1781738704265.427,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/sidebar-panel.memory-center.test.tsx"},{"assertionResults":[{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 章节库和大纲库顶部只保留单标题模式","status":"passed","title":"章节库和大纲库顶部只保留单标题模式","duration":2.7817999999999756,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 支持新建卷和新建文件夹","status":"passed","title":"支持新建卷和新建文件夹","duration":0.4630999999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 从大纲库切回章节库时会清理未完成的新建大纲状态","status":"passed","title":"从大纲库切回章节库时会清理未完成的新建大纲状态","duration":0.4227999999999952,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 章节栏顶部显示当前所有章节总字数","status":"passed","title":"章节栏顶部显示当前所有章节总字数","duration":0.27789999999998827,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 大纲库顶部提供导入文件和导入文件夹入口","status":"passed","title":"大纲库顶部提供导入文件和导入文件夹入口","duration":0.3213999999999828,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 章节导入记忆确认支持提取、只导入和取消导入三种结果","status":"passed","title":"章节导入记忆确认支持提取、只导入和取消导入三种结果","duration":0.46359999999998536,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["小说模式侧栏"],"fullName":"小说模式侧栏 AI 大纲导入文件夹也会询问是否提取记忆","status":"passed","title":"AI 大纲导入文件夹也会询问是否提取记忆","duration":0.2576999999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703385,"endTime":1781738703390.4636,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/sidebar-panel.novel-mode.test.tsx"},{"assertionResults":[{"ancestorTitles":["sidebar-panel outline tree"],"fullName":"sidebar-panel outline tree uses the outline knowledge tree in novel mode instead of the raw source sidebar","status":"passed","title":"uses the outline knowledge tree in novel mode instead of the raw source sidebar","duration":1.7642999999999915,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704693,"endTime":1781738704694.7644,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/sidebar-panel.outline-tree.test.tsx"},{"assertionResults":[{"ancestorTitles":["sidebar-panel search history"],"fullName":"sidebar-panel search history shows search history in the second column when search view is active","status":"passed","title":"shows search history in the second column when search view is active","duration":2.310299999999984,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704659,"endTime":1781738704661.3103,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/sidebar-panel.search-history.test.tsx"},{"assertionResults":[{"ancestorTitles":["SoulSidebarPanel runtime"],"fullName":"SoulSidebarPanel runtime reloads bound characters after the shared data version changes","status":"passed","title":"reloads bound characters after the shared data version changes","duration":75.47019999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699514,"endTime":1781738699589.4702,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/soul-sidebar-panel.runtime.test.tsx"},{"assertionResults":[{"ancestorTitles":["soul-sidebar-panel"],"fullName":"soul-sidebar-panel shows bound novel characters under the project soul tab","status":"passed","title":"shows bound novel characters under the project soul tab","duration":2.3261999999999716,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["soul-sidebar-panel"],"fullName":"soul-sidebar-panel shows which novel character is bound before the aura selector","status":"passed","title":"shows which novel character is bound before the aura selector","duration":0.311300000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["soul-sidebar-panel"],"fullName":"soul-sidebar-panel allows switching the bound aura directly from the project soul panel","status":"passed","title":"allows switching the bound aura directly from the project soul panel","duration":0.3143000000000029,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["soul-sidebar-panel"],"fullName":"soul-sidebar-panel keeps an unbind action on the right side of each binding row","status":"passed","title":"keeps an unbind action on the right side of each binding row","duration":0.2714000000000283,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["soul-sidebar-panel"],"fullName":"soul-sidebar-panel uses the novel character name as the binding card key","status":"passed","title":"uses the novel character name as the binding card key","duration":0.21580000000000155,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703792,"endTime":1781738703796.2158,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/soul-sidebar-panel.test.tsx"},{"assertionResults":[{"ancestorTitles":["startup heavy imports"],"fullName":"startup heavy imports keeps memory-center out of the default sidebar startup path","status":"passed","title":"keeps memory-center out of the default sidebar startup path","duration":3.432700000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["startup heavy imports"],"fullName":"startup heavy imports keeps chapter ingest out of the knowledge tree startup path","status":"passed","title":"keeps chapter ingest out of the knowledge tree startup path","duration":0.4134999999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["startup heavy imports"],"fullName":"startup heavy imports keeps chapter ingest and review modules out of the preview startup path","status":"passed","title":"keeps chapter ingest and review modules out of the preview startup path","duration":0.47109999999997854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["startup heavy imports"],"fullName":"startup heavy imports keeps novel context and ingest modules out of the chat startup path","status":"passed","title":"keeps novel context and ingest modules out of the chat startup path","duration":0.38100000000002865,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703477,"endTime":1781738703481.4712,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/startup-heavy-imports.test.ts"},{"assertionResults":[{"ancestorTitles":["three-column risk fixes"],"fullName":"three-column risk fixes keeps graph control preferences compatible with existing localStorage keys","status":"passed","title":"keeps graph control preferences compatible with existing localStorage keys","duration":2.2236000000000047,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["three-column risk fixes"],"fullName":"three-column risk fixes lets the graph sidebar trigger GraphView refresh through the shared store","status":"passed","title":"lets the graph sidebar trigger GraphView refresh through the shared store","duration":0.5050999999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["three-column risk fixes"],"fullName":"three-column risk fixes keeps non-novel review center compatible with the existing dashboard view","status":"passed","title":"keeps non-novel review center compatible with the existing dashboard view","duration":0.2231000000000165,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703587,"endTime":1781738703590.5051,"status":"passed","message":"","name":"E:/QMAI/src/components/layout/three-column-risk-fixes.test.tsx"},{"assertionResults":[{"ancestorTitles":["BookAnalysisResultViewer"],"fullName":"BookAnalysisResultViewer renders selected Skill import controls for generated character skills","status":"passed","title":"renders selected Skill import controls for generated character skills","duration":32.61519999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["BookAnalysisResultViewer"],"fullName":"BookAnalysisResultViewer renders single-character reextract buttons when a character is selected","status":"passed","title":"renders single-character reextract buttons when a character is selected","duration":3.2684999999999036,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700618,"endTime":1781738700654.2686,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/book-analysis-result-viewer.spec.tsx"},{"assertionResults":[{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 绑定小说人物卡片位于详情内容上方","status":"passed","title":"绑定小说人物卡片位于详情内容上方","duration":2.4989000000000487,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 自定义灵魂先显示预览,再进入新建或编辑表单","status":"passed","title":"自定义灵魂先显示预览,再进入新建或编辑表单","duration":0.8125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 新建角色灵魂入口固定在自定义列表上方","status":"passed","title":"新建角色灵魂入口固定在自定义列表上方","duration":0.39390000000003056,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 自定义灵魂预览区提供删除入口","status":"passed","title":"自定义灵魂预览区提供删除入口","duration":0.3417999999996937,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 编辑当前自定义灵魂时会回填当前人物信息","status":"passed","title":"编辑当前自定义灵魂时会回填当前人物信息","duration":0.6332999999999629,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 页面异步失败时显示中文错误提示而不是吞掉异常","status":"passed","title":"页面异步失败时显示中文错误提示而不是吞掉异常","duration":0.40990000000010696,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 内置和自定义灵魂详情展示分类、摘要和五层结构","status":"passed","title":"内置和自定义灵魂详情展示分类、摘要和五层结构","duration":0.4412999999999556,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 详情区域展示灵魂文档和研究文件预览状态","status":"passed","title":"详情区域展示灵魂文档和研究文件预览状态","duration":0.4601999999999862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 新建自定义灵魂保留资料导入入口","status":"passed","title":"新建自定义灵魂保留资料导入入口","duration":0.529700000000048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 生成自定义灵魂时提供工作流进度并锁定切换","status":"passed","title":"生成自定义灵魂时提供工作流进度并锁定切换","duration":0.5671999999999571,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 详情区域展示生成提示词和 AI 搜索状态","status":"passed","title":"详情区域展示生成提示词和 AI 搜索状态","duration":0.2584999999999127,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 编辑自定义灵魂允许维护资料来源索引","status":"passed","title":"编辑自定义灵魂允许维护资料来源索引","duration":0.18580000000019936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 提供灵魂注入预览入口和空状态提示","status":"passed","title":"提供灵魂注入预览入口和空状态提示","duration":0.2600999999999658,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 灵魂注入预览复用上下文构建函数并显示结果","status":"passed","title":"灵魂注入预览复用上下文构建函数并显示结果","duration":0.2270000000003165,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 用户可见文案不再出现旧功能名","status":"passed","title":"用户可见文案不再出现旧功能名","duration":0.34639999999990323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["角色灵魂页面"],"fullName":"角色灵魂页面 导航入口和内容区能渲染角色灵魂页面","status":"passed","title":"导航入口和内容区能渲染角色灵魂页面","duration":0.22820000000001528,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterAuraView hideSidebar selection"],"fullName":"CharacterAuraView hideSidebar selection uses the selected built-in soul from the store in embedded mode","status":"passed","title":"uses the selected built-in soul from the store in embedded mode","duration":106.90430000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterAuraView hideSidebar selection"],"fullName":"CharacterAuraView hideSidebar selection uses the selected custom soul section from the store in embedded mode","status":"passed","title":"uses the selected custom soul section from the store in embedded mode","duration":53.63790000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterAuraView hideSidebar selection"],"fullName":"CharacterAuraView hideSidebar selection uses AI to generate the aura preview instead of local extracted text","status":"passed","title":"uses AI to generate the aura preview instead of local extracted text","duration":63.02509999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterAuraView hideSidebar selection"],"fullName":"CharacterAuraView hideSidebar selection binds a character and bumps the shared data version for the project sidebar","status":"failed","title":"binds a character and bumps the shared data version for the project sidebar","duration":71.19270000000006,"failureMessages":["AssertionError: expected 2 to be 1 // Object.is equality\n at E:/QMAI/src/components/novel/character-aura-view.test.tsx:404:49\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]}],"startTime":1781738698696,"endTime":1781738699000.1926,"status":"failed","message":"","name":"E:/QMAI/src/components/novel/character-aura-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 渲染所有识别出的角色","status":"passed","title":"渲染所有识别出的角色","duration":374.4380000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 标题显示识别出 N 个角色","status":"passed","title":"标题显示识别出 N 个角色","duration":140.82160000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 未选时两个提取按钮显示 0 个角色","status":"passed","title":"未选时两个提取按钮显示 0 个角色","duration":118.08080000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 已选 1 个时按钮显示 1 个角色","status":"passed","title":"已选 1 个时按钮显示 1 个角色","duration":90.22299999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 包含全选主角配角按钮","status":"passed","title":"包含全选主角配角按钮","duration":133.61370000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 包含深度和简单提取两个按钮","status":"passed","title":"包含深度和简单提取两个按钮","duration":120.67500000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["CharacterSelectionPanel"],"fullName":"CharacterSelectionPanel 点击全选主角配角回调被调用","status":"passed","title":"点击全选主角配角回调被调用","duration":111.69669999999996,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698392,"endTime":1781738699481.6968,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/character-selection-panel.spec.tsx"},{"assertionResults":[{"ancestorTitles":["去AI味预览对话框"],"fullName":"去AI味预览对话框 open=true 时显示原文、去AI味稿、替换正文、另存草稿、取消","status":"passed","title":"open=true 时显示原文、去AI味稿、替换正文、另存草稿、取消","duration":2.5412000000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["去AI味预览对话框"],"fullName":"去AI味预览对话框 点击替换正文触发 onApply","status":"passed","title":"点击替换正文触发 onApply","duration":0.2630999999999801,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["去AI味预览对话框"],"fullName":"去AI味预览对话框 点击取消触发 onClose","status":"passed","title":"点击取消触发 onClose","duration":0.23860000000001946,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704299,"endTime":1781738704302.2632,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/de-ai-preview-dialog.test.tsx"},{"assertionResults":[{"ancestorTitles":["dismantling library visibility"],"fullName":"dismantling library visibility hides the dismantling library navigation entry in version 2.2.7+","status":"passed","title":"hides the dismantling library navigation entry in version 2.2.7+","duration":3.320600000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library visibility"],"fullName":"dismantling library visibility keeps the hidden dismantling sidebar disconnected from the visible workspace","status":"passed","title":"keeps the hidden dismantling sidebar disconnected from the visible workspace","duration":0.9800999999999931,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library visibility"],"fullName":"dismantling library visibility keeps the underlying dismantling implementation intact for later re-enable","status":"passed","title":"keeps the underlying dismantling implementation intact for later re-enable","duration":0.608099999999979,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703107,"endTime":1781738703111.6082,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/dismantling-view.spec.ts"},{"assertionResults":[{"ancestorTitles":["memory center dismantling visibility"],"fullName":"memory center dismantling visibility hides the dismantling memory library entry from the memory center list","status":"passed","title":"hides the dismantling memory library entry from the memory center list","duration":3.314399999999978,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704585,"endTime":1781738704588.3145,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/memory-center-dismantling.spec.ts"},{"assertionResults":[{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view loads memory data from a dedicated loader","status":"passed","title":"loads memory data from a dedicated loader","duration":2.241800000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view renders snapshot and memory sections for novel recall","status":"passed","title":"renders snapshot and memory sections for novel recall","duration":0.5416000000000167,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view provides open-file actions for memory pages","status":"passed","title":"provides open-file actions for memory pages","duration":0.28609999999997626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view opens snapshot and memory details inside the memory center instead of switching to wiki","status":"passed","title":"opens snapshot and memory details inside the memory center instead of switching to wiki","duration":0.35449999999997317,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view strips markdown frontmatter before rendering memory details","status":"passed","title":"strips markdown frontmatter before rendering memory details","duration":0.27480000000002747,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view restores the previous memory-center position after closing detail view","status":"passed","title":"restores the previous memory-center position after closing detail view","duration":0.27890000000002146,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view reads the selected memory entry from the shared store instead of rendering the overview grid itself","status":"passed","title":"reads the selected memory entry from the shared store instead of rendering the overview grid itself","duration":0.2891999999999939,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center-view"],"fullName":"memory-center-view shows a placeholder until the middle memory list chooses an entry","status":"passed","title":"shows a placeholder until the middle memory list chooses an entry","duration":0.2755000000000223,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703177,"endTime":1781738703181.2893,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/memory-center-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["TextTransformPreviewDialog"],"fullName":"TextTransformPreviewDialog renders generated content as editable when a change handler is provided","status":"passed","title":"renders generated content as editable when a change handler is provided","duration":271.6379999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738698268,"endTime":1781738698539.638,"status":"passed","message":"","name":"E:/QMAI/src/components/novel/text-transform-preview-dialog.spec.tsx"},{"assertionResults":[{"ancestorTitles":["新建小说项目弹窗"],"fullName":"新建小说项目弹窗 支持表单回车直接创建","status":"passed","title":"支持表单回车直接创建","duration":2.545199999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["新建小说项目弹窗"],"fullName":"新建小说项目弹窗 创建前会自动补齐默认目录并创建父目录","status":"passed","title":"创建前会自动补齐默认目录并创建父目录","duration":0.39400000000000546,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704617,"endTime":1781738704620.394,"status":"passed","message":"","name":"E:/QMAI/src/components/project/create-project-dialog.test.tsx"},{"assertionResults":[{"ancestorTitles":["search-view search module loading"],"fullName":"search-view search module loading does not load heavy search modules when the panel mounts","status":"passed","title":"does not load heavy search modules when the panel mounts","duration":2.633100000000013,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703348,"endTime":1781738703350.633,"status":"passed","message":"","name":"E:/QMAI/src/components/search/search-view.imports.test.ts"},{"assertionResults":[{"ancestorTitles":["ReviewCenterView six-dimension routing"],"fullName":"ReviewCenterView six-dimension routing passes an independent dimension key when a six-dimension tab is selected","status":"passed","title":"passes an independent dimension key when a six-dimension tab is selected","duration":11.421799999999962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ReviewCenterView six-dimension routing"],"fullName":"ReviewCenterView six-dimension routing keeps AI review routed to the original review view without a dimension key","status":"passed","title":"keeps AI review routed to the original review view without a dimension key","duration":0.8053999999999633,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701481,"endTime":1781738701493.8054,"status":"passed","message":"","name":"E:/QMAI/src/components/review/review-center-view.spec.tsx"},{"assertionResults":[{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 开始审稿时生成 runId 并写入 store","status":"passed","title":"开始审稿时生成 runId 并写入 store","duration":2.255800000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 审稿完成后通过 finishReviewRun 匹配 runId 写回结果","status":"passed","title":"审稿完成后通过 finishReviewRun 匹配 runId 写回结果","duration":0.2960000000000491,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 finally 块检查 runId 匹配后写回 running: false","status":"passed","title":"finally 块检查 runId 匹配后写回 running: false","duration":0.23260000000004766,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 错误时使用 finishReviewRun 写回 error","status":"passed","title":"错误时使用 finishReviewRun 写回 error","duration":0.2185999999999808,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 错误 UI 不遮挡已有结果和历史","status":"passed","title":"错误 UI 不遮挡已有结果和历史","duration":0.294399999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-view 长任务状态持久化"],"fullName":"review-view 长任务状态持久化 审稿结果卡片支持直接跳到对应正文编辑","status":"passed","title":"审稿结果卡片支持直接跳到对应正文编辑","duration":0.29820000000000846,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703037,"endTime":1781738703041.298,"status":"passed","message":"","name":"E:/QMAI/src/components/review/review-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["outline chat web research wiring"],"fullName":"outline chat web research wiring injects controlled web research into outline chat when the user asks for it","status":"passed","title":"injects controlled web research into outline chat when the user asks for it","duration":2.911699999999996,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704337,"endTime":1781738704339.9116,"status":"passed","message":"","name":"E:/QMAI/src/components/sources/outline-chat-web-research.spec.ts"},{"assertionResults":[{"ancestorTitles":["outline list import and extract state"],"fullName":"outline list import and extract state keeps the auto-extract toggle wired to novelConfig","status":"passed","title":"keeps the auto-extract toggle wired to novelConfig","duration":2.3052000000000135,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline list import and extract state"],"fullName":"outline list import and extract state registers extract task ids after import so progress shows immediately","status":"passed","title":"registers extract task ids after import so progress shows immediately","duration":0.36889999999999645,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline list import and extract state"],"fullName":"outline list import and extract state exposes a bulk outline ingest entry in the main outline toolbar","status":"passed","title":"exposes a bulk outline ingest entry in the main outline toolbar","duration":0.31529999999997926,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704341,"endTime":1781738704343.369,"status":"passed","message":"","name":"E:/QMAI/src/components/sources/sources-view.test.tsx"},{"assertionResults":[{"ancestorTitles":["settings contact support section"],"fullName":"settings contact support section adds a contact and support category to the settings sidebar","status":"passed","title":"adds a contact and support category to the settings sidebar","duration":2.6895000000000095,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings contact support section"],"fullName":"settings contact support section shows WeChat contact, WeChat pay, and Alipay pay images","status":"passed","title":"shows WeChat contact, WeChat pay, and Alipay pay images","duration":0.6741000000000099,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704278,"endTime":1781738704281.674,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/contact-support-section.spec.ts"},{"assertionResults":[{"ancestorTitles":["LLM Wiki model settings copied into QMAI"],"fullName":"LLM Wiki model settings copied into QMAI includes the LLM Wiki provider rows below the custom row","status":"passed","title":"includes the LLM Wiki provider rows below the custom row","duration":2.8125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["LLM Wiki model settings copied into QMAI"],"fullName":"LLM Wiki model settings copied into QMAI resolves DeepSeek to an OpenAI-compatible custom endpoint","status":"passed","title":"resolves DeepSeek to an OpenAI-compatible custom endpoint","duration":1.714400000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["LLM Wiki model settings copied into QMAI"],"fullName":"LLM Wiki model settings copied into QMAI resolves Azure OpenAI deployment settings into the Azure chat endpoint","status":"passed","title":"resolves Azure OpenAI deployment settings into the Azure chat endpoint","duration":0.7679999999999723,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["LLM Wiki model settings copied into QMAI"],"fullName":"LLM Wiki model settings copied into QMAI keeps local CLI provider options available in the resolved config","status":"passed","title":"keeps local CLI provider options available in the resolved config","duration":2.1281000000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["LLM Wiki model settings copied into QMAI"],"fullName":"LLM Wiki model settings copied into QMAI has Chinese labels for the copied LLM Wiki settings instead of placeholder question marks","status":"passed","title":"has Chinese labels for the copied LLM Wiki settings instead of placeholder question marks","duration":0.7291000000000167,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702487,"endTime":1781738702494.729,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/llm-wiki-model-settings.spec.ts"},{"assertionResults":[{"ancestorTitles":["resolveConfig"],"fullName":"resolveConfig defaults reasoning to auto instead of inheriting another preset's fallback","status":"passed","title":"defaults reasoning to auto instead of inheriting another preset's fallback","duration":3.095700000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveConfig"],"fullName":"resolveConfig keeps an explicit provider-level reasoning override","status":"passed","title":"keeps an explicit provider-level reasoning override","duration":0.3106999999999971,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703695,"endTime":1781738703699.3108,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/preset-resolver.test.ts"},{"assertionResults":[{"ancestorTitles":["settings hidden legacy import sections"],"fullName":"settings hidden legacy import sections does not expose source watch or scheduled import categories in settings","status":"passed","title":"does not expose source watch or scheduled import categories in settings","duration":2.654699999999991,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704582,"endTime":1781738704584.6548,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/settings-hidden-legacy-sections.spec.ts"},{"assertionResults":[{"ancestorTitles":["settings model categories"],"fullName":"settings model categories shows LLM, reranker, and embedding as independent settings categories","status":"passed","title":"shows LLM, reranker, and embedding as independent settings categories","duration":2.879700000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model categories"],"fullName":"settings model categories keeps embedding and reranker panels out of the LLM category body","status":"passed","title":"keeps embedding and reranker panels out of the LLM category body","duration":0.5422000000000367,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["settings model categories"],"fullName":"settings model categories uses the requested Chinese model category names","status":"passed","title":"uses the requested Chinese model category names","duration":0.6392000000000166,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704279,"endTime":1781738704282.6392,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/settings-model-categories.spec.ts"},{"assertionResults":[{"ancestorTitles":["settings-view 小说设置入口"],"fullName":"settings-view 小说设置入口 左侧分类包含 novel,用户可以进入小说模型测试页","status":"passed","title":"左侧分类包含 novel,用户可以进入小说模型测试页","duration":1.8727999999999838,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704758,"endTime":1781738704759.8728,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/settings-view.novel-category.test.tsx"},{"assertionResults":[{"ancestorTitles":["backupChapterFile"],"fullName":"backupChapterFile writes backups into .qmai/chapter-backups with a timestamped chapter name","status":"passed","title":"writes backups into .qmai/chapter-backups with a timestamped chapter name","duration":4.540899999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["backupChapterFile"],"fullName":"backupChapterFile surfaces write failures so callers can block overwrite","status":"passed","title":"surfaces write failures so callers can block overwrite","duration":2.4687000000000126,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702142,"endTime":1781738702148.4688,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-backup.spec.ts"},{"assertionResults":[{"ancestorTitles":["cleanGeneratedChapterContentForSave"],"fullName":"cleanGeneratedChapterContentForSave keeps only chapter prose when an answer includes metadata and citations","status":"passed","title":"keeps only chapter prose when an answer includes metadata and citations","duration":3.498899999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["cleanGeneratedChapterContentForSave"],"fullName":"cleanGeneratedChapterContentForSave removes hidden thinking and citation markers without deleting normal prose","status":"passed","title":"removes hidden thinking and citation markers without deleting normal prose","duration":0.43880000000001473,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703852,"endTime":1781738703856.4387,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-content-cleanup.test.ts"},{"assertionResults":[{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile forces chapter_number and title to match the target chapter","status":"passed","title":"forces chapter_number and title to match the target chapter","duration":6.6831000000000245,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile reattaches the original frontmatter when the model returns body-only content (issue #10)","status":"passed","title":"reattaches the original frontmatter when the model returns body-only content (issue #10)","duration":1.0307999999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile synthesizes default chapter frontmatter when neither side has one","status":"passed","title":"synthesizes default chapter frontmatter when neither side has one","duration":0.4158999999999651,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile prepends a chapter heading when the modified body has none","status":"passed","title":"prepends a chapter heading when the modified body has none","duration":0.349899999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile strips wrapping code fences and 【第N章】 markers from model output","status":"passed","title":"strips wrapping code fences and 【第N章】 markers from model output","duration":0.5045999999999822,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["normalizeChapterEditFile"],"fullName":"normalizeChapterEditFile still rejects an empty modification result","status":"passed","title":"still rejects an empty modification result","duration":0.6813999999999965,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701448,"endTime":1781738701457.6814,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-edit-file.spec.ts"},{"assertionResults":[{"ancestorTitles":["chapter import"],"fullName":"chapter import extracts Arabic and Chinese chapter numbers from file names","status":"passed","title":"extracts Arabic and Chinese chapter numbers from file names","duration":4.2666000000000395,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import matches chapter numbers after book and volume prefixes without treating volume as the chapter","status":"passed","title":"matches chapter numbers after book and volume prefixes without treating volume as the chapter","duration":0.49049999999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import sorts imported chapters by detected chapter number before unknown files","status":"passed","title":"sorts imported chapters by detected chapter number before unknown files","duration":1.176499999999976,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import collects and sorts importable chapter files from a selected folder before confirmation","status":"passed","title":"collects and sorts importable chapter files from a selected folder before confirmation","duration":1.8220999999999776,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import builds chapter markdown as final only when memory extraction is requested","status":"passed","title":"builds chapter markdown as final only when memory extraction is requested","duration":0.803799999999967,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import imports prefixed book-volume chapter files with clean chapter titles","status":"passed","title":"imports prefixed book-volume chapter files with clean chapter titles","duration":2.1043000000000234,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter import"],"fullName":"chapter import extracts imported chapter memories one by one and stops after cancellation","status":"passed","title":"extracts imported chapter memories one by one and stops after cancellation","duration":2.8985000000000127,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700769,"endTime":1781738700782.8984,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-import.spec.ts"},{"assertionResults":[{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts builds chapter wiki fields used by shared Wiki ingestion","status":"passed","title":"builds chapter wiki fields used by shared Wiki ingestion","duration":7.730700000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts keeps chapter ending state separate from the ending hook","status":"passed","title":"keeps chapter ending state separate from the ending hook","duration":0.9090999999999667,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts builds traceable shared Wiki update patch entries","status":"passed","title":"builds traceable shared Wiki update patch entries","duration":1.580600000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts 将 snapshot 的 Details 字段编入对应的 Wiki 条目 fields 中","status":"passed","title":"将 snapshot 的 Details 字段编入对应的 Wiki 条目 fields 中","duration":1.030499999999961,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts 不包含 Details 字段的旧快照仍可正常构建输出(向后兼容)","status":"passed","title":"不包含 Details 字段的旧快照仍可正常构建输出(向后兼容)","duration":1.616199999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts builds graph derivation candidates with source-backed nodes and edges","status":"passed","title":"builds graph derivation candidates with source-backed nodes and edges","duration":0.9062999999999874,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts merges character aliases into one canonical character before building memory output","status":"passed","title":"merges character aliases into one canonical character before building memory output","duration":2.059000000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts builds separated keyword search and vector index text","status":"passed","title":"builds separated keyword search and vector index text","duration":0.8138999999999896,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter ingest output contracts"],"fullName":"chapter ingest output contracts merges formal chapters into one shared Wiki state with traceable sources","status":"passed","title":"merges formal chapters into one shared Wiki state with traceable sources","duration":1.5785000000000196,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700361,"endTime":1781738700379.5786,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-ingest-output.test.ts"},{"assertionResults":[{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory 刷新同步后的图谱缓存,避免章节生成继续读取旧实体","status":"passed","title":"刷新同步后的图谱缓存,避免章节生成继续读取旧实体","duration":30.212099999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory 同步大纲快照时清理旧格式来源名留下的旧实体","status":"passed","title":"同步大纲快照时清理旧格式来源名留下的旧实体","duration":1.9728999999999814,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory 鍚屾澶х翰蹇収鏃朵細鐢熸垚鍙洖鐪嬬殑鍘嗗彶蹇収","status":"passed","title":"鍚屾澶х翰蹇収鏃朵細鐢熸垚鍙洖鐪嬬殑鍘嗗彶蹇収","duration":2.06189999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory writes revision metadata to the current snapshot and archives the superseded snapshot as history","status":"passed","title":"writes revision metadata to the current snapshot and archives the superseded snapshot as history","duration":2.6363000000000056,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory removes stale projection pages that only belong to the superseded snapshot id","status":"passed","title":"removes stale projection pages that only belong to the superseded snapshot id","duration":1.5400000000000205,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["syncSnapshotToMemory"],"fullName":"syncSnapshotToMemory clears derived memory files when deleting the last snapshot","status":"passed","title":"clears derived memory files when deleting the last snapshot","duration":4.602100000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699738,"endTime":1781738699781.602,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-ingest.sync.test.ts"},{"assertionResults":[{"ancestorTitles":["fact-model contracts"],"fullName":"fact-model contracts allows only verified facts to graph promotion","status":"passed","title":"allows only verified facts to graph promotion","duration":2.282099999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-store"],"fullName":"fact-store appends fact status transitions without overwriting history","status":"passed","title":"appends fact status transitions without overwriting history","duration":1.2578000000000316,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-store"],"fullName":"fact-store links events by fact_batch_id","status":"passed","title":"links events by fact_batch_id","duration":1.0120999999999754,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-pipeline"],"fullName":"chapter-pipeline forwards project and chapter path to injected ingest function","status":"passed","title":"forwards project and chapter path to injected ingest function","duration":0.5492000000000417,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-pipeline"],"fullName":"chapter-pipeline forwards resolved review model to injected ingest function","status":"passed","title":"forwards resolved review model to injected ingest function","duration":1.9014000000000237,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter-pipeline"],"fullName":"chapter-pipeline propagates errors from injected ingest function","status":"passed","title":"propagates errors from injected ingest function","duration":2.1697000000000344,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701383,"endTime":1781738701393.1697,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-pipeline.test.ts"},{"assertionResults":[{"ancestorTitles":["decideChapterSaveStrategy"],"fullName":"decideChapterSaveStrategy always saves to the next chapter when there is no explicit generated chapter target","status":"passed","title":"always saves to the next chapter when there is no explicit generated chapter target","duration":2.8974000000000046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideChapterSaveStrategy"],"fullName":"decideChapterSaveStrategy still saves to the next chapter even when the selected chapter already has content","status":"passed","title":"still saves to the next chapter even when the selected chapter already has content","duration":0.3593000000000188,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideChapterSaveStrategy"],"fullName":"decideChapterSaveStrategy still creates the explicit target chapter when it does not yet exist","status":"passed","title":"still creates the explicit target chapter when it does not yet exist","duration":0.31380000000001473,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideChapterSaveStrategy"],"fullName":"decideChapterSaveStrategy falls back to the next chapter when the explicit target chapter already exists","status":"passed","title":"falls back to the next chapter when the explicit target chapter already exists","duration":0.269299999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["decideChapterSaveStrategy"],"fullName":"decideChapterSaveStrategy still falls back to the next chapter when the explicit generated target chapter already exists","status":"passed","title":"still falls back to the next chapter when the explicit generated target chapter already exists","duration":0.24219999999996844,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectGeneratedTargetChapterNumber"],"fullName":"detectGeneratedTargetChapterNumber detects an explicit generated chapter number from the content","status":"passed","title":"detects an explicit generated chapter number from the content","duration":0.6038000000000352,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703007,"endTime":1781738703012.6038,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-save-strategy.spec.ts"},{"assertionResults":[{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat uses the selected chapter plus one for continue-next-chapter requests","status":"passed","title":"uses the selected chapter plus one for continue-next-chapter requests","duration":3.4938000000000216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat uses the next available chapter when no chapter is selected","status":"passed","title":"uses the next available chapter when no chapter is selected","duration":1.1196999999999662,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat keeps an explicit chapter number instead of advancing it","status":"passed","title":"keeps an explicit chapter number instead of advancing it","duration":0.34039999999998827,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat does not force a target chapter for ordinary current-chapter continuation","status":"passed","title":"does not force a target chapter for ordinary current-chapter continuation","duration":0.3369999999999891,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat advances past the chapter generated in this conversation even when it is not saved yet (issue #6)","status":"passed","title":"advances past the chapter generated in this conversation even when it is not saved yet (issue #6)","duration":0.44149999999996226,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveTargetChapterNumberForChat"],"fullName":"resolveTargetChapterNumberForChat keeps the library-derived next chapter when it is already ahead of the conversation","status":"passed","title":"keeps the library-derived next chapter when it is already ahead of the conversation","duration":0.3946000000000254,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLastGeneratedChapterNumber"],"fullName":"detectLastGeneratedChapterNumber reads the deep-generation target chapter marker from the latest assistant message","status":"passed","title":"reads the deep-generation target chapter marker from the latest assistant message","duration":0.5076999999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLastGeneratedChapterNumber"],"fullName":"detectLastGeneratedChapterNumber reads chapter heading lines from generated chapter content","status":"passed","title":"reads chapter heading lines from generated chapter content","duration":0.31440000000003465,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["detectLastGeneratedChapterNumber"],"fullName":"detectLastGeneratedChapterNumber ignores ordinary answers that merely mention chapter numbers","status":"passed","title":"ignores ordinary answers that merely mention chapter numbers","duration":0.3351999999999862,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702152,"endTime":1781738702160.3352,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chapter-utils.spec.ts"},{"assertionResults":[{"ancestorTitles":["buildCharacterAuraContext"],"fullName":"buildCharacterAuraContext matches a bound character from extra chapter context when the user request only contains a chapter number","status":"passed","title":"matches a bound character from extra chapter context when the user request only contains a chapter number","duration":4.001500000000021,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703802,"endTime":1781738703806.0015,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/character-aura.spec.ts"},{"assertionResults":[{"ancestorTitles":["character-aura"],"fullName":"character-aura listBindableNovelCharacters 只返回人物名,不混入人物小传里的字段标题","status":"passed","title":"listBindableNovelCharacters 只返回人物名,不混入人物小传里的字段标题","duration":21.26830000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["character-aura"],"fullName":"character-aura listBindableNovelCharacters 会排除关系线和群像集合标题,只保留可绑定的人物角色","status":"passed","title":"listBindableNovelCharacters 会排除关系线和群像集合标题,只保留可绑定的人物角色","duration":0.9553999999999974,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700294,"endTime":1781738700315.9553,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/character-aura.test.ts"},{"assertionResults":[{"ancestorTitles":["isChatEditRequest"],"fullName":"isChatEditRequest detects edit requests and ignores normal chat","status":"passed","title":"detects edit requests and ignores normal chat","duration":2.248899999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChatEditTarget"],"fullName":"resolveChatEditTarget defaults to the selected chapter when the user asks to modify this chapter","status":"passed","title":"defaults to the selected chapter when the user asks to modify this chapter","duration":1.7269000000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChatEditTarget"],"fullName":"resolveChatEditTarget resolves chapter 1 when the user explicitly asks to modify chapter 1","status":"passed","title":"resolves chapter 1 when the user explicitly asks to modify chapter 1","duration":0.5482999999999834,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChatEditTarget"],"fullName":"resolveChatEditTarget resolves the previous 10 chapters from the selected chapter anchor","status":"passed","title":"resolves the previous 10 chapters from the selected chapter anchor","duration":0.3838000000000079,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChatEditTarget"],"fullName":"resolveChatEditTarget returns an error when no chapter is selected for an implicit chapter edit","status":"passed","title":"returns an error when no chapter is selected for an implicit chapter edit","duration":0.27780000000001337,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseStructuredChapterEdits"],"fullName":"parseStructuredChapterEdits parses structured multi-chapter output into per-chapter content","status":"passed","title":"parses structured multi-chapter output into per-chapter content","duration":0.48869999999999436,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateStructuredChapterEditResult"],"fullName":"validateStructuredChapterEditResult accepts matching multi-chapter results","status":"passed","title":"accepts matching multi-chapter results","duration":0.5792000000000144,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateStructuredChapterEditResult"],"fullName":"validateStructuredChapterEditResult rejects when returned chapter count does not match targets","status":"passed","title":"rejects when returned chapter count does not match targets","duration":0.2658999999999878,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateStructuredChapterEditResult"],"fullName":"validateStructuredChapterEditResult rejects when one target chapter block is missing","status":"passed","title":"rejects when one target chapter block is missing","duration":0.266900000000021,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702491,"endTime":1781738702498.579,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/chat-edit-mode.spec.ts"},{"assertionResults":[{"ancestorTitles":["DataSourceRegistry"],"fullName":"DataSourceRegistry replaces undefined snapshot payloads with default values","status":"passed","title":"replaces undefined snapshot payloads with default values","duration":3.4104999999999563,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DataSourceRegistry"],"fullName":"DataSourceRegistry replaces undefined scalar payloads with source defaults","status":"passed","title":"replaces undefined scalar payloads with source defaults","duration":0.6528999999999883,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703711,"endTime":1781738703715.6528,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/context-data-source.spec.ts"},{"assertionResults":[{"ancestorTitles":["de-ai-adapter skill binding"],"fullName":"de-ai-adapter skill binding binds the novel de-ai prompt to the bundled skill markdown","status":"failed","title":"binds the novel de-ai prompt to the bundled skill markdown","duration":11.946000000000026,"failureMessages":["AssertionError: expected 'import type { ChatMessage } from \"@/l…' to contain 'QM-QUAI.md?raw'\n at E:/QMAI/src/lib/novel/de-ai-adapter-skill.test.ts:9:20\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]}],"startTime":1781738696579,"endTime":1781738696590.946,"status":"failed","message":"","name":"E:/QMAI/src/lib/novel/de-ai-adapter-skill.test.ts"},{"assertionResults":[{"ancestorTitles":["de-ai-adapter","buildDeAiSystemPrompt"],"fullName":"de-ai-adapter buildDeAiSystemPrompt 返回来自小说去AI味技能文件的 system prompt","status":"failed","title":"返回来自小说去AI味技能文件的 system prompt","duration":13.766900000000078,"failureMessages":["AssertionError: expected '---\\r\\nname: de-AI-writing\\r\\ndescrip…' to contain '# 小说去 AI 味技能'\n at E:/QMAI/src/lib/novel/de-ai-adapter.test.ts:14:22\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","buildDeAiSystemPrompt"],"fullName":"de-ai-adapter buildDeAiSystemPrompt buildDeAiSystemPrompt 与 QM-QUAI system prompt 保持一致并包含小说补充规则","status":"failed","title":"buildDeAiSystemPrompt 与 QM-QUAI system prompt 保持一致并包含小说补充规则","duration":5.540399999999977,"failureMessages":["AssertionError: expected '---\\r\\nname: de-AI-writing\\r\\ndescrip…' to contain '中文小说去 AI 味补充规则'\n at E:/QMAI/src/lib/novel/de-ai-adapter.test.ts:24:41\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:302:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:26\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2326:20\n at new Promise (<anonymous>)\n at runWithCancel (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2323:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2305:20\n at new Promise (<anonymous>)\n at runWithTimeout (file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2272:10)\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:2955:64"],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","buildDeAiRewriteMessages"],"fullName":"de-ai-adapter buildDeAiRewriteMessages 返回两条消息,第一条是 system 角色","status":"passed","title":"返回两条消息,第一条是 system 角色","duration":1.0837999999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","buildDeAiRewriteMessages"],"fullName":"de-ai-adapter buildDeAiRewriteMessages 第二条消息包含原文和 QM-QUAI skill 指令","status":"passed","title":"第二条消息包含原文和 QM-QUAI skill 指令","duration":0.33960000000001855,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","buildDeAiRewriteMessages"],"fullName":"de-ai-adapter buildDeAiRewriteMessages 传入空字符串时抛出异常","status":"passed","title":"传入空字符串时抛出异常","duration":0.9441999999999098,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","injectDeAiDirective"],"fullName":"de-ai-adapter injectDeAiDirective enabled=true 时在前面拼接去AI味指令并保留原输入","status":"passed","title":"enabled=true 时在前面拼接去AI味指令并保留原输入","duration":0.344600000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","injectDeAiDirective"],"fullName":"de-ai-adapter injectDeAiDirective enabled=false 时原样返回","status":"passed","title":"enabled=false 时原样返回","duration":0.21050000000002456,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","injectDeAiDirective"],"fullName":"de-ai-adapter injectDeAiDirective enabled=true 且 content=\"\" 时返回纯前缀","status":"passed","title":"enabled=true 且 content=\"\" 时返回纯前缀","duration":0.29769999999996344,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["de-ai-adapter","injectDeAiDirective"],"fullName":"de-ai-adapter injectDeAiDirective enabled=false 且 content=\"\" 时返回空字符串","status":"passed","title":"enabled=false 且 content=\"\" 时返回空字符串","duration":0.2477000000000089,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738697003,"endTime":1781738697026.2478,"status":"failed","message":"","name":"E:/QMAI/src/lib/novel/de-ai-adapter.test.ts"},{"assertionResults":[{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration keeps the word-count target in planning and draft prompts without forcing later review stages","status":"passed","title":"keeps the word-count target in planning and draft prompts without forcing later review stages","duration":3.6900000000000546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration only enables deep generation for write-chapter routes when the switch is on","status":"passed","title":"only enables deep generation for write-chapter routes when the switch is on","duration":0.48489999999992506,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration publishes stage results into thinking and returns the final simple review result when review passes","status":"passed","title":"publishes stage results into thinking and returns the final simple review result when review passes","duration":19.826099999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration shows a visible golden-three hint in thinking when generating the first chapter","status":"passed","title":"shows a visible golden-three hint in thinking when generating the first chapter","duration":1.2801999999999225,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration uses safe defaults when a context pack is missing optional array fields","status":"passed","title":"uses safe defaults when a context pack is missing optional array fields","duration":4.713799999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration falls back to an empty context pack when context building throws","status":"passed","title":"falls back to an empty context pack when context building throws","duration":2.5061000000000604,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration revises once when review returns blocking errors","status":"passed","title":"revises once when review returns blocking errors","duration":1.940900000000056,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration automatically expands a too-short draft before review and final output","status":"passed","title":"automatically expands a too-short draft before review and final output","duration":3.1762999999999693,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration does not force expansion after final polish even when the result is short","status":"passed","title":"does not force expansion after final polish even when the result is short","duration":1.0084000000000515,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration trims runaway repeated chapter output before review and final polish","status":"passed","title":"trims runaway repeated chapter output before review and final polish","duration":2.834899999999948,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration does not stop the AI chat stream at the old chapter hard max","status":"passed","title":"does not stop the AI chat stream at the old chapter hard max","duration":2.016200000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration sends long drafts directly to review without a stage 4 length rewrite","status":"passed","title":"sends long drafts directly to review without a stage 4 length rewrite","duration":2.22829999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration does not optimize the stage 3 draft in stage 4 before review","status":"passed","title":"does not optimize the stage 3 draft in stage 4 before review","duration":1.8815999999999349,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration does not retry stage 4 length optimization when the draft stays long","status":"passed","title":"does not retry stage 4 length optimization when the draft stays long","duration":1.9797999999999547,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration does not force a length rewrite after final polish","status":"passed","title":"does not force a length rewrite after final polish","duration":1.6009000000000242,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration resumes from a saved review checkpoint instead of regenerating earlier stages","status":"passed","title":"resumes from a saved review checkpoint instead of regenerating earlier stages","duration":1.052500000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration still treats provider-side cancellation as an error when there is no local length cutoff","status":"passed","title":"still treats provider-side cancellation as an error when there is no local length cutoff","duration":2.6562000000000126,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration stops before review when the user cancels during draft streaming","status":"passed","title":"stops before review when the user cancels during draft streaming","duration":2.232099999999946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepChapterGeneration"],"fullName":"runDeepChapterGeneration forwards the stop signal into the review stage","status":"passed","title":"forwards the stop signal into the review stage","duration":3.6060000000001082,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699450,"endTime":1781738699510.606,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/deep-chapter-generation.spec.ts"},{"assertionResults":[{"ancestorTitles":["deep chapter prompts with golden three chapter constraints"],"fullName":"deep chapter prompts with golden three chapter constraints injects golden constraints into every deep generation prompt stage","status":"passed","title":"injects golden constraints into every deep generation prompt stage","duration":3.7019000000000233,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704085,"endTime":1781738704088.702,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/deep-chapter-golden.spec.ts"},{"assertionResults":[{"ancestorTitles":["resolveChapterLengthSpec"],"fullName":"resolveChapterLengthSpec keeps the built-in defaults when no target is configured","status":"passed","title":"keeps the built-in defaults when no target is configured","duration":2.6843000000000075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChapterLengthSpec"],"fullName":"resolveChapterLengthSpec derives all thresholds from a configured chapter target (issue #8)","status":"passed","title":"derives all thresholds from a configured chapter target (issue #8)","duration":0.5769999999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChapterLengthSpec"],"fullName":"resolveChapterLengthSpec scales output token budget up for long chapters","status":"passed","title":"scales output token budget up for long chapters","duration":0.2691000000000372,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveChapterLengthSpec"],"fullName":"resolveChapterLengthSpec clamps unreasonable targets","status":"passed","title":"clamps unreasonable targets","duration":0.3144999999999527,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["chapter prompts honor the configured length spec"],"fullName":"chapter prompts honor the configured length spec injects the configured target into brief and draft prompts","status":"passed","title":"injects the configured target into brief and draft prompts","duration":0.8338999999999714,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703647,"endTime":1781738703652.834,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/deep-chapter-prompts.spec.ts"},{"assertionResults":[{"ancestorTitles":["runDeepOutlineGeneration"],"fullName":"runDeepOutlineGeneration falls back safely when outline context is missing","status":"passed","title":"falls back safely when outline context is missing","duration":3.937999999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepOutlineGeneration"],"fullName":"runDeepOutlineGeneration publishes staged outline thinking and returns the final outline","status":"passed","title":"publishes staged outline thinking and returns the final outline","duration":0.7509999999999764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["runDeepOutlineGeneration"],"fullName":"runDeepOutlineGeneration streams outline stage content into thinking while each stage is generating","status":"passed","title":"streams outline stage content into thinking while each stage is generating","duration":1.754000000000019,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702668,"endTime":1781738702674.754,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/deep-outline-generation.spec.ts"},{"assertionResults":[{"ancestorTitles":["deleteNovelSourceMemory"],"fullName":"deleteNovelSourceMemory deletes chapter snapshots by chapter_number before the page disappears","status":"passed","title":"deletes chapter snapshots by chapter_number before the page disappears","duration":4.578499999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteNovelSourceMemory"],"fullName":"deleteNovelSourceMemory deletes outline snapshots using the same filename hash as outline ingest","status":"passed","title":"deletes outline snapshots using the same filename hash as outline ingest","duration":1.3653999999999655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteNovelSourceMemory"],"fullName":"deleteNovelSourceMemory deletes entity pages that only came from the deleted chapter source","status":"passed","title":"deletes entity pages that only came from the deleted chapter source","duration":1.2608000000000175,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteNovelSourceMemory"],"fullName":"deleteNovelSourceMemory preserves entity pages that still reference other sources","status":"passed","title":"preserves entity pages that still reference other sources","duration":2.404200000000003,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701477,"endTime":1781738701486.4043,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/delete-source-memory.spec.ts"},{"assertionResults":[{"ancestorTitles":["six-dimension review adapter"],"fullName":"six-dimension review adapter defines six independent professional review workflows","status":"passed","title":"defines six independent professional review workflows","duration":3.606200000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension review adapter"],"fullName":"six-dimension review adapter builds a dimension-specific prompt with shared context and strict output rules","status":"passed","title":"builds a dimension-specific prompt with shared context and strict output rules","duration":0.6650999999999954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension review adapter"],"fullName":"six-dimension review adapter runs one dimension with two high-reasoning model calls and publishes thinking","status":"passed","title":"runs one dimension with two high-reasoning model calls and publishes thinking","duration":2.4326000000000363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension review adapter"],"fullName":"six-dimension review adapter runs all six dimensions with one shared context and continues after one failure","status":"passed","title":"runs all six dimensions with one shared context and continues after one failure","duration":1.941000000000031,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701844,"endTime":1781738701851.941,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/dimension-review-adapter.spec.ts"},{"assertionResults":[{"ancestorTitles":["dismantling library"],"fullName":"dismantling library stores dismantling data in an isolated project cache path","status":"passed","title":"stores dismantling data in an isolated project cache path","duration":2.026900000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library splits imported text into ordered chapters without writing to novel memory","status":"passed","title":"splits imported text into ordered chapters without writing to novel memory","duration":4.759999999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library auto-detects chapter headings from a full imported novel with volumes and full-width digits","status":"passed","title":"auto-detects chapter headings from a full imported novel with volumes and full-width digits","duration":0.8850000000000477,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library auto-detects chapters when imported document extraction keeps headings inside paragraphs","status":"passed","title":"auto-detects chapters when imported document extraction keeps headings inside paragraphs","duration":1.0272999999999683,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library extracts a reader-style full novel text into its complete chapter catalog","status":"passed","title":"extracts a reader-style full novel text into its complete chapter catalog","duration":0.7458000000000311,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library falls back to reading the original imported book when preprocessing skips text files","status":"passed","title":"falls back to reading the original imported book when preprocessing skips text files","duration":0.3517999999999688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library deduplicates dismantling projects with the same normalized title","status":"passed","title":"deduplicates dismantling projects with the same normalized title","duration":0.8512999999999806,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library selects only the requested pending chapters for one batch","status":"passed","title":"selects only the requested pending chapters for one batch","duration":0.45249999999998636,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library builds an analysis prompt that keeps dismantling memory separate from current novel facts","status":"passed","title":"builds an analysis prompt that keeps dismantling memory separate from current novel facts","duration":0.43570000000005393,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library builds a web research prompt for hot-topic and webpage dismantling without mixing novel memory","status":"passed","title":"builds a web research prompt for hot-topic and webpage dismantling without mixing novel memory","duration":0.4230000000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["dismantling library"],"fullName":"dismantling library builds a chat directive that references structure but forbids copying original content","status":"passed","title":"builds a chat directive that references structure but forbids copying original content","duration":0.29479999999995243,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700888,"endTime":1781738700900.295,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/dismantling.spec.ts"},{"assertionResults":[{"ancestorTitles":["entry-projection"],"fullName":"entry-projection aggregates only facts with canon_status verified","status":"passed","title":"aggregates only facts with canon_status verified","duration":4.0464000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["entry-projection"],"fullName":"entry-projection returns an empty array when no verified facts exist","status":"passed","title":"returns an empty array when no verified facts exist","duration":0.3620999999999981,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703492,"endTime":1781738703497.362,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/entry-projection.test.ts"},{"assertionResults":[{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot returns empty results for empty snapshot list","status":"passed","title":"returns empty results for empty snapshot list","duration":2.641200000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot returns empty results for single snapshot","status":"passed","title":"returns empty results for single snapshot","duration":0.376700000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot returns empty results for two consistent snapshots","status":"passed","title":"returns empty results for two consistent snapshots","duration":1.1630999999999858,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot detects character state jump across 2+ levels","status":"passed","title":"detects character state jump across 2+ levels","duration":0.7760000000000105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot does not flag gradual state change","status":"passed","title":"does not flag gradual state change","duration":0.30860000000001264,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot handles non-standard state labels","status":"passed","title":"handles non-standard state labels","duration":0.4275000000000091,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot does not flag location changes as conflicts when chapters switch places","status":"passed","title":"does not flag location changes as conflicts when chapters switch places","duration":0.30539999999996326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot detects item holder change without transfer event","status":"passed","title":"detects item holder change without transfer event","duration":0.3399000000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot detects organization leader flip without power change event","status":"passed","title":"detects organization leader flip without power change event","duration":0.3708000000000311,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot detects setting conflict with contradictory facts","status":"passed","title":"detects setting conflict with contradictory facts","duration":0.5626000000000317,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot detects timeline conflict with exclusive events","status":"passed","title":"detects timeline conflict with exclusive events","duration":0.38189999999997326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot returns rule engine time in report","status":"passed","title":"returns rule engine time in report","duration":0.2706999999999766,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot verifyFactCheckLlm returns unchanged results when list is empty","status":"passed","title":"verifyFactCheckLlm returns unchanged results when list is empty","duration":0.4960999999999558,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot verifyFactCheckLlm returns unchanged results when all confidence is 1","status":"passed","title":"verifyFactCheckLlm returns unchanged results when all confidence is 1","duration":0.304300000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["fact-snapshot"],"fullName":"fact-snapshot verifyFactCheckLlm handles no usable LLM gracefully","status":"passed","title":"verifyFactCheckLlm handles no usable LLM gracefully","duration":37.34269999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699335,"endTime":1781738699382.3428,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/fact-snapshot.test.ts"},{"assertionResults":[{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt returns full score for empty store","status":"passed","title":"returns full score for empty store","duration":3.6539000000000215,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt marks recently planted as normal","status":"passed","title":"marks recently planted as normal","duration":0.4551999999999907,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt marks stale planted as critical","status":"passed","title":"marks stale planted as critical","duration":0.3241999999999621,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt marks stale advanced as warning","status":"passed","title":"marks stale advanced as warning","duration":0.273399999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt handles resolved items correctly","status":"passed","title":"handles resolved items correctly","duration":0.27710000000001855,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["foreshadowing-debt"],"fullName":"foreshadowing-debt accepts custom thresholds","status":"passed","title":"accepts custom thresholds","duration":0.27370000000001937,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703307,"endTime":1781738703312.277,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/foreshadowing-debt.test.ts"},{"assertionResults":[{"ancestorTitles":["generation-history"],"fullName":"generation-history 保存连贯性检查历史到项目内独立文件","status":"passed","title":"保存连贯性检查历史到项目内独立文件","duration":7.238900000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generation-history"],"fullName":"generation-history 按类型读取历史并按创建时间倒序展示","status":"passed","title":"按类型读取历史并按创建时间倒序展示","duration":12.939200000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generation-history"],"fullName":"generation-history 删除历史记录时移动对应历史文件到回收站","status":"passed","title":"删除历史记录时移动对应历史文件到回收站","duration":1.2591999999999643,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700176,"endTime":1781738700198.2593,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/generation-history.test.ts"},{"assertionResults":[{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints treats first-three and opening requests as first chapter plus chapter two and three directions","status":"passed","title":"treats first-three and opening requests as first chapter plus chapter two and three directions","duration":3.1218000000000075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints treats first chapter and opening synonyms as golden opening requests","status":"passed","title":"treats first chapter and opening synonyms as golden opening requests","duration":0.9140999999999622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints keeps explicit second and third chapter requests chapter-only","status":"passed","title":"keeps explicit second and third chapter requests chapter-only","duration":0.9014999999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints does not enable golden constraints for ordinary chapter continuation","status":"passed","title":"does not enable golden constraints for ordinary chapter continuation","duration":0.25430000000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints prefers the resolved chapter number over incidental 开篇/第一章 wording (issue #9)","status":"passed","title":"prefers the resolved chapter number over incidental 开篇/第一章 wording (issue #9)","duration":0.3159999999999741,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints does not enable golden constraints from 开篇 writing requirements inside a longer prompt","status":"passed","title":"does not enable golden constraints from 开篇 writing requirements inside a longer prompt","duration":0.21350000000001046,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["golden three chapter constraints"],"fullName":"golden three chapter constraints does not enable golden constraints when polishing mentions 开篇","status":"passed","title":"does not enable golden constraints when polishing mentions 开篇","duration":0.2953000000000543,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702210,"endTime":1781738702216.316,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/golden-three-chapters.spec.ts"},{"assertionResults":[{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki snapshotToGraphEdges 会把纯名称关系边归一到已知实体节点","status":"passed","title":"snapshotToGraphEdges 会把纯名称关系边归一到已知实体节点","duration":6.114499999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki 将单次 WikiUpdatePatch 的每个条目写入对应的实体页面","status":"passed","title":"将单次 WikiUpdatePatch 的每个条目写入对应的实体页面","duration":5.897199999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki 连续两次调用时对同一实体执行合并追加,不覆盖原有内容","status":"passed","title":"连续两次调用时对同一实体执行合并追加,不覆盖原有内容","duration":1.1721000000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki 会跳过章节、事件和整句型事实条目,只保留长期实体","status":"passed","title":"会跳过章节、事件和整句型事实条目,只保留长期实体","duration":0.8742999999999483,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki writeSnapshotToWiki 只会落长期实体,不再创建章节页和事件页","status":"passed","title":"writeSnapshotToWiki 只会落长期实体,不再创建章节页和事件页","duration":1.668299999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writePatchFieldsToWiki"],"fullName":"writePatchFieldsToWiki 实体页面保留 writeSnapshotToWiki 写入的关系链接,buildWikiGraph 可继续解析","status":"passed","title":"实体页面保留 writeSnapshotToWiki 写入的关系链接,buildWikiGraph 可继续解析","duration":0.9495000000000005,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700781,"endTime":1781738700797.9495,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/graph-adapter.test.ts"},{"assertionResults":[{"ancestorTitles":["graph-projection"],"fullName":"graph-projection generates active edges only from verified facts","status":"passed","title":"generates active edges only from verified facts","duration":3.526999999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["graph-projection"],"fullName":"graph-projection returns empty array when no verified facts","status":"passed","title":"returns empty array when no verified facts","duration":0.5332000000000221,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703241,"endTime":1781738703245.5332,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/graph-projection.test.ts"},{"assertionResults":[{"ancestorTitles":["memory-center"],"fullName":"memory-center builds newest-first snapshot cards and trims long change lists","status":"passed","title":"builds newest-first snapshot cards and trims long change lists","duration":4.8034000000000106,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center"],"fullName":"memory-center parses headed memory markdown into preview sections","status":"passed","title":"parses headed memory markdown into preview sections","duration":1.1655999999999835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center"],"fullName":"memory-center builds memory center stats from snapshots and memory previews","status":"passed","title":"builds memory center stats from snapshots and memory previews","duration":0.7153999999999883,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-center"],"fullName":"memory-center loadMemoryCenterData 保留最近 10 条章节快照,不会被意外裁成 6 条","status":"passed","title":"loadMemoryCenterData 保留最近 10 条章节快照,不会被意外裁成 6 条","duration":2.915399999999977,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701574,"endTime":1781738701583.9153,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/memory-center.test.ts"},{"assertionResults":[{"ancestorTitles":["memory-rebuild"],"fullName":"memory-rebuild 只接受有效章节范围内的快照","status":"passed","title":"只接受有效章节范围内的快照","duration":2.629099999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-rebuild"],"fullName":"memory-rebuild 会把不确定内容写入候选区,并过滤泛化人物状态","status":"passed","title":"会把不确定内容写入候选区,并过滤泛化人物状态","duration":2.9490000000000123,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["memory-rebuild"],"fullName":"memory-rebuild 会拦住句子型事件,只保留稳定命名事件","status":"passed","title":"会拦住句子型事件,只保留稳定命名事件","duration":0.45800000000002683,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702676,"endTime":1781738702682.458,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/memory-rebuild.test.ts"},{"assertionResults":[{"ancestorTitles":["testNovelModel"],"fullName":"testNovelModel uses the task-specific model when configured","status":"passed","title":"uses the task-specific model when configured","duration":3.8151000000000295,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["testNovelModel"],"fullName":"testNovelModel falls back to the main model when the novel model is empty","status":"passed","title":"falls back to the main model when the novel model is empty","duration":0.48790000000002465,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["testNovelModel"],"fullName":"testNovelModel throws when the model returns empty content","status":"passed","title":"throws when the model returns empty content","duration":2.161800000000028,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702448,"endTime":1781738702455.1619,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/novel-model-test.test.ts"},{"assertionResults":[{"ancestorTitles":["长篇稳定性测试","章节快照读写性能"],"fullName":"长篇稳定性测试 章节快照读写性能 能正确列出 1000 个快照文件","status":"passed","title":"能正确列出 1000 个快照文件","duration":5.195699999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","章节快照读写性能"],"fullName":"长篇稳定性测试 章节快照读写性能 快照序号排序正确","status":"passed","title":"快照序号排序正确","duration":1.369199999999978,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","章节快照读写性能"],"fullName":"长篇稳定性测试 章节快照读写性能 能正确加载单个快照","status":"passed","title":"能正确加载单个快照","duration":1.6475000000000364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","上下文包 Token 控制"],"fullName":"长篇稳定性测试 上下文包 Token 控制 contextPackToPrompt 在无预算时输出完整内容","status":"passed","title":"contextPackToPrompt 在无预算时输出完整内容","duration":4.307099999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","上下文包 Token 控制"],"fullName":"长篇稳定性测试 上下文包 Token 控制 contextPackToPrompt 在有 Token 预算时会裁剪","status":"passed","title":"contextPackToPrompt 在有 Token 预算时会裁剪","duration":1.4718999999998914,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","上下文包 Token 控制"],"fullName":"长篇稳定性测试 上下文包 Token 控制 Token 预算为 0 时不裁剪","status":"passed","title":"Token 预算为 0 时不裁剪","duration":1.2246999999999844,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","上下文包 Token 控制"],"fullName":"长篇稳定性测试 上下文包 Token 控制 回看窗口不超出章节边界","status":"passed","title":"回看窗口不超出章节边界","duration":0.5202999999999065,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","上下文包 Token 控制"],"fullName":"长篇稳定性测试 上下文包 Token 控制 大量摘要的上下文包不超过合理长度","status":"passed","title":"大量摘要的上下文包不超过合理长度","duration":0.9064999999999372,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","草稿不污染 Canon 保护"],"fullName":"长篇稳定性测试 草稿不污染 Canon 保护 非 final 状态的章节不应被摄取","status":"passed","title":"非 final 状态的章节不应被摄取","duration":0.6094000000000506,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["长篇稳定性测试","草稿不污染 Canon 保护"],"fullName":"长篇稳定性测试 草稿不污染 Canon 保护 状态为 outline 的页面不被当作章节","status":"passed","title":"状态为 outline 的页面不被当作章节","duration":0.5098000000000411,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","章节生成意图识别"],"fullName":"任务路由 task-router 章节生成意图识别 识别「写第10章」","status":"passed","title":"识别「写第10章」","duration":2.046699999999987,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","章节生成意图识别"],"fullName":"任务路由 task-router 章节生成意图识别 识别「生成第128章」","status":"passed","title":"识别「生成第128章」","duration":0.9995999999999867,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","章节生成意图识别"],"fullName":"任务路由 task-router 章节生成意图识别 识别「帮我写新章节」","status":"passed","title":"识别「帮我写新章节」","duration":0.945699999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","续写意图识别"],"fullName":"任务路由 task-router 续写意图识别 识别「继续写第30章」","status":"passed","title":"识别「继续写第30章」","duration":0.4637000000000171,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","续写意图识别"],"fullName":"任务路由 task-router 续写意图识别 识别「续写下去」","status":"passed","title":"识别「续写下去」","duration":0.6589999999999918,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","续写意图识别"],"fullName":"任务路由 task-router 续写意图识别 识别「接着写」","status":"passed","title":"识别「接着写」","duration":0.1914000000000442,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","改写/润色意图识别"],"fullName":"任务路由 task-router 改写/润色意图识别 识别「改写这一段」","status":"passed","title":"识别「改写这一段」","duration":0.17980000000000018,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","改写/润色意图识别"],"fullName":"任务路由 task-router 改写/润色意图识别 识别「润色一下」","status":"passed","title":"识别「润色一下」","duration":0.1936999999999216,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","改写/润色意图识别"],"fullName":"任务路由 task-router 改写/润色意图识别 识别「让节奏更紧」","status":"passed","title":"识别「让节奏更紧」","duration":0.17870000000004893,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","审稿/检查意图识别"],"fullName":"任务路由 task-router 审稿/检查意图识别 识别「审稿第15章」","status":"passed","title":"识别「审稿第15章」","duration":0.19290000000000873,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","审稿/检查意图识别"],"fullName":"任务路由 task-router 审稿/检查意图识别 识别「检查有没有人设崩坏」","status":"passed","title":"识别「检查有没有人设崩坏」","duration":0.16550000000006548,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","审稿/检查意图识别"],"fullName":"任务路由 task-router 审稿/检查意图识别 识别「连贯性检查」","status":"passed","title":"识别「连贯性检查」","duration":0.1428000000000793,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","大纲生成意图识别"],"fullName":"任务路由 task-router 大纲生成意图识别 识别「生成大纲」","status":"passed","title":"识别「生成大纲」","duration":0.1875999999999749,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","大纲生成意图识别"],"fullName":"任务路由 task-router 大纲生成意图识别 识别「帮我写分卷大纲」","status":"passed","title":"识别「帮我写分卷大纲」","duration":0.19200000000000728,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","查询意图识别"],"fullName":"任务路由 task-router 查询意图识别 识别「还有哪些伏笔没回收」","status":"passed","title":"识别「还有哪些伏笔没回收」","duration":0.15140000000008058,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","查询意图识别"],"fullName":"任务路由 task-router 查询意图识别 识别「林烬现在在哪」","status":"passed","title":"识别「林烬现在在哪」","duration":0.1370000000000573,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","查询意图识别"],"fullName":"任务路由 task-router 查询意图识别 识别「时间线到哪了」","status":"passed","title":"识别「时间线到哪了」","duration":0.15489999999999782,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","查询意图识别"],"fullName":"任务路由 task-router 查询意图识别 识别「搜索黑玉令」","status":"passed","title":"识别「搜索黑玉令」","duration":0.1551999999999225,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","一般对话降级"],"fullName":"任务路由 task-router 一般对话降级 识别普通对话","status":"passed","title":"识别普通对话","duration":0.16410000000007585,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","一般对话降级"],"fullName":"任务路由 task-router 一般对话降级 空输入为一般对话","status":"passed","title":"空输入为一般对话","duration":0.2094000000000733,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","任务指令构建"],"fullName":"任务路由 task-router 任务指令构建 为写作意图生成指令","status":"passed","title":"为写作意图生成指令","duration":0.30299999999999727,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["任务路由 task-router","任务指令构建"],"fullName":"任务路由 task-router 任务指令构建 一般对话不生成指令","status":"passed","title":"一般对话不生成指令","duration":0.13779999999997017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["项目元数据 project-meta"],"fullName":"项目元数据 project-meta 创建默认元数据结构完整","status":"passed","title":"创建默认元数据结构完整","duration":1.2758999999999787,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["项目元数据 project-meta"],"fullName":"项目元数据 project-meta 元数据字段可以自由更新","status":"passed","title":"元数据字段可以自由更新","duration":0.18589999999994689,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章模拟数据构建"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章模拟数据构建 生成 1000 章的上下文包不会内存溢出","status":"passed","title":"生成 1000 章的上下文包不会内存溢出","duration":0.7515999999999394,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章模拟数据构建"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章模拟数据构建 300 万字总量模拟:上下文 prompt 不超 200KB","status":"passed","title":"300 万字总量模拟:上下文 prompt 不超 200KB","duration":0.5317999999999756,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章模拟数据构建"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章模拟数据构建 极端上下文包:500 条角色状态 + 200 条伏笔","status":"passed","title":"极端上下文包:500 条角色状态 + 200 条伏笔","duration":0.9205999999999221,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","大规模快照操作"],"fullName":"Phase 8 长篇吞吐量压力测试 大规模快照操作 模拟 1000 章快照文件列表","status":"passed","title":"模拟 1000 章快照文件列表","duration":1.445799999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","大规模快照操作"],"fullName":"Phase 8 长篇吞吐量压力测试 大规模快照操作 超长章节摘要(3000 字摘要)能正常加载","status":"passed","title":"超长章节摘要(3000 字摘要)能正常加载","duration":0.3787999999999556,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","大规模前 100 章连续回看"],"fullName":"Phase 8 长篇吞吐量压力测试 大规模前 100 章连续回看 回看窗口在 1000 章场景中渐进正确","status":"passed","title":"回看窗口在 1000 章场景中渐进正确","duration":0.2887000000000626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","大规模前 100 章连续回看"],"fullName":"Phase 8 长篇吞吐量压力测试 大规模前 100 章连续回看 回看窗口数量可扩展到 10 章","status":"passed","title":"回看窗口数量可扩展到 10 章","duration":0.15150000000005548,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","大规模前 100 章连续回看"],"fullName":"Phase 8 长篇吞吐量压力测试 大规模前 100 章连续回看 回看窗口不返回负数章节号","status":"passed","title":"回看窗口不返回负数章节号","duration":0.21090000000003783,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的草稿/正史边界"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的草稿/正史边界 大量章节中 non-final 状态全部被过滤","status":"passed","title":"大量章节中 non-final 状态全部被过滤","duration":0.19119999999998072,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的草稿/正史边界"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的草稿/正史边界 outline 类型在大量章节中也被排除","status":"passed","title":"outline 类型在大量章节中也被排除","duration":0.16449999999997544,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的任务路由"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的任务路由 识别「写第999章」","status":"passed","title":"识别「写第999章」","duration":0.20609999999999218,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的任务路由"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的任务路由 识别「继续写第998章」需要了解前文","status":"passed","title":"识别「继续写第998章」需要了解前文","duration":0.17640000000005784,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的任务路由"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的任务路由 长篇后期审稿不被误判为写新章","status":"passed","title":"长篇后期审稿不被误判为写新章","duration":0.18389999999999418,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","1000 章场景下的任务路由"],"fullName":"Phase 8 长篇吞吐量压力测试 1000 章场景下的任务路由 长篇更新元数据类指令正确路由","status":"passed","title":"长篇更新元数据类指令正确路由","duration":0.31349999999997635,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Phase 8 长篇吞吐量压力测试","项目元数据在 300 万字场景中"],"fullName":"Phase 8 长篇吞吐量压力测试 项目元数据在 300 万字场景中 元数据结构可承载 1000 章 300 万字配置","status":"passed","title":"元数据结构可承载 1000 章 300 万字配置","duration":0.19869999999991705,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700318,"endTime":1781738700353.1987,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/novel-stability.test.ts"},{"assertionResults":[{"ancestorTitles":["snapshot-history"],"fullName":"snapshot-history backs up the previous snapshot before saving an edited one","status":"passed","title":"backs up the previous snapshot before saving an edited one","duration":18.021100000000047,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["snapshot-history"],"fullName":"snapshot-history restores a historical snapshot back into the current snapshot file","status":"passed","title":"restores a historical snapshot back into the current snapshot file","duration":13.756999999999948,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["snapshot-history"],"fullName":"snapshot-history restoreSnapshotHistory creates a new current revision instead of reviving the archived one unchanged","status":"passed","title":"restoreSnapshotHistory creates a new current revision instead of reviving the archived one unchanged","duration":4.8508999999999105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["snapshot-history"],"fullName":"snapshot-history rebuilds current projection and derived memory files when restoring snapshot history","status":"passed","title":"rebuilds current projection and derived memory files when restoring snapshot history","duration":7.506600000000049,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["context-pack freshness"],"fullName":"context-pack freshness buildContextPack excludes stale query matches and keeps the latest authoritative memory","status":"passed","title":"buildContextPack excludes stale query matches and keeps the latest authoritative memory","duration":17.97289999999998,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699326,"endTime":1781738699388.973,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/novel.test.ts"},{"assertionResults":[{"ancestorTitles":["outline-generation context fallback"],"fullName":"outline-generation context fallback still builds a generation prompt when context loading fails","status":"passed","title":"still builds a generation prompt when context loading fails","duration":3.3157999999999674,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-generation context fallback"],"fullName":"outline-generation context fallback returns an empty refinement context when context loading fails","status":"passed","title":"returns an empty refinement context when context loading fails","duration":1.2011999999999716,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703573,"endTime":1781738703577.2012,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/outline-generation.spec.ts"},{"assertionResults":[{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation generates six outline files with separate llm calls","status":"passed","title":"generates six outline files with separate llm calls","duration":7.8075000000000045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation falls back to the first written refinement file when chapter outlines are empty","status":"passed","title":"falls back to the first written refinement file when chapter outlines are empty","duration":0.8892000000000735,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation generates a single selected outline section","status":"passed","title":"generates a single selected outline section","duration":0.9737999999999829,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation can append generated section content into the current outline file","status":"passed","title":"can append generated section content into the current outline file","duration":0.9838000000000875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation can create a new outline file and add it to the outline list","status":"passed","title":"can create a new outline file and add it to the outline list","duration":1.3817999999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation uses English filenames and titles when output language is English","status":"passed","title":"uses English filenames and titles when output language is English","duration":0.8434999999999491,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation uses language-based filenames for the main outline file","status":"passed","title":"uses language-based filenames for the main outline file","duration":0.7907000000000153,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation builds initial outline prompts with project memory context","status":"passed","title":"builds initial outline prompts with project memory context","duration":0.6443000000000438,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation runs refinement in the background task flow","status":"passed","title":"runs refinement in the background task flow","duration":4.159899999999993,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation checks outline availability from the wiki search results","status":"passed","title":"checks outline availability from the wiki search results","duration":1.658900000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation creates ingest tasks that retain the outline path in the shared store","status":"passed","title":"creates ingest tasks that retain the outline path in the shared store","duration":0.8160000000000309,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline refinement generation"],"fullName":"outline refinement generation runs bulk outline ingest sequentially for every outline markdown file","status":"passed","title":"runs bulk outline ingest sequentially for every outline markdown file","duration":19.28520000000003,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700289,"endTime":1781738700329.2852,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/outline-generation.test.ts"},{"assertionResults":[{"ancestorTitles":["outline-import"],"fullName":"outline-import imports selected documents into the outline library as markdown pages","status":"passed","title":"imports selected documents into the outline library as markdown pages","duration":8.006100000000004,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["outline-import"],"fullName":"outline-import preserves folder structure when importing a document folder into the outline library","status":"passed","title":"preserves folder structure when importing a document folder into the outline library","duration":1.8065000000000282,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701600,"endTime":1781738701610.8064,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/outline-import.test.ts"},{"assertionResults":[{"ancestorTitles":["review-adapter staged review"],"fullName":"review-adapter staged review builds a staged deep review prompt with outline, memory, foreshadowing, and cognition checks","status":"passed","title":"builds a staged deep review prompt with outline, memory, foreshadowing, and cognition checks","duration":7.9886000000000195,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-adapter staged review"],"fullName":"review-adapter staged review runs staged review with high reasoning and publishes stage thinking","status":"passed","title":"runs staged review with high reasoning and publishes stage thinking","duration":5.009399999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-adapter staged review"],"fullName":"review-adapter staged review passes the provided abort signal into staged review streaming","status":"passed","title":"passes the provided abort signal into staged review streaming","duration":2.8501999999999725,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-adapter staged review"],"fullName":"review-adapter staged review starts the staged review already aborted when the stop signal fired beforehand","status":"passed","title":"starts the staged review already aborted when the stop signal fired beforehand","duration":0.3269999999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700751,"endTime":1781738700767.327,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/review-adapter.spec.ts"},{"assertionResults":[{"ancestorTitles":["resolveReviewModel"],"fullName":"resolveReviewModel returns trimmed review model from store","status":"passed","title":"returns trimmed review model from store","duration":2.464400000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["resolveReviewModel"],"fullName":"resolveReviewModel returns empty string when review model is blank","status":"passed","title":"returns empty string when review model is blank","duration":0.33270000000001687,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704404,"endTime":1781738704407.3328,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/review-model.test.ts"},{"assertionResults":[{"ancestorTitles":["review-scoring"],"fullName":"review-scoring returns full score for empty results","status":"passed","title":"returns full score for empty results","duration":3.219799999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring subtracts correct amount for single error","status":"passed","title":"subtracts correct amount for single error","duration":0.6365999999999872,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring handles mixed severity types correctly","status":"passed","title":"handles mixed severity types correctly","duration":0.4229000000000269,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring does not go below 0 for any dimension","status":"passed","title":"does not go below 0 for any dimension","duration":0.318300000000022,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring classifies severity levels correctly","status":"passed","title":"classifies severity levels correctly","duration":0.47169999999999845,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring accepts custom dimension weights","status":"passed","title":"accepts custom dimension weights","duration":0.3276000000000181,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring accepts custom severity deductions","status":"passed","title":"accepts custom severity deductions","duration":0.30340000000001055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring partial overrides keep defaults for unspecified values","status":"passed","title":"partial overrides keep defaults for unspecified values","duration":0.3213000000000079,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring calibrated weights sum to approximately 1","status":"passed","title":"calibrated weights sum to approximately 1","duration":0.3813999999999851,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring calibrated deductions maintain error > warning > info order","status":"passed","title":"calibrated deductions maintain error > warning > info order","duration":0.36650000000003047,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring calibrated weights produce stricter scores than defaults for serious errors","status":"passed","title":"calibrated weights produce stricter scores than defaults for serious errors","duration":0.257000000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["review-scoring"],"fullName":"review-scoring calibrated empty results still give 100","status":"passed","title":"calibrated empty results still give 100","duration":0.1786000000000172,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702368,"endTime":1781738702376.1787,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/review-scoring.test.ts"},{"assertionResults":[{"ancestorTitles":["novelMixedSearch"],"fullName":"novelMixedSearch uses fused ranking instead of always putting keyword results first","status":"passed","title":"uses fused ranking instead of always putting keyword results first","duration":6.751999999999953,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelMixedSearch"],"fullName":"novelMixedSearch merges the same page across retrieval sources and boosts it","status":"passed","title":"merges the same page across retrieval sources and boosts it","duration":3.1135999999999626,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelMixedSearch"],"fullName":"novelMixedSearch returns completed sources even when one retrieval branch stalls","status":"passed","title":"returns completed sources even when one retrieval branch stalls","duration":9.18199999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["novelMixedSearch"],"fullName":"novelMixedSearch excludes query-log pages when authoritativeOnly retrieval is enabled","status":"passed","title":"excludes query-log pages when authoritativeOnly retrieval is enabled","duration":1.4703000000000088,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700447,"endTime":1781738700467.4702,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/search-adapter.test.ts"},{"assertionResults":[{"ancestorTitles":["SOUL_DOC_FILENAME"],"fullName":"SOUL_DOC_FILENAME should be soul.md","status":"passed","title":"should be soul.md","duration":3.0978999999999814,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["readSoulDoc"],"fullName":"readSoulDoc should read soul.md from project root","status":"passed","title":"should read soul.md from project root","duration":3.5815000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["readSoulDoc"],"fullName":"readSoulDoc should return empty string when file does not exist","status":"passed","title":"should return empty string when file does not exist","duration":1.040399999999977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["readSoulDoc"],"fullName":"readSoulDoc should return empty string for other errors","status":"passed","title":"should return empty string for other errors","duration":0.3815000000000168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSoulDoc"],"fullName":"writeSoulDoc should write content to soul.md atomically","status":"passed","title":"should write content to soul.md atomically","duration":0.47980000000001155,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["writeSoulDoc"],"fullName":"writeSoulDoc should propagate write errors","status":"passed","title":"should propagate write errors","duration":1.842899999999986,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702106,"endTime":1781738702116.843,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/soul-doc.test.ts"},{"assertionResults":[{"ancestorTitles":["addSourceToOutlineCategory"],"fullName":"addSourceToOutlineCategory writes the imported source into the category folder","status":"passed","title":"writes the imported source into the category folder","duration":9.949000000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["addSourceToOutlineCategory"],"fullName":"addSourceToOutlineCategory creates a unique file when the same source is added repeatedly","status":"passed","title":"creates a unique file when the same source is added repeatedly","duration":0.8430000000000177,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702066,"endTime":1781738702076.843,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/source-outline-import.test.ts"},{"assertionResults":[{"ancestorTitles":["startNovelReviewRun"],"fullName":"startNovelReviewRun stores staged review thinking while the review is running","status":"passed","title":"stores staged review thinking while the review is running","duration":9.99109999999996,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702488,"endTime":1781738702497.9912,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/start-review-run.spec.ts"},{"assertionResults":[{"ancestorTitles":[],"fullName":"prefers selected chapter file name over stale frontmatter","status":"passed","title":"prefers selected chapter file name over stale frontmatter","duration":5.984899999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703389,"endTime":1781738703394.9849,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/start-review-run.test.ts"},{"assertionResults":[{"ancestorTitles":["startSixDimensionReviewRun"],"fullName":"startSixDimensionReviewRun stores dimension progress, thinking, results, and history while running","status":"passed","title":"stores dimension progress, thinking, results, and history while running","duration":10.432799999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["startSixDimensionReviewRun"],"fullName":"startSixDimensionReviewRun reruns only the selected dimension and preserves existing dimension results","status":"passed","title":"reruns only the selected dimension and preserves existing dimension results","duration":2.2596999999999525,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701028,"endTime":1781738701041.2598,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/start-six-dimension-review-run.spec.ts"},{"assertionResults":[{"ancestorTitles":["routeTask golden opening requests"],"fullName":"routeTask golden opening requests routes opening and first-three requests into chapter writing","status":"passed","title":"routes opening and first-three requests into chapter writing","duration":3.8414000000000215,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask golden opening requests"],"fullName":"routeTask golden opening requests routes explicit second and third chapter requests into chapter writing with chapter numbers","status":"passed","title":"routes explicit second and third chapter requests into chapter writing with chapter numbers","duration":0.526600000000002,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703545,"endTime":1781738703549.5266,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/task-router.golden.spec.ts"},{"assertionResults":[{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation routes continue-next-chapter requests into chapter generation flow","status":"passed","title":"routes continue-next-chapter requests into chapter generation flow","duration":4.196000000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation routes the continue-next-chapter button prompt into chapter generation flow","status":"passed","title":"routes the continue-next-chapter button prompt into chapter generation flow","duration":1.789199999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation routes outline-based chapter requests and extracts Chinese chapter numbers","status":"passed","title":"routes outline-based chapter requests and extracts Chinese chapter numbers","duration":1.354600000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation routes analyze-outline-then-generate-chapter requests into chapter writing","status":"passed","title":"routes analyze-outline-then-generate-chapter requests into chapter writing","duration":0.48169999999998936,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation does not hijack a customized next-chapter prompt that mentions 开篇 writing requirements (issue #9)","status":"passed","title":"does not hijack a customized next-chapter prompt that mentions 开篇 writing requirements (issue #9)","duration":0.3908000000000129,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation does not treat incidental 第一章 mentions in next-chapter requests as opening requests","status":"passed","title":"does not treat incidental 第一章 mentions in next-chapter requests as opening requests","duration":0.3229999999999791,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["routeTask chapter generation"],"fullName":"routeTask chapter generation keeps explicit later chapter numbers even when the prompt mentions 开篇 hooks","status":"passed","title":"keeps explicit later chapter numbers even when the prompt mentions 开篇 hooks","duration":0.3824999999999932,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701805,"endTime":1781738701814.3826,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/task-router.spec.ts"},{"assertionResults":[{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser emits text from a single stream_event text_delta","status":"passed","title":"emits text from a single stream_event text_delta","duration":2.4191000000000145,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser accumulates multiple stream_event deltas in order","status":"passed","title":"accumulates multiple stream_event deltas in order","duration":0.44029999999997926,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser falls back to `assistant` message text when no deltas arrived","status":"passed","title":"falls back to `assistant` message text when no deltas arrived","duration":0.27649999999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser emits only the novel tail when `assistant` events ship cumulative text","status":"passed","title":"emits only the novel tail when `assistant` events ship cumulative text","duration":0.3195999999999799,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser skips `assistant` events entirely once stream_event deltas are seen","status":"passed","title":"skips `assistant` events entirely once stream_event deltas are seen","duration":0.311300000000017,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser concatenates multiple text parts inside one `assistant` event","status":"passed","title":"concatenates multiple text parts inside one `assistant` event","duration":0.22239999999999327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser returns null for system init, result, tool_use, and unknown types","status":"passed","title":"returns null for system init, result, tool_use, and unknown types","duration":0.2804999999999609,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser returns null for malformed JSON or blank lines","status":"passed","title":"returns null for malformed JSON or blank lines","duration":0.27789999999998827,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["createClaudeCodeStreamParser"],"fullName":"createClaudeCodeStreamParser returns null for stream_event shapes we don't recognize (usage/etc.)","status":"passed","title":"returns null for stream_event shapes we don't recognize (usage/etc.)","duration":0.26019999999999754,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["streamClaudeCodeCli"],"fullName":"streamClaudeCodeCli waits for the done event before resolving so connection tests see emitted content","status":"passed","title":"waits for the done event before resolving so connection tests see emitted content","duration":8.519499999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError translates Unauthenticated stderr into an actionable login hint","status":"passed","title":"translates Unauthenticated stderr into an actionable login hint","duration":0.6591000000000236,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError includes the original stderr at the bottom for context","status":"passed","title":"includes the original stderr at the bottom for context","duration":0.42149999999998045,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError falls through to the bare exit-code form for unrecognized stderr","status":"passed","title":"falls through to the bare exit-code form for unrecognized stderr","duration":0.8448000000000206,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError works without stderr at all (truly silent exit)","status":"passed","title":"works without stderr at all (truly silent exit)","duration":0.2903999999999769,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError matches the case-insensitive Authentication failed variant","status":"passed","title":"matches the case-insensitive Authentication failed variant","duration":0.1875,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError falls back to unparsed stdout when stderr is empty (the real-user case)","status":"passed","title":"falls back to unparsed stdout when stderr is empty (the real-user case)","duration":0.22539999999997917,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError prefers stderr over unparsed stdout when both are present","status":"passed","title":"prefers stderr over unparsed stdout when both are present","duration":0.23129999999997608,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildExitError"],"fullName":"buildExitError recommends terminal reproduction when both stderr and stdout are empty","status":"passed","title":"recommends terminal reproduction when both stderr and stdout are empty","duration":0.19580000000001974,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700550,"endTime":1781738700567.2312,"status":"passed","message":"","name":"E:/QMAI/src/lib/__tests__/claude-cli-transport.test.ts"},{"assertionResults":[{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider uses the Anthropic Messages endpoint under /anthropic","status":"passed","title":"uses the Anthropic Messages endpoint under /anthropic","duration":2.230600000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider supports the China regional endpoint via customEndpoint","status":"passed","title":"supports the China regional endpoint via customEndpoint","duration":0.273900000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider uses Authorization: Bearer (MiniMax rejects x-api-key at CORS layer)","status":"passed","title":"uses Authorization: Bearer (MiniMax rejects x-api-key at CORS layer)","duration":0.2733000000000061,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider sets Content-Type to application/json","status":"passed","title":"sets Content-Type to application/json","duration":0.20820000000003347,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider enables streaming","status":"passed","title":"enables streaming","duration":0.238900000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider includes max_tokens (required by Anthropic wire)","status":"passed","title":"includes max_tokens (required by Anthropic wire)","duration":0.19299999999998363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider carries the model in the body","status":"passed","title":"carries the model in the body","duration":0.191599999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax Provider"],"fullName":"MiniMax Provider separates system messages from conversation (Anthropic convention)","status":"passed","title":"separates system messages from conversation (Anthropic convention)","duration":0.9508000000000152,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["MiniMax provider registration"],"fullName":"MiniMax provider registration minimax is a valid provider value in the type union","status":"passed","title":"minimax is a valid provider value in the type union","duration":0.2506000000000199,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling appends /v1/messages to a bare host","status":"passed","title":"appends /v1/messages to a bare host","duration":0.38800000000003365,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling appends /v1/messages to a bare /anthropic proxy base","status":"passed","title":"appends /v1/messages to a bare /anthropic proxy base","duration":0.2536999999999807,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling does NOT double the /v1 when base already ends in /v1","status":"passed","title":"does NOT double the /v1 when base already ends in /v1","duration":0.15880000000004202,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling does NOT double the /v1 when proxy base ends in /anthropic/v1","status":"passed","title":"does NOT double the /v1 when proxy base ends in /anthropic/v1","duration":0.20709999999996853,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling preserves a full /v1/messages URL","status":"passed","title":"preserves a full /v1/messages URL","duration":0.14449999999999363,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling handles arbitrary version segments like /api/paas/v4","status":"passed","title":"handles arbitrary version segments like /api/paas/v4","duration":0.15850000000000364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildAnthropicUrl — URL suffix handling"],"fullName":"buildAnthropicUrl — URL suffix handling strips trailing slashes","status":"passed","title":"strips trailing slashes","duration":0.13569999999998572,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing extracts plain text from a single-part event","status":"passed","title":"extracts plain text from a single-part event","duration":0.23229999999995243,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing concatenates text across multiple parts in one event","status":"passed","title":"concatenates text across multiple parts in one event","duration":0.14239999999995234,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing skips thought parts so reasoning tokens don't leak into output","status":"passed","title":"skips thought parts so reasoning tokens don't leak into output","duration":0.14150000000000773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing returns null when the event has no visible text","status":"passed","title":"returns null when the event has no visible text","duration":0.21109999999998763,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing returns null for non-data lines","status":"passed","title":"returns null for non-data lines","duration":0.31560000000001764,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["parseGoogleLine — Gemini SSE parsing"],"fullName":"parseGoogleLine — Gemini SSE parsing returns null for malformed JSON","status":"passed","title":"returns null for malformed JSON","duration":0.26329999999995835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Claude Code CLI provider — not reachable via getProviderConfig"],"fullName":"Claude Code CLI provider — not reachable via getProviderConfig throws, because the subprocess transport dispatches one layer up in streamChat","status":"passed","title":"throws, because the subprocess transport dispatches one layer up in streamChat","duration":1.562400000000025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Codex CLI provider — not reachable via getProviderConfig"],"fullName":"Codex CLI provider — not reachable via getProviderConfig throws, because the subprocess transport dispatches one layer up in streamChat","status":"passed","title":"throws, because the subprocess transport dispatches one layer up in streamChat","duration":0.28940000000000055,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Google provider URL — model path encoding"],"fullName":"Google provider URL — model path encoding embeds a normal model id directly in the URL","status":"passed","title":"embeds a normal model id directly in the URL","duration":0.19319999999999027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Google provider URL — model path encoding"],"fullName":"Google provider URL — model path encoding encodes slashes in a pasted OpenRouter-style id so the path stays well-formed","status":"passed","title":"encodes slashes in a pasted OpenRouter-style id so the path stays well-formed","duration":0.28129999999998745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires Gemini nests overrides under generationConfig with Gemini naming","status":"passed","title":"Gemini nests overrides under generationConfig with Gemini naming","duration":0.7523000000000479,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires Gemini omits generationConfig entirely when no overrides passed","status":"passed","title":"Gemini omits generationConfig entirely when no overrides passed","duration":0.17390000000000327,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires OpenAI wires put overrides at the top level","status":"passed","title":"OpenAI wires put overrides at the top level","duration":0.8721999999999639,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires OpenAI GPT-5 maps max_tokens and strips unsupported sampling knobs","status":"passed","title":"OpenAI GPT-5 maps max_tokens and strips unsupported sampling knobs","duration":0.4655999999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires OpenAI o-series models use max_completion_tokens","status":"passed","title":"OpenAI o-series models use max_completion_tokens","duration":0.30520000000001346,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires custom OpenAI-compatible GPT-5 routes keep legacy overrides for OpenRouter-style shims","status":"passed","title":"custom OpenAI-compatible GPT-5 routes keep legacy overrides for OpenRouter-style shims","duration":1.1288000000000125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires custom Kimi routes strip unsupported temperature overrides","status":"passed","title":"custom Kimi routes strip unsupported temperature overrides","duration":0.538599999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Sampling override translation across wires"],"fullName":"Sampling override translation across wires Anthropic maps stop → stop_sequences and respects max_tokens override","status":"passed","title":"Anthropic maps stop → stop_sequences and respects max_tokens override","duration":0.7602999999999724,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround Ollama provider sends Origin: http://localhost when server is on localhost","status":"passed","title":"Ollama provider sends Origin: http://localhost when server is on localhost","duration":0.2155999999999949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround Ollama provider sends Origin: http://localhost EVEN WHEN server is on a remote LAN IP","status":"passed","title":"Ollama provider sends Origin: http://localhost EVEN WHEN server is on a remote LAN IP","duration":0.24969999999996162,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround Ollama provider Origin doesn't change with /v1 in the URL","status":"passed","title":"Ollama provider Origin doesn't change with /v1 in the URL","duration":0.3211000000000013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround custom OpenAI-compat endpoint gets the same Origin override (LM Studio / llama.cpp / vLLM)","status":"passed","title":"custom OpenAI-compat endpoint gets the same Origin override (LM Studio / llama.cpp / vLLM)","duration":0.2699999999999818,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround strips a pasted /embeddings tail from custom chat endpoints before appending /chat/completions","status":"passed","title":"strips a pasted /embeddings tail from custom chat endpoints before appending /chat/completions","duration":0.2045999999999708,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround commercial provider (OpenAI) does NOT get an explicit Origin override","status":"passed","title":"commercial provider (OpenAI) does NOT get an explicit Origin override","duration":0.13559999999995398,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Origin header — local LLM CORS workaround"],"fullName":"Origin header — local LLM CORS workaround Ollama provider always sets Origin even when the configured URL is malformed","status":"passed","title":"Ollama provider always sets Origin even when the configured URL is malformed","duration":0.16109999999997626,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700550,"endTime":1781738700567.2046,"status":"passed","message":"","name":"E:/QMAI/src/lib/__tests__/llm-providers.test.ts"},{"assertionResults":[{"ancestorTitles":["ChangelogSection"],"fullName":"ChangelogSection shows five changelog items first and expands to all items when requested","status":"passed","title":"shows five changelog items first and expands to all items when requested","duration":180.0426,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699673,"endTime":1781738699853.0425,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/sections/changelog-section.test.tsx"},{"assertionResults":[{"ancestorTitles":["LLM provider model controls"],"fullName":"LLM provider model controls keeps fetched model selection wired into the LLM provider panel","status":"passed","title":"keeps fetched model selection wired into the LLM provider panel","duration":2.761199999999974,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["LLM provider model controls"],"fullName":"LLM provider model controls shows separate controls for fetching models and testing the selected model","status":"passed","title":"shows separate controls for fetching models and testing the selected model","duration":0.4615000000000009,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704176,"endTime":1781738704179.4614,"status":"passed","message":"","name":"E:/QMAI/src/components/settings/sections/llm-provider-section.spec.ts"},{"assertionResults":[{"ancestorTitles":["LlmProviderSection"],"fullName":"LlmProviderSection runs the copied LLM Wiki connection and function tests for the custom preset","status":"failed","title":"runs the copied LLM Wiki connection and function tests for the custom preset","duration":249.92050000000017,"failureMessages":["Error: 测试连接 button not found. Available buttons: 添加模型, (icon), 自定义模型, (icon), (icon), Anthropic (Claude)Official Claude API, (icon), (icon), Claude Code CLI (local)Uses the local `claude` binary — no API key needed, (icon), (icon), Codex CLI (local)Uses the local `codex` binary — no API key needed, (icon), (icon), OpenAI (GPT)Official OpenAI API, (icon), (icon), Google (Gemini)Generative Language API, (icon), (icon), Azure OpenAIAzure OpenAI resource endpoint; Model field is the deployment name, (icon), (icon), DeepSeekapi.deepseek.com, (icon), (icon), Groqapi.groq.com, (icon), (icon), xAI (Grok)api.x.ai, (icon), (icon), NVIDIA NIMintegrate.api.nvidia.com, (icon), (icon), Kimi (Moonshot)api.moonshot.ai, (icon), (icon), Kimi (Moonshot, 中国)api.moonshot.cn, (icon), (icon), Kimi (Coding Plan)api.kimi.com, (icon), (icon), 智谱 GLM (Zhipu)open.bigmodel.cn, (icon), (icon), MiniMax (Global)api.minimax.io/anthropic, (icon), (icon), MiniMax (中国)api.minimaxi.com/anthropic, (icon), (icon), 阿里百炼 Coding Plancoding.dashscope.aliyuncs.com, (icon), (icon), 小米 MiMo (Xiaomi)api.xiaomimimo.com, (icon), (icon), 火山引擎 Ark (Volcengine)ark.cn-beijing.volces.com/api/coding/v3, (icon), (icon), Ollama (Local)Self-hosted llama.cpp / Ollama, (icon), (icon), Ollama Cloudollama.com, (icon)\n at findButton (E:/QMAI/src/components/settings/sections/llm-provider-section.test.tsx:36:11)\n at E:/QMAI/src/components/settings/sections/llm-provider-section.test.tsx:107:7\n at Proxy.process.env.NODE_ENV.exports.act (E:\\QMAI\\node_modules\\react\\cjs\\react.development.js:814:22)\n at E:/QMAI/src/components/settings/sections/llm-provider-section.test.tsx:106:11\n at file:///E:/QMAI/node_modules/@vitest/runner/dist/chunk-artifact.js:1903:20"],"meta":{},"tags":[]}],"startTime":1781738699300,"endTime":1781738699549.9204,"status":"failed","message":"","name":"E:/QMAI/src/components/settings/sections/llm-provider-section.test.tsx"},{"assertionResults":[{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver builds map with canonical + merged aliases","status":"passed","title":"builds map with canonical + merged aliases","duration":5.090700000000027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver rejects self-alias (canonical repeated in aliases)","status":"passed","title":"rejects self-alias (canonical repeated in aliases)","duration":0.6003000000000043,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver rejects too-long aliases","status":"passed","title":"rejects too-long aliases","duration":0.40559999999999263,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver rejects pure-punctuation alias","status":"passed","title":"rejects pure-punctuation alias","duration":0.2617000000000189,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver trims whitespace and collapses spaces","status":"passed","title":"trims whitespace and collapses spaces","duration":0.2948000000000093,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver applyCanonicalNames replaces all aliases","status":"passed","title":"applyCanonicalNames replaces all aliases","duration":0.2785999999999831,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["alias-resolver"],"fullName":"alias-resolver matchesAnyAlias returns true for canonical or any alias","status":"passed","title":"matchesAnyAlias returns true for canonical or any alias","duration":0.3435000000000059,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702196,"endTime":1781738702203.3435,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/alias-resolver.spec.ts"},{"assertionResults":[{"ancestorTitles":["importBookAnalysisSkillsAsAuras"],"fullName":"importBookAnalysisSkillsAsAuras 为每一个被选中的 skill 创建一个自定义灵魂","status":"passed","title":"为每一个被选中的 skill 创建一个自定义灵魂","duration":5.0984000000000265,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["importBookAnalysisSkillsAsAuras"],"fullName":"importBookAnalysisSkillsAsAuras 未选中的 skill 不会触发灵魂创建","status":"passed","title":"未选中的 skill 不会触发灵魂创建","duration":0.7972999999999502,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["importBookAnalysisSkillsAsAuras"],"fullName":"importBookAnalysisSkillsAsAuras skill 找不到对应角色时会被跳过,不会创建空灵魂","status":"passed","title":"skill 找不到对应角色时会被跳过,不会创建空灵魂","duration":0.6853999999999587,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["importBookAnalysisSkillsAsAuras"],"fullName":"importBookAnalysisSkillsAsAuras 传给 createCustomCharacterAuraFromGeneratedSkill 的输入必须带拆书标签和原作品来源","status":"passed","title":"传给 createCustomCharacterAuraFromGeneratedSkill 的输入必须带拆书标签和原作品来源","duration":2.515800000000013,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701751,"endTime":1781738701760.5159,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/aura-adapter.e2e.spec.ts"},{"assertionResults":[{"ancestorTitles":["buildGeneratedAuraInputFromBookCharacter"],"fullName":"buildGeneratedAuraInputFromBookCharacter maps extracted character data into custom soul fields","status":"passed","title":"maps extracted character data into custom soul fields","duration":2.7396000000000242,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704759,"endTime":1781738704761.7395,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/aura-adapter.spec.ts"},{"assertionResults":[{"ancestorTitles":["isAuraFromBook"],"fullName":"isAuraFromBook category 必须是 拆书角色","status":"passed","title":"category 必须是 拆书角色","duration":2.461500000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAuraFromBook"],"fullName":"isAuraFromBook sourceNote 中包含《书名》才认为来自该书","status":"passed","title":"sourceNote 中包含《书名》才认为来自该书","duration":0.6302000000000021,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAuraFromBook"],"fullName":"isAuraFromBook corpus 中包含《书名》也算","status":"passed","title":"corpus 中包含《书名》也算","duration":0.3217000000000212,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isAuraFromBook"],"fullName":"isAuraFromBook 空书名直接返回 false","status":"passed","title":"空书名直接返回 false","duration":0.28350000000000364,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["listOrphanAurasForBook"],"fullName":"listOrphanAurasForBook 返回未绑定的、属于该书的灵魂 ID","status":"passed","title":"返回未绑定的、属于该书的灵魂 ID","duration":1.2443999999999846,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteOrphanAurasForBook"],"fullName":"deleteOrphanAurasForBook 只删除属于该书且未绑定的灵魂","status":"passed","title":"只删除属于该书且未绑定的灵魂","duration":1.488900000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteOrphanAurasForBook"],"fullName":"deleteOrphanAurasForBook 内置灵魂永远不会被删","status":"passed","title":"内置灵魂永远不会被删","duration":0.4271999999999707,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteOrphanAurasForBook"],"fullName":"deleteOrphanAurasForBook 书名不匹配时不会误删","status":"passed","title":"书名不匹配时不会误删","duration":0.310100000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["deleteOrphanAurasForBook"],"fullName":"deleteOrphanAurasForBook 删除失败时不影响其他灵魂","status":"passed","title":"删除失败时不影响其他灵魂","duration":5.233699999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738700923,"endTime":1781738700936.2336,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/aura-cleanup.spec.ts"},{"assertionResults":[{"ancestorTitles":["persistCharacterToDisk"],"fullName":"persistCharacterToDisk writes character JSON under bookPath/characters/{id}.json","status":"passed","title":"writes character JSON under bookPath/characters/{id}.json","duration":3.6510999999999854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["persistCharacterToDisk"],"fullName":"persistCharacterToDisk throws if writeFile fails (so caller can toast)","status":"passed","title":"throws if writeFile fails (so caller can toast)","duration":2.164699999999982,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702622,"endTime":1781738702628.1648,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/character-disk-store.spec.ts"},{"assertionResults":[{"ancestorTitles":["extractSingleCharacter (fix/character-reextract-and-loading-state)"],"fullName":"extractSingleCharacter (fix/character-reextract-and-loading-state) simple 模式内部直接调用 LLM(不再依赖外部 _llmCall 注入)","status":"passed","title":"simple 模式内部直接调用 LLM(不再依赖外部 _llmCall 注入)","duration":4.267300000000034,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractSingleCharacter (fix/character-reextract-and-loading-state)"],"fullName":"extractSingleCharacter (fix/character-reextract-and-loading-state) simple 模式下 LLM 抛错时,extractSingleCharacter 抛出包含错误信息的 Error","status":"passed","title":"simple 模式下 LLM 抛错时,extractSingleCharacter 抛出包含错误信息的 Error","duration":2.234800000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702706,"endTime":1781738702713.2349,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/character-extraction-engine.spec.ts"},{"assertionResults":[{"ancestorTitles":["heuristicRecognizeCharacters"],"fullName":"heuristicRecognizeCharacters 按出场章节数统计名字频次","status":"passed","title":"按出场章节数统计名字频次","duration":3.9864000000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["heuristicRecognizeCharacters"],"fullName":"heuristicRecognizeCharacters 次要角色低于 minChapters 阈值不出现","status":"passed","title":"次要角色低于 minChapters 阈值不出现","duration":0.5517999999999574,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["heuristicRecognizeCharacters"],"fullName":"heuristicRecognizeCharacters 空章节返回空数组","status":"passed","title":"空章节返回空数组","duration":0.22210000000001173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llmScoreCharacters"],"fullName":"llmScoreCharacters 调用 LLM 1 次并覆盖中频候选启发式分数","status":"passed","title":"调用 LLM 1 次并覆盖中频候选启发式分数","duration":1.4284999999999854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llmScoreCharacters"],"fullName":"llmScoreCharacters LLM 失败时回退到启发式分数","status":"passed","title":"LLM 失败时回退到启发式分数","duration":0.46170000000000755,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["llmScoreCharacters"],"fullName":"llmScoreCharacters 没有中频候选时直接返回,跳过 LLM 调用","status":"passed","title":"没有中频候选时直接返回,跳过 LLM 调用","duration":1.3523000000000138,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["filterMidFrequencyCandidates"],"fullName":"filterMidFrequencyCandidates 保留 appearances ≤ maxAppearances 的角色,截断到 maxCandidates","status":"passed","title":"保留 appearances ≤ maxAppearances 的角色,截断到 maxCandidates","duration":0.3453999999999837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stableCharacterId"],"fullName":"stableCharacterId 相同 name + sourceBook 生成相同 id","status":"passed","title":"相同 name + sourceBook 生成相同 id","duration":0.28129999999998745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stableCharacterId"],"fullName":"stableCharacterId 不同 name 或不同 sourceBook 生成不同 id","status":"passed","title":"不同 name 或不同 sourceBook 生成不同 id","duration":0.5971000000000117,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stableCharacterId"],"fullName":"stableCharacterId heuristicRecognizeCharacters 用稳定 id","status":"passed","title":"heuristicRecognizeCharacters 用稳定 id","duration":0.6023000000000138,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701238,"endTime":1781738701248.6023,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/character-recognition-engine.spec.ts"},{"assertionResults":[{"ancestorTitles":["content-fingerprint"],"fullName":"content-fingerprint fingerprintText 对相同输入稳定","status":"passed","title":"fingerprintText 对相同输入稳定","duration":2.268700000000024,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-fingerprint"],"fullName":"content-fingerprint fingerprintText 对不同输入差异","status":"passed","title":"fingerprintText 对不同输入差异","duration":1.1031000000000404,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-fingerprint"],"fullName":"content-fingerprint fingerprintText 接受空字符串","status":"passed","title":"fingerprintText 接受空字符串","duration":0.39159999999998263,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-fingerprint"],"fullName":"content-fingerprint fingerprintFileSample 同时考虑 size + head + tail","status":"passed","title":"fingerprintFileSample 同时考虑 size + head + tail","duration":1.2339999999999804,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["content-fingerprint"],"fullName":"content-fingerprint fingerprintFileSample 返回 16 位 hex","status":"passed","title":"fingerprintFileSample 返回 16 位 hex","duration":0.3020999999999958,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703183,"endTime":1781738703188.302,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/content-fingerprint.spec.ts"},{"assertionResults":[{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 无记录时返回 null","status":"passed","title":"无记录时返回 null","duration":3.178100000000086,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 读到合法值 fast 时返回 fast","status":"passed","title":"读到合法值 fast 时返回 fast","duration":0.7934999999999945,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 读到合法值 standard 时返回 standard","status":"passed","title":"读到合法值 standard 时返回 standard","duration":0.451799999999821,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 读到合法值 deep 时返回 deep","status":"passed","title":"读到合法值 deep 时返回 deep","duration":0.34839999999985594,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 读到非法字符串时返回 null","status":"passed","title":"读到非法字符串时返回 null","duration":0.3270999999999731,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference 读到非合法字符串(如数字)时返回 null","status":"passed","title":"读到非合法字符串(如数字)时返回 null","duration":0.2903000000001157,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","loadDepthPreference"],"fullName":"depth-preference loadDepthPreference localStorage 抛错时返回 null,不向上抛","status":"passed","title":"localStorage 抛错时返回 null,不向上抛","duration":1.36200000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","saveDepthPreference"],"fullName":"depth-preference saveDepthPreference 写入 fast 后可读出 fast","status":"passed","title":"写入 fast 后可读出 fast","duration":0.3329000000001088,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","saveDepthPreference"],"fullName":"depth-preference saveDepthPreference 写入 deep 后可读出 deep","status":"passed","title":"写入 deep 后可读出 deep","duration":0.3148000000001048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","saveDepthPreference"],"fullName":"depth-preference saveDepthPreference 写入非法值时不报错且不写入","status":"passed","title":"写入非法值时不报错且不写入","duration":0.4089000000001306,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["depth-preference","saveDepthPreference"],"fullName":"depth-preference saveDepthPreference localStorage 抛错时静默忽略","status":"passed","title":"localStorage 抛错时静默忽略","duration":1.246599999999944,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703110,"endTime":1781738703119.2466,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/depth-preference.spec.ts"},{"assertionResults":[{"ancestorTitles":["library-store"],"fullName":"library-store loadBookLibrary: 文件不存在或损坏 → 返回空库","status":"passed","title":"loadBookLibrary: 文件不存在或损坏 → 返回空库","duration":5.480600000000038,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["library-store"],"fullName":"library-store upsertBookLibraryEntry: 新增","status":"passed","title":"upsertBookLibraryEntry: 新增","duration":1.4200999999999908,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["library-store"],"fullName":"library-store upsertBookLibraryEntry: 同 bookId 覆盖","status":"passed","title":"upsertBookLibraryEntry: 同 bookId 覆盖","duration":0.5936999999999557,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["library-store"],"fullName":"library-store findBookLibraryEntry: 按 sourcePath 命中","status":"passed","title":"findBookLibraryEntry: 按 sourcePath 命中","duration":0.359299999999962,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["library-store"],"fullName":"library-store findBookLibraryEntry: hash 不一致返回 undefined","status":"passed","title":"findBookLibraryEntry: hash 不一致返回 undefined","duration":0.30040000000002465,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702587,"endTime":1781738702595.3594,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/library-store.spec.ts"},{"assertionResults":[{"ancestorTitles":["loadBookAnalysisResult"],"fullName":"loadBookAnalysisResult reads metadata, characters and skills, returns assembled result","status":"passed","title":"reads metadata, characters and skills, returns assembled result","duration":4.747299999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["loadBookAnalysisResult"],"fullName":"loadBookAnalysisResult returns null when metadata is missing","status":"passed","title":"returns null when metadata is missing","duration":4.287399999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["loadBookAnalysisResult"],"fullName":"loadBookAnalysisResult returns result with empty characters and skills when those dirs are absent","status":"passed","title":"returns result with empty characters and skills when those dirs are absent","duration":1.224200000000053,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701231,"endTime":1781738701241.224,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/result-loader.spec.ts"},{"assertionResults":[{"ancestorTitles":["extractSimpleProfiles"],"fullName":"extractSimpleProfiles 1 次 LLM 调用 + 输出每个角色的 profile","status":"passed","title":"1 次 LLM 调用 + 输出每个角色的 profile","duration":4.651499999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["extractSimpleProfiles"],"fullName":"extractSimpleProfiles LLM 失败时每个角色返回空 profile + 标记 error","status":"passed","title":"LLM 失败时每个角色返回空 profile + 标记 error","duration":1.3920999999999708,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702890,"endTime":1781738702896.392,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/simple-extraction-engine.spec.ts"},{"assertionResults":[{"ancestorTitles":["buildSimpleExtractionPrompt"],"fullName":"buildSimpleExtractionPrompt 包含所有选中角色名","status":"passed","title":"包含所有选中角色名","duration":2.59820000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildSimpleExtractionPrompt"],"fullName":"buildSimpleExtractionPrompt 输出 JSON Schema 包含 4 字段 + quotes","status":"passed","title":"输出 JSON Schema 包含 4 字段 + quotes","duration":0.48279999999999745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["buildSimpleExtractionPrompt"],"fullName":"buildSimpleExtractionPrompt 要求 quotes 3-5 句","status":"passed","title":"要求 quotes 3-5 句","duration":0.35370000000000346,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738704141,"endTime":1781738704144.3538,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/simple-extraction-prompts.spec.ts"},{"assertionResults":[{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine fast mode does not call LLM or web","status":"passed","title":"fast mode does not call LLM or web","duration":4.085199999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine standard mode calls LLM 6 times and skips web","status":"passed","title":"standard mode calls LLM 6 times and skips web","duration":1.3342999999999847,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine deep mode tries web + 6 LLM; web failure → llmFallback","status":"passed","title":"deep mode tries web + 6 LLM; web failure → llmFallback","duration":0.995900000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine deep mode with web success sets webSearchUsed","status":"passed","title":"deep mode with web success sets webSearchUsed","duration":0.8846000000000345,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine DEPTH_DESCRIPTIONS has 3 levels with token multiplier","status":"passed","title":"DEPTH_DESCRIPTIONS has 3 levels with token multiplier","duration":0.4522000000000048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine standard mode reports each dimension with running→done transition in dimensions array","status":"passed","title":"standard mode reports each dimension with running→done transition in dimensions array","duration":0.7468000000000075,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine onProgress reports characterName in every progress","status":"passed","title":"onProgress reports characterName in every progress","duration":0.762299999999982,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine deep mode reports fetching_web stage before dimension stages","status":"passed","title":"deep mode reports fetching_web stage before dimension stages","duration":0.5542000000000371,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine final done progress has all dimensions marked done","status":"passed","title":"final done progress has all dimensions marked done","duration":0.4773999999999887,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-engine"],"fullName":"six-dimension-engine fast mode reports dimensions all done in single progress","status":"passed","title":"fast mode reports dimensions all done in single progress","duration":0.4571000000000254,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738701550,"endTime":1781738701560.4773,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/six-dimension-engine.spec.ts"},{"assertionResults":[{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts exports 6 dimensions in stable order","status":"passed","title":"exports 6 dimensions in stable order","duration":2.810799999999972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts has 6 labels in matching order","status":"passed","title":"has 6 labels in matching order","duration":0.6827999999999861,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts PROMPT_BUILDERS covers every dimension","status":"passed","title":"PROMPT_BUILDERS covers every dimension","duration":0.7203000000000088,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts public-material prompt contains name + aliases + book info","status":"passed","title":"public-material prompt contains name + aliases + book info","duration":0.7255000000000109,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts speech prompt asks for at least 3 typical dialogues","status":"passed","title":"speech prompt asks for at least 3 typical dialogues","duration":0.2633999999999901,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts expression-dna prompt contains mapping table","status":"passed","title":"expression-dna prompt contains mapping table","duration":0.24630000000001928,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts external-views prompt handles missing web material","status":"passed","title":"external-views prompt handles missing web material","duration":0.4977999999999838,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts external-views prompt uses web material when provided","status":"passed","title":"external-views prompt uses web material when provided","duration":0.4849999999999568,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts decision-log prompt asks for at least 3 cases","status":"passed","title":"decision-log prompt asks for at least 3 cases","duration":0.29050000000000864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts timeline prompt asks for early/mid/late","status":"passed","title":"timeline prompt asks for early/mid/late","duration":0.36480000000000246,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["six-dimension-prompts"],"fullName":"six-dimension-prompts truncates long corpus","status":"passed","title":"truncates long corpus","duration":0.3628999999999678,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738702270,"endTime":1781738702277.3647,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/six-dimension-prompts.spec.ts"},{"assertionResults":[{"ancestorTitles":["isSixDimensionSkill"],"fullName":"isSixDimensionSkill 存在 sixDimensionResearch + sixDimensionMeta 时返回 true","status":"passed","title":"存在 sixDimensionResearch + sixDimensionMeta 时返回 true","duration":2.459699999999998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["isSixDimensionSkill"],"fullName":"isSixDimensionSkill 仅 personalityProfile 时返回 false","status":"passed","title":"仅 personalityProfile 时返回 false","duration":0.3557999999999879,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateSimpleSkillMarkdown"],"fullName":"generateSimpleSkillMarkdown 包含 4 字段 + 代表性台词","status":"passed","title":"包含 4 字段 + 代表性台词","duration":0.9105000000000132,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateCharacterSkill 模式分支"],"fullName":"generateCharacterSkill 模式分支 personalityProfile 存在时走简单提取模板,不调 LLM","status":"passed","title":"personalityProfile 存在时走简单提取模板,不调 LLM","duration":0.46760000000000446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["generateCharacterSkill 模式分支"],"fullName":"generateCharacterSkill 模式分支 sixDimensionResearch 存在时走 6 维度模板","status":"passed","title":"sixDimensionResearch 存在时走 6 维度模板","duration":0.5350999999999999,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738703670,"endTime":1781738703675.5352,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/skill-generator.spec.ts"},{"assertionResults":[{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo returns null on 4xx (no retry)","status":"passed","title":"searchDuckDuckGo returns null on 4xx (no retry)","duration":53.56450000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo retries 5xx and succeeds on second attempt","status":"passed","title":"searchDuckDuckGo retries 5xx and succeeds on second attempt","duration":2.488900000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo does NOT retry 4xx (other than 429)","status":"passed","title":"searchDuckDuckGo does NOT retry 4xx (other than 429)","duration":0.7262000000000057,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo retries on network error (fetch throws)","status":"passed","title":"searchDuckDuckGo retries on network error (fetch throws)","duration":0.6932000000000471,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo returns null when Abstract too short","status":"passed","title":"searchDuckDuckGo returns null when Abstract too short","duration":0.608099999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchDuckDuckGo returns result on good payload","status":"passed","title":"searchDuckDuckGo returns result on good payload","duration":0.7087999999999965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchWikipedia skips 404, tries next endpoint","status":"passed","title":"searchWikipedia skips 404, tries next endpoint","duration":1.2244000000000028,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search searchWikipedia retries 5xx across endpoints","status":"passed","title":"searchWikipedia retries 5xx across endpoints","duration":0.6908000000000243,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search fetchCharacterExternalMaterial falls back from ddg → wiki → null","status":"passed","title":"fetchCharacterExternalMaterial falls back from ddg → wiki → null","duration":1.2945000000000277,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["web-search"],"fullName":"web-search fetchCharacterExternalMaterial returns ddg when present","status":"passed","title":"fetchCharacterExternalMaterial returns ddg when present","duration":0.6048999999999864,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withRetry"],"fullName":"withRetry returns immediately on success","status":"passed","title":"returns immediately on success","duration":0.6546000000000163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withRetry"],"fullName":"withRetry retries up to maxAttempts then throws","status":"passed","title":"retries up to maxAttempts then throws","duration":2.303699999999992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withRetry"],"fullName":"withRetry calls onRetry with attempt and delay","status":"passed","title":"calls onRetry with attempt and delay","duration":0.8072999999999979,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["withRetry"],"fullName":"withRetry stops immediately when signal is aborted","status":"passed","title":"stops immediately when signal is aborted","duration":0.8328999999999951,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1781738699522,"endTime":1781738699589.833,"status":"passed","message":"","name":"E:/QMAI/src/lib/novel/book-analysis/web-search.spec.ts"}]}
|