@geminilight/mindos 0.6.40 → 0.6.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +18 -18
  4. package/_standalone/.next/build-manifest.json +3 -3
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/react-loadable-manifest.json +5 -1
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +2 -2
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/ask/route.js +1 -1
  40. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  41. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  45. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  47. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  49. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  51. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  54. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  56. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  58. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  60. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  62. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  65. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  67. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  69. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/mcp/install/route.js +1 -1
  71. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/mcp/install-skill/route.js.nft.json +1 -1
  73. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/status/route.js +1 -1
  76. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  78. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  80. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  83. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  87. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  93. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  96. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  97. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  102. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  103. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/changes/page.js +1 -1
  105. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  106. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  107. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  108. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  109. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/echo/page.js +1 -1
  111. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  112. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/explore/page.js +1 -1
  114. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  115. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  116. package/_standalone/.next/server/app/help/page.js +2 -2
  117. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  118. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  119. package/_standalone/.next/server/app/login/page.js +1 -1
  120. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  121. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  122. package/_standalone/.next/server/app/page.js +2 -2
  123. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  124. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  125. package/_standalone/.next/server/app/setup/page.js +2 -2
  126. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  127. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  128. package/_standalone/.next/server/app/trash/page.js +3 -3
  129. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  130. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  131. package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
  132. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  133. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  134. package/_standalone/.next/server/app-paths-manifest.json +18 -18
  135. package/_standalone/.next/server/chunks/1550.js +1 -1
  136. package/_standalone/.next/server/chunks/2190.js +11 -0
  137. package/_standalone/.next/server/chunks/{6365.js → 2536.js} +2 -2
  138. package/_standalone/.next/server/chunks/5648.js +2 -0
  139. package/_standalone/.next/server/chunks/8388.js +1 -1
  140. package/_standalone/.next/server/chunks/953.js +1 -1
  141. package/_standalone/.next/server/chunks/9539.js +219 -0
  142. package/_standalone/.next/server/middleware-build-manifest.js +1 -1
  143. package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  144. package/_standalone/.next/server/next-font-manifest.js +1 -1
  145. package/_standalone/.next/server/next-font-manifest.json +1 -1
  146. package/_standalone/.next/server/pages/500.html +2 -2
  147. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  148. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  149. package/_standalone/.next/static/chunks/1053-b70535785cc5aaee.js +29 -0
  150. package/_standalone/.next/static/chunks/{8663-de911d2d395622be.js → 1880-c2a9e76201841c86.js} +1 -1
  151. package/_standalone/.next/static/chunks/3637.0541ac2d0ea7de1f.js +1 -0
  152. package/_standalone/.next/static/chunks/4563-b2a2ce80aff845af.js +6 -0
  153. package/_standalone/.next/static/chunks/6981-3d7dcac2d12a5670.js +1 -0
  154. package/_standalone/.next/static/chunks/7144-5febf62f1a79fe64.js +1 -0
  155. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-773071a99c4daac2.js +1 -0
  156. package/_standalone/.next/static/chunks/app/agents/page-6102a884b2cb3cfe.js +5 -0
  157. package/_standalone/.next/static/chunks/app/help/page-2325d25b6846ca07.js +1 -0
  158. package/_standalone/.next/static/chunks/app/{layout-9378c1c8d3e5761b.js → layout-42cdbce19f404567.js} +34 -34
  159. package/_standalone/.next/static/chunks/app/{page-9bae420fbbdc5fff.js → page-8c9643b649e01735.js} +1 -1
  160. package/_standalone/.next/static/chunks/app/setup/page-d158b8cb533feb1e.js +1 -0
  161. package/_standalone/.next/static/chunks/app/trash/{page-b61ef2d5cd4f8d73.js → page-e9ab74ffeb96af41.js} +1 -1
  162. package/_standalone/.next/static/chunks/app/view/[...path]/page-764a69a1c8bd4eef.js +12 -0
  163. package/_standalone/.next/static/chunks/{webpack-c28c55d0a6021a6b.js → webpack-7b276daaa930d480.js} +1 -1
  164. package/_standalone/.next/static/css/bc9179074eaf65ae.css +1 -0
  165. package/_standalone/.next/trace +63 -63
  166. package/_standalone/__tests__/api/mcp-install.test.ts +23 -0
  167. package/_standalone/__tests__/cli/agent-routing.test.ts +232 -0
  168. package/_standalone/__tests__/cli/file-subcommands.test.ts +379 -0
  169. package/_standalone/__tests__/core/tools.test.ts +3 -6
  170. package/_standalone/components/FileTree.tsx +3 -2
  171. package/_standalone/components/MarkdownView.tsx +30 -15
  172. package/_standalone/components/RightAskPanel.tsx +36 -6
  173. package/_standalone/components/Sidebar.tsx +3 -3
  174. package/_standalone/components/agents/AgentsMcpSection.tsx +3 -0
  175. package/_standalone/components/settings/McpAgentInstall.tsx +94 -27
  176. package/_standalone/components/settings/McpSkillsSection.tsx +1 -1
  177. package/_standalone/components/settings/McpTab.tsx +484 -340
  178. package/_standalone/components/settings/SettingsContent.tsx +12 -6
  179. package/_standalone/components/settings/types.ts +3 -0
  180. package/_standalone/components/setup/StepAgents.tsx +113 -47
  181. package/_standalone/components/setup/StepReview.tsx +14 -27
  182. package/_standalone/components/setup/types.ts +6 -0
  183. package/_standalone/data/skills/mindos/SKILL.md +92 -92
  184. package/_standalone/data/skills/mindos/references/write-supplement.md +119 -0
  185. package/_standalone/data/skills/mindos-zh/SKILL.md +100 -104
  186. package/_standalone/data/skills/mindos-zh/references/write-supplement.md +119 -0
  187. package/_standalone/lib/i18n/modules/features.ts +4 -4
  188. package/_standalone/lib/i18n/modules/knowledge.ts +4 -0
  189. package/_standalone/lib/i18n/modules/onboarding.ts +40 -30
  190. package/_standalone/lib/i18n/modules/settings.ts +78 -6
  191. package/_standalone/lib/mcp-snippets.ts +5 -1
  192. package/_standalone/tsconfig.tsbuildinfo +1 -1
  193. package/app/app/api/ask/route.ts +3 -2
  194. package/app/app/api/mcp/install/route.ts +2 -1
  195. package/app/app/api/mcp/status/route.ts +14 -6
  196. package/app/app/view/[...path]/ViewPageClient.tsx +12 -27
  197. package/app/components/FileTree.tsx +3 -2
  198. package/app/components/MarkdownView.tsx +30 -15
  199. package/app/components/RightAskPanel.tsx +36 -6
  200. package/app/components/Sidebar.tsx +3 -3
  201. package/app/components/agents/AgentsMcpSection.tsx +3 -0
  202. package/app/components/help/HelpContent.tsx +1 -0
  203. package/app/components/settings/McpAgentInstall.tsx +94 -27
  204. package/app/components/settings/McpSkillsSection.tsx +1 -1
  205. package/app/components/settings/McpTab.tsx +484 -340
  206. package/app/components/settings/SettingsContent.tsx +12 -6
  207. package/app/components/settings/types.ts +3 -0
  208. package/app/components/setup/StepAgents.tsx +113 -47
  209. package/app/components/setup/StepReview.tsx +14 -27
  210. package/app/components/setup/index.tsx +12 -11
  211. package/app/components/setup/types.ts +6 -0
  212. package/app/data/skills/mindos/SKILL.md +92 -92
  213. package/app/data/skills/mindos/references/write-supplement.md +119 -0
  214. package/app/data/skills/mindos-zh/SKILL.md +100 -104
  215. package/app/data/skills/mindos-zh/references/write-supplement.md +119 -0
  216. package/app/lib/fs.ts +0 -6
  217. package/app/lib/i18n/modules/features.ts +4 -4
  218. package/app/lib/i18n/modules/knowledge.ts +4 -0
  219. package/app/lib/i18n/modules/onboarding.ts +40 -30
  220. package/app/lib/i18n/modules/settings.ts +78 -6
  221. package/app/lib/mcp-agents.ts +1 -2
  222. package/app/lib/mcp-snippets.ts +5 -1
  223. package/app/lib/renderers/index.ts +2 -1
  224. package/bin/cli.js +168 -1404
  225. package/bin/commands/agent.js +156 -20
  226. package/bin/commands/api.js +14 -11
  227. package/bin/commands/ask.js +79 -68
  228. package/bin/commands/build.js +26 -0
  229. package/bin/commands/config.js +170 -0
  230. package/bin/commands/dev.js +58 -0
  231. package/bin/commands/doctor.js +205 -0
  232. package/bin/commands/file.js +551 -36
  233. package/bin/commands/gateway.js +42 -0
  234. package/bin/commands/init-skills.js +56 -0
  235. package/bin/commands/logs.js +32 -0
  236. package/bin/commands/mcp-cmd.js +57 -0
  237. package/bin/commands/onboard.js +25 -0
  238. package/bin/commands/open.js +41 -0
  239. package/bin/commands/restart.js +48 -0
  240. package/bin/commands/search.js +16 -14
  241. package/bin/commands/space.js +96 -25
  242. package/bin/commands/start.js +262 -0
  243. package/bin/commands/status.js +2 -2
  244. package/bin/commands/stop.js +14 -0
  245. package/bin/commands/sync-cmd.js +134 -0
  246. package/bin/commands/token.js +98 -0
  247. package/bin/commands/uninstall.js +154 -0
  248. package/bin/commands/update.js +286 -0
  249. package/bin/lib/build.js +1 -1
  250. package/bin/lib/colors.js +8 -7
  251. package/bin/lib/command.js +37 -96
  252. package/bin/lib/config.js +5 -0
  253. package/bin/lib/csv.js +19 -0
  254. package/bin/lib/jsonc.js +12 -0
  255. package/bin/lib/markdown.js +69 -0
  256. package/bin/lib/mcp-agents.js +1 -6
  257. package/bin/lib/mcp-build.js +1 -1
  258. package/bin/lib/mcp-install.js +2 -1
  259. package/bin/lib/one-shot.js +88 -0
  260. package/bin/lib/path-expand.js +9 -0
  261. package/bin/lib/remote.js +65 -0
  262. package/bin/lib/repl.js +167 -0
  263. package/bin/lib/{utils.js → shell.js} +10 -26
  264. package/bin/lib/skill-check.js +1 -1
  265. package/bin/lib/sse-stream.js +167 -0
  266. package/package.json +2 -2
  267. package/scripts/setup.js +182 -120
  268. package/skills/mindos/SKILL.md +92 -92
  269. package/skills/mindos-zh/SKILL.md +100 -104
  270. package/_standalone/.next/server/chunks/1955.js +0 -11
  271. package/_standalone/.next/server/chunks/3680.js +0 -1
  272. package/_standalone/.next/server/chunks/4497.js +0 -219
  273. package/_standalone/.next/server/chunks/5560.js +0 -2
  274. package/_standalone/.next/static/chunks/1053-0adaccc98a752a58.js +0 -29
  275. package/_standalone/.next/static/chunks/3637.f9a42cca59fd5bb5.js +0 -1
  276. package/_standalone/.next/static/chunks/4563-c2afaeacb241d1d0.js +0 -6
  277. package/_standalone/.next/static/chunks/6090-c98268ca726a68d3.js +0 -1
  278. package/_standalone/.next/static/chunks/9371-575600301da5d6bb.js +0 -1
  279. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-3e08abb495ecd5fd.js +0 -1
  280. package/_standalone/.next/static/chunks/app/agents/page-e7e0f87ad3d765ac.js +0 -5
  281. package/_standalone/.next/static/chunks/app/help/page-3d0e1ceaa4abc243.js +0 -1
  282. package/_standalone/.next/static/chunks/app/setup/page-99ed3d1bb6b8f4ef.js +0 -1
  283. package/_standalone/.next/static/chunks/app/view/[...path]/page-44fa78cbea613a78.js +0 -12
  284. package/_standalone/.next/static/css/d300701f384db50d.css +0 -1
  285. package/_standalone/components/renderers/agent-inspector/manifest.ts +0 -16
  286. /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_buildManifest.js +0 -0
  287. /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_ssgManifest.js +0 -0
@@ -176,6 +176,29 @@ describe('POST /api/mcp/install', () => {
176
176
  expect(content).toContain('[mcp_servers.mindos]');
177
177
  });
178
178
 
179
+ it('handles empty config file gracefully (e.g. fresh VS Code mcp.json)', async () => {
180
+ const { POST } = await importInstallRoute();
181
+ // Pre-create an empty config file (common with VS Code)
182
+ const copilotDir = path.join(tempHome, '.config', 'Code', 'User');
183
+ fs.mkdirSync(copilotDir, { recursive: true });
184
+ fs.writeFileSync(path.join(copilotDir, 'mcp.json'), '', 'utf-8');
185
+
186
+ const req = new NextRequest('http://localhost/api/mcp/install', {
187
+ method: 'POST',
188
+ body: JSON.stringify({
189
+ agents: [{ key: 'github-copilot', scope: 'global' }],
190
+ transport: 'stdio',
191
+ }),
192
+ headers: { 'content-type': 'application/json' },
193
+ });
194
+ const res = await POST(req);
195
+ const body = await res.json();
196
+ expect(body.results[0].status).toBe('ok');
197
+
198
+ const config = JSON.parse(fs.readFileSync(path.join(copilotDir, 'mcp.json'), 'utf-8'));
199
+ expect(config.servers.mindos.type).toBe('stdio');
200
+ });
201
+
179
202
  it('installs github-copilot agent with servers key for global scope', async () => {
180
203
  const { POST } = await importInstallRoute();
181
204
  const req = new NextRequest('http://localhost/api/mcp/install', {
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Tests for agent/ask CLI routing logic:
3
+ * - `-p` flag → non-interactive (print mode)
4
+ * - No `-p` + no args → interactive REPL
5
+ * - Management subcommands → direct execution
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+
10
+ const MANAGEMENT_SUBCOMMANDS = new Set(['list', 'ls', 'info', 'stats', 'help']);
11
+
12
+ type AgentRoute = 'interactive' | 'print' | 'management';
13
+
14
+ function classifyAgentArgs(
15
+ args: string[],
16
+ flags: Record<string, unknown> = {},
17
+ ): AgentRoute {
18
+ const sub = args[0];
19
+
20
+ // Management subcommands always take priority
21
+ if (sub && MANAGEMENT_SUBCOMMANDS.has(sub)) return 'management';
22
+
23
+ // -p / --print → non-interactive print mode
24
+ if (flags.p || flags.print) return 'print';
25
+
26
+ // Has task text without -p → also print mode (backward compat: inline task)
27
+ if (sub) return 'print';
28
+
29
+ // No args, no -p → interactive REPL
30
+ return 'interactive';
31
+ }
32
+
33
+ function classifyAskArgs(
34
+ args: string[],
35
+ flags: Record<string, unknown> = {},
36
+ ): 'interactive' | 'print' {
37
+ if (flags.p || flags.print) return 'print';
38
+ if (args.length > 0) return 'print';
39
+ return 'interactive';
40
+ }
41
+
42
+ function buildAskBody(
43
+ content: string | string[],
44
+ mode: 'agent' | 'chat',
45
+ opts: { file?: string; maxSteps?: number } = {},
46
+ ) {
47
+ const messages = Array.isArray(content)
48
+ ? content.map((c, i) => ({
49
+ role: i % 2 === 0 ? 'user' : 'assistant',
50
+ content: c,
51
+ timestamp: Date.now(),
52
+ }))
53
+ : [{ role: 'user', content, timestamp: Date.now() }];
54
+
55
+ const body: Record<string, unknown> = { messages, mode };
56
+ if (opts.file) body.attachedFiles = [opts.file];
57
+ if (opts.maxSteps) body.maxSteps = opts.maxSteps;
58
+ return body;
59
+ }
60
+
61
+ // ── Agent routing ─────────────────────────────────────────────────────────────
62
+
63
+ describe('Agent CLI routing', () => {
64
+ it('routes empty args (no -p) to interactive', () => {
65
+ expect(classifyAgentArgs([])).toBe('interactive');
66
+ });
67
+
68
+ it('routes -p flag with task to print mode', () => {
69
+ expect(classifyAgentArgs(['整理笔记'], { p: true })).toBe('print');
70
+ });
71
+
72
+ it('routes --print flag with task to print mode', () => {
73
+ expect(classifyAgentArgs(['do stuff'], { print: true })).toBe('print');
74
+ });
75
+
76
+ it('routes bare task (no -p) to print mode for backward compat', () => {
77
+ expect(classifyAgentArgs(['Summarize my notes'])).toBe('print');
78
+ });
79
+
80
+ it('routes "list" to management regardless of -p', () => {
81
+ expect(classifyAgentArgs(['list'])).toBe('management');
82
+ expect(classifyAgentArgs(['list'], { p: true })).toBe('management');
83
+ });
84
+
85
+ it('routes "ls" alias to management', () => {
86
+ expect(classifyAgentArgs(['ls'])).toBe('management');
87
+ });
88
+
89
+ it('routes "info" to management', () => {
90
+ expect(classifyAgentArgs(['info', 'cursor'])).toBe('management');
91
+ });
92
+
93
+ it('routes "stats" to management', () => {
94
+ expect(classifyAgentArgs(['stats'])).toBe('management');
95
+ });
96
+
97
+ it('routes "help" to management', () => {
98
+ expect(classifyAgentArgs(['help'])).toBe('management');
99
+ });
100
+
101
+ it('routes Chinese task text to print mode', () => {
102
+ expect(classifyAgentArgs(['整理我的笔记'])).toBe('print');
103
+ });
104
+
105
+ it('does not misroute words starting with subcommand prefix', () => {
106
+ expect(classifyAgentArgs(['listing', 'all', 'files'])).toBe('print');
107
+ expect(classifyAgentArgs(['information', 'about', 'RAG'])).toBe('print');
108
+ });
109
+
110
+ it('routes -p with no task text to print mode', () => {
111
+ expect(classifyAgentArgs([], { p: true })).toBe('print');
112
+ });
113
+ });
114
+
115
+ // ── Ask routing ───────────────────────────────────────────────────────────────
116
+
117
+ describe('Ask CLI routing', () => {
118
+ it('routes empty args to interactive', () => {
119
+ expect(classifyAskArgs([])).toBe('interactive');
120
+ });
121
+
122
+ it('routes -p with question to print', () => {
123
+ expect(classifyAskArgs(['what is RAG'], { p: true })).toBe('print');
124
+ });
125
+
126
+ it('routes bare question (no -p) to print for backward compat', () => {
127
+ expect(classifyAskArgs(['what is RAG'])).toBe('print');
128
+ });
129
+
130
+ it('routes --print flag to print', () => {
131
+ expect(classifyAskArgs(['hello'], { print: true })).toBe('print');
132
+ });
133
+ });
134
+
135
+ // ── API body construction ─────────────────────────────────────────────────────
136
+
137
+ describe('Ask API body construction', () => {
138
+ it('builds agent mode body with correct message format', () => {
139
+ const body = buildAskBody('do something', 'agent');
140
+ expect(body.mode).toBe('agent');
141
+ expect(body.messages).toHaveLength(1);
142
+ const msg = (body.messages as any[])[0];
143
+ expect(msg.role).toBe('user');
144
+ expect(msg.content).toBe('do something');
145
+ expect(msg.timestamp).toBeTypeOf('number');
146
+ });
147
+
148
+ it('builds chat mode body', () => {
149
+ const body = buildAskBody('what is RAG', 'chat');
150
+ expect(body.mode).toBe('chat');
151
+ });
152
+
153
+ it('builds multi-turn conversation body', () => {
154
+ const body = buildAskBody(['hello', 'hi there', 'how are you'], 'chat');
155
+ expect(body.messages).toHaveLength(3);
156
+ const msgs = body.messages as any[];
157
+ expect(msgs[0].role).toBe('user');
158
+ expect(msgs[1].role).toBe('assistant');
159
+ expect(msgs[2].role).toBe('user');
160
+ });
161
+
162
+ it('attaches file when provided', () => {
163
+ const body = buildAskBody('summarize', 'agent', { file: 'notes.md' });
164
+ expect(body.attachedFiles).toEqual(['notes.md']);
165
+ });
166
+
167
+ it('sets maxSteps when provided', () => {
168
+ const body = buildAskBody('do task', 'agent', { maxSteps: 10 });
169
+ expect(body.maxSteps).toBe(10);
170
+ });
171
+
172
+ it('omits attachedFiles and maxSteps when not provided', () => {
173
+ const body = buildAskBody('simple task', 'agent');
174
+ expect(body.attachedFiles).toBeUndefined();
175
+ expect(body.maxSteps).toBeUndefined();
176
+ });
177
+ });
178
+
179
+ describe('SSE event parsing', () => {
180
+ function parseSSELine(line: string): { type: string; [key: string]: unknown } | null {
181
+ if (!line.startsWith('data:')) return null;
182
+ const payload = line.slice(5);
183
+ if (!payload) return null;
184
+ try {
185
+ return JSON.parse(payload);
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+
191
+ it('parses text_delta event', () => {
192
+ const event = parseSSELine('data:{"type":"text_delta","text":"Hello"}');
193
+ expect(event).toEqual({ type: 'text_delta', text: 'Hello' });
194
+ });
195
+
196
+ it('parses tool_start event', () => {
197
+ const event = parseSSELine('data:{"type":"tool_start","name":"search_notes","input":{"query":"today"}}');
198
+ expect(event?.type).toBe('tool_start');
199
+ expect(event?.name).toBe('search_notes');
200
+ });
201
+
202
+ it('parses tool_end event', () => {
203
+ const event = parseSSELine('data:{"type":"tool_end","result":"found 3 notes"}');
204
+ expect(event?.type).toBe('tool_end');
205
+ expect(event?.result).toBe('found 3 notes');
206
+ });
207
+
208
+ it('parses error event', () => {
209
+ const event = parseSSELine('data:{"type":"error","message":"API key not configured"}');
210
+ expect(event?.type).toBe('error');
211
+ expect(event?.message).toBe('API key not configured');
212
+ });
213
+
214
+ it('parses done event', () => {
215
+ const event = parseSSELine('data:{"type":"done"}');
216
+ expect(event?.type).toBe('done');
217
+ });
218
+
219
+ it('returns null for non-data lines', () => {
220
+ expect(parseSSELine('event: message')).toBeNull();
221
+ expect(parseSSELine('')).toBeNull();
222
+ expect(parseSSELine('retry: 3000')).toBeNull();
223
+ });
224
+
225
+ it('returns null for empty data', () => {
226
+ expect(parseSSELine('data:')).toBeNull();
227
+ });
228
+
229
+ it('returns null for invalid JSON', () => {
230
+ expect(parseSSELine('data:{broken')).toBeNull();
231
+ });
232
+ });
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Tests for new `mindos file` subcommands:
3
+ * write, append, edit-section, insert-heading, append-csv, backlinks, recent, history
4
+ *
5
+ * Tests the pure-logic modules (markdown.js, csv.js) and integration via file.js helpers.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import fs from 'fs';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import { execFileSync } from 'child_process';
13
+
14
+ function mkTmp(): string {
15
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'mindos-cli-file-'));
16
+ }
17
+ function cleanup(dir: string): void {
18
+ fs.rmSync(dir, { recursive: true, force: true });
19
+ }
20
+ function seed(root: string, rel: string, content: string): void {
21
+ const abs = path.join(root, rel);
22
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
23
+ fs.writeFileSync(abs, content, 'utf-8');
24
+ }
25
+ function read(root: string, rel: string): string {
26
+ return fs.readFileSync(path.join(root, rel), 'utf-8');
27
+ }
28
+
29
+ // ── Markdown section logic (ported from app/lib/core/lines.ts) ──
30
+
31
+ function findHeadingIndex(lines: string[], heading: string): number {
32
+ return lines.findIndex(l => {
33
+ const trimmed = l.trim();
34
+ return trimmed === heading || trimmed.replace(/^#+\s*/, '') === heading.replace(/^#+\s*/, '');
35
+ });
36
+ }
37
+
38
+ function replaceSection(content: string, heading: string, newBody: string): string {
39
+ const lines = content.split('\n');
40
+ const idx = findHeadingIndex(lines, heading);
41
+ if (idx === -1) return '';
42
+
43
+ const headingLevel = (lines[idx].match(/^#+/) ?? [''])[0].length;
44
+ let sectionEnd = lines.length - 1;
45
+ for (let i = idx + 1; i < lines.length; i++) {
46
+ const m = lines[i].match(/^(#+)\s/);
47
+ if (m && m[1].length <= headingLevel) {
48
+ sectionEnd = i - 1;
49
+ break;
50
+ }
51
+ }
52
+ while (sectionEnd > idx && lines[sectionEnd].trim() === '') sectionEnd--;
53
+
54
+ const before = lines.slice(0, idx + 1);
55
+ const after = lines.slice(sectionEnd + 1);
56
+ return [...before, '', newBody, ...after].join('\n');
57
+ }
58
+
59
+ function insertAfterHeading(content: string, heading: string, insertion: string): string {
60
+ const lines = content.split('\n');
61
+ const idx = findHeadingIndex(lines, heading);
62
+ if (idx === -1) return '';
63
+
64
+ let insertAt = idx + 1;
65
+ while (insertAt < lines.length && lines[insertAt].trim() === '') insertAt++;
66
+
67
+ const before = lines.slice(0, insertAt);
68
+ const after = lines.slice(insertAt);
69
+ return [...before, '', insertion, ...after].join('\n');
70
+ }
71
+
72
+ function escapeCsvRow(row: string[]): string {
73
+ return row.map(cell => {
74
+ if (cell.includes(',') || cell.includes('"') || cell.includes('\n')) {
75
+ return `"${cell.replace(/"/g, '""')}"`;
76
+ }
77
+ return cell;
78
+ }).join(',');
79
+ }
80
+
81
+ function listHeadings(content: string): string[] {
82
+ return content.split('\n')
83
+ .filter(l => /^#{1,6}\s/.test(l))
84
+ .map(l => l.trim());
85
+ }
86
+
87
+ // ── Markdown replaceSection tests ──
88
+
89
+ describe('replaceSection', () => {
90
+ it('replaces section body between same-level headings', () => {
91
+ const input = '# Title\n\n## A\n\nOld A content\n\n## B\n\nB content';
92
+ const result = replaceSection(input, '## A', 'New A content');
93
+ expect(result).toContain('## A');
94
+ expect(result).toContain('New A content');
95
+ expect(result).not.toContain('Old A content');
96
+ expect(result).toContain('## B');
97
+ expect(result).toContain('B content');
98
+ });
99
+
100
+ it('replaces last section (no following heading)', () => {
101
+ const input = '# Title\n\n## A\n\nA content\n\n## B\n\nOld B';
102
+ const result = replaceSection(input, '## B', 'New B');
103
+ expect(result).toContain('New B');
104
+ expect(result).not.toContain('Old B');
105
+ });
106
+
107
+ it('returns empty string when heading not found', () => {
108
+ const input = '# Title\n\n## A\n\nContent';
109
+ expect(replaceSection(input, '## Missing', 'x')).toBe('');
110
+ });
111
+
112
+ it('handles heading with extra spaces', () => {
113
+ const input = '## Status \n\nOld status';
114
+ const result = replaceSection(input, '## Status', 'New status');
115
+ expect(result).toContain('New status');
116
+ });
117
+
118
+ it('stops at higher-level heading', () => {
119
+ const input = '# Title\n\n## A\n\nA stuff\n\n### Sub\n\nSub stuff\n\n## B\n\nB stuff';
120
+ const result = replaceSection(input, '## A', 'Replaced');
121
+ expect(result).toContain('Replaced');
122
+ expect(result).toContain('## B');
123
+ expect(result).toContain('B stuff');
124
+ expect(result).not.toContain('A stuff');
125
+ expect(result).not.toContain('Sub stuff');
126
+ });
127
+
128
+ it('replaces section with empty content', () => {
129
+ const input = '## A\n\nContent\n\n## B\n\nMore';
130
+ const result = replaceSection(input, '## A', '');
131
+ expect(result).toContain('## A');
132
+ expect(result).not.toContain('Content');
133
+ });
134
+ });
135
+
136
+ // ── insertAfterHeading tests ──
137
+
138
+ describe('insertAfterHeading', () => {
139
+ it('inserts after heading, before existing content', () => {
140
+ const input = '## Notes\n\nExisting note';
141
+ const result = insertAfterHeading(input, '## Notes', 'New note');
142
+ expect(result).toContain('## Notes');
143
+ expect(result).toContain('New note');
144
+ expect(result).toContain('Existing note');
145
+ const lines = result.split('\n');
146
+ const notesIdx = lines.findIndex(l => l.includes('## Notes'));
147
+ const newIdx = lines.findIndex(l => l.includes('New note'));
148
+ const existIdx = lines.findIndex(l => l.includes('Existing note'));
149
+ expect(newIdx).toBeGreaterThan(notesIdx);
150
+ expect(newIdx).toBeLessThan(existIdx);
151
+ });
152
+
153
+ it('returns empty string when heading not found', () => {
154
+ expect(insertAfterHeading('# Title', '## Missing', 'x')).toBe('');
155
+ });
156
+
157
+ it('inserts at end if heading is last thing', () => {
158
+ const input = '## Notes';
159
+ const result = insertAfterHeading(input, '## Notes', 'Added');
160
+ expect(result).toContain('## Notes');
161
+ expect(result).toContain('Added');
162
+ });
163
+ });
164
+
165
+ // ── CSV escaping tests ──
166
+
167
+ describe('escapeCsvRow', () => {
168
+ it('joins simple values', () => {
169
+ expect(escapeCsvRow(['a', 'b', 'c'])).toBe('a,b,c');
170
+ });
171
+
172
+ it('quotes values containing commas', () => {
173
+ expect(escapeCsvRow(['a,b', 'c'])).toBe('"a,b",c');
174
+ });
175
+
176
+ it('escapes double quotes', () => {
177
+ expect(escapeCsvRow(['say "hello"', 'x'])).toBe('"say ""hello""",x');
178
+ });
179
+
180
+ it('quotes values containing newlines', () => {
181
+ expect(escapeCsvRow(['line1\nline2', 'x'])).toBe('"line1\nline2",x');
182
+ });
183
+
184
+ it('handles empty strings', () => {
185
+ expect(escapeCsvRow(['', '', ''])).toBe(',,');
186
+ });
187
+
188
+ it('handles single value', () => {
189
+ expect(escapeCsvRow(['only'])).toBe('only');
190
+ });
191
+ });
192
+
193
+ // ── listHeadings tests ──
194
+
195
+ describe('listHeadings', () => {
196
+ it('extracts all markdown headings', () => {
197
+ const input = '# Title\n\nSome text\n\n## A\n\nContent\n\n### Sub\n\n## B';
198
+ expect(listHeadings(input)).toEqual(['# Title', '## A', '### Sub', '## B']);
199
+ });
200
+
201
+ it('returns empty for no headings', () => {
202
+ expect(listHeadings('Just text\nMore text')).toEqual([]);
203
+ });
204
+
205
+ it('ignores lines that look like headings inside code blocks', () => {
206
+ expect(listHeadings('## Real\n\n```\n## Not a heading\n```')).toContain('## Real');
207
+ });
208
+ });
209
+
210
+ // ── Backlinks logic tests ──
211
+
212
+ describe('findBacklinksLocal', () => {
213
+ let root: string;
214
+ beforeEach(() => { root = mkTmp(); });
215
+ afterEach(() => { cleanup(root); });
216
+
217
+ function findBacklinksLocal(mindRoot: string, targetPath: string): string[] {
218
+ const results: string[] = [];
219
+ const bname = path.basename(targetPath, '.md');
220
+ const escapedTarget = targetPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
221
+ const escapedBname = bname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
222
+
223
+ const patterns = [
224
+ new RegExp(`\\[\\[${escapedBname}(?:[|#][^\\]]*)?\\]\\]`, 'i'),
225
+ new RegExp(`\\[\\[${escapedTarget}(?:[|#][^\\]]*)?\\]\\]`, 'i'),
226
+ new RegExp(`\\[[^\\]]+\\]\\(${escapedTarget}(?:#[^)]*)?\\)`, 'i'),
227
+ ];
228
+
229
+ function walk(dir: string): void {
230
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
231
+ if (entry.name.startsWith('.')) continue;
232
+ const full = path.join(dir, entry.name);
233
+ if (entry.isDirectory()) { walk(full); continue; }
234
+ if (!entry.name.endsWith('.md')) continue;
235
+ const rel = path.relative(mindRoot, full);
236
+ if (rel === targetPath) continue;
237
+ const content = fs.readFileSync(full, 'utf-8');
238
+ if (patterns.some(p => p.test(content))) results.push(rel);
239
+ }
240
+ }
241
+ walk(mindRoot);
242
+ return results;
243
+ }
244
+
245
+ it('finds wikilink references', () => {
246
+ seed(root, 'target.md', '# Target');
247
+ seed(root, 'linker.md', 'See [[target]] for details');
248
+ expect(findBacklinksLocal(root, 'target.md')).toEqual(['linker.md']);
249
+ });
250
+
251
+ it('finds markdown link references', () => {
252
+ seed(root, 'notes/plan.md', '# Plan');
253
+ seed(root, 'index.md', 'Check [the plan](notes/plan.md) here');
254
+ expect(findBacklinksLocal(root, 'notes/plan.md')).toEqual(['index.md']);
255
+ });
256
+
257
+ it('returns empty when no references', () => {
258
+ seed(root, 'a.md', '# A');
259
+ seed(root, 'b.md', '# B - no links');
260
+ expect(findBacklinksLocal(root, 'a.md')).toEqual([]);
261
+ });
262
+
263
+ it('excludes self-references', () => {
264
+ seed(root, 'self.md', 'Link to [[self]]');
265
+ expect(findBacklinksLocal(root, 'self.md')).toEqual([]);
266
+ });
267
+ });
268
+
269
+ // ── Recent files logic tests ──
270
+
271
+ describe('getRecentFiles', () => {
272
+ let root: string;
273
+ beforeEach(() => { root = mkTmp(); });
274
+ afterEach(() => { cleanup(root); });
275
+
276
+ function getRecentFiles(mindRoot: string, limit: number): Array<{path: string; mtime: number}> {
277
+ const results: Array<{path: string; mtime: number}> = [];
278
+ function walk(dir: string): void {
279
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
280
+ if (entry.name.startsWith('.')) continue;
281
+ const full = path.join(dir, entry.name);
282
+ if (entry.isDirectory()) { walk(full); continue; }
283
+ if (!entry.name.endsWith('.md') && !entry.name.endsWith('.csv')) continue;
284
+ const stat = fs.statSync(full);
285
+ results.push({ path: path.relative(mindRoot, full), mtime: stat.mtimeMs });
286
+ }
287
+ }
288
+ walk(mindRoot);
289
+ results.sort((a, b) => b.mtime - a.mtime);
290
+ return results.slice(0, limit);
291
+ }
292
+
293
+ it('returns files sorted by modification time', () => {
294
+ seed(root, 'old.md', 'old');
295
+ const oldTime = Date.now() - 10000;
296
+ fs.utimesSync(path.join(root, 'old.md'), new Date(oldTime), new Date(oldTime));
297
+ seed(root, 'new.md', 'new');
298
+ const result = getRecentFiles(root, 10);
299
+ expect(result[0].path).toBe('new.md');
300
+ expect(result[1].path).toBe('old.md');
301
+ });
302
+
303
+ it('respects limit', () => {
304
+ seed(root, 'a.md', 'a');
305
+ seed(root, 'b.md', 'b');
306
+ seed(root, 'c.md', 'c');
307
+ expect(getRecentFiles(root, 2)).toHaveLength(2);
308
+ });
309
+
310
+ it('returns empty for empty directory', () => {
311
+ expect(getRecentFiles(root, 10)).toEqual([]);
312
+ });
313
+
314
+ it('skips hidden files', () => {
315
+ seed(root, '.hidden.md', 'hidden');
316
+ seed(root, 'visible.md', 'visible');
317
+ const result = getRecentFiles(root, 10);
318
+ expect(result).toHaveLength(1);
319
+ expect(result[0].path).toBe('visible.md');
320
+ });
321
+ });
322
+
323
+ // ── Git history tests (integration, requires git) ──
324
+
325
+ describe('gitHistory', () => {
326
+ let root: string;
327
+ let hasGit: boolean;
328
+
329
+ beforeEach(() => {
330
+ root = mkTmp();
331
+ try {
332
+ execFileSync('git', ['--version'], { stdio: 'pipe' });
333
+ hasGit = true;
334
+ } catch { hasGit = false; }
335
+ });
336
+ afterEach(() => { cleanup(root); });
337
+
338
+ it('returns git log entries for a file', () => {
339
+ if (!hasGit) return;
340
+ execFileSync('git', ['init'], { cwd: root, stdio: 'pipe' });
341
+ execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: root, stdio: 'pipe' });
342
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: root, stdio: 'pipe' });
343
+ seed(root, 'test.md', 'v1');
344
+ execFileSync('git', ['add', '.'], { cwd: root, stdio: 'pipe' });
345
+ execFileSync('git', ['commit', '-m', 'initial'], { cwd: root, stdio: 'pipe' });
346
+
347
+ const output = execFileSync(
348
+ 'git',
349
+ ['log', '--follow', '--format=%H%x00%aI%x00%s%x00%an', '-n', '10', '--', path.join(root, 'test.md')],
350
+ { cwd: root, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
351
+ ).trim();
352
+
353
+ const entries = output.split('\n').map(line => {
354
+ const [hash, date, message, author] = line.split('\0');
355
+ return { hash, date, message, author };
356
+ });
357
+
358
+ expect(entries).toHaveLength(1);
359
+ expect(entries[0].message).toBe('initial');
360
+ expect(entries[0].author).toBe('Test');
361
+ });
362
+
363
+ it('returns empty for file with no commits', () => {
364
+ if (!hasGit) return;
365
+ execFileSync('git', ['init'], { cwd: root, stdio: 'pipe' });
366
+ seed(root, 'untracked.md', 'content');
367
+ let output = '';
368
+ try {
369
+ output = execFileSync(
370
+ 'git',
371
+ ['log', '--follow', '--format=%H%x00%aI%x00%s%x00%an', '-n', '10', '--', path.join(root, 'untracked.md')],
372
+ { cwd: root, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
373
+ ).trim();
374
+ } catch {
375
+ output = '';
376
+ }
377
+ expect(output).toBe('');
378
+ });
379
+ });
@@ -57,12 +57,12 @@ describe('tools: list_files', () => {
57
57
  seedFile('data.csv', 'a,b,c');
58
58
  });
59
59
 
60
- it('lists all files with default params (excluding system files)', async () => {
60
+ it('lists all files with default params', async () => {
61
61
  const result = await callTool('list_files', {});
62
62
  expect(result).toContain('Profile/');
63
63
  expect(result).toContain('Identity.md');
64
64
  expect(result).toContain('Goals.md');
65
- expect(result).not.toContain('README.md'); // System file hidden from Agent
65
+ expect(result).toContain('README.md'); // System files now included in tree
66
66
  expect(result).toContain('data.csv');
67
67
  expect(result).not.toContain('Error:');
68
68
  });
@@ -71,15 +71,13 @@ describe('tools: list_files', () => {
71
71
  const result = await callTool('list_files', { path: 'Profile' });
72
72
  expect(result).toContain('Identity.md');
73
73
  expect(result).toContain('Goals.md');
74
- expect(result).not.toContain('README.md');
75
74
  expect(result).not.toContain('data.csv');
76
75
  });
77
76
 
78
- it('respects depth parameter (hides system files)', async () => {
77
+ it('respects depth parameter', async () => {
79
78
  const result = await callTool('list_files', { depth: 1 });
80
79
  expect(result).toContain('Profile/');
81
80
  expect(result).toContain('...');
82
- expect(result).not.toContain('README.md'); // System file hidden
83
81
  });
84
82
 
85
83
  it('returns error message for non-existent directory', async () => {
@@ -91,7 +89,6 @@ describe('tools: list_files', () => {
91
89
  it('handles nested subdirectory path', async () => {
92
90
  const result = await callTool('list_files', { path: 'Projects/Products' });
93
91
  expect(result).toContain('ProductA.md');
94
- expect(result).not.toContain('README.md');
95
92
  });
96
93
 
97
94
  it('returns (empty directory) for dir with no allowed files', async () => {