@diff-review-system/drs 4.0.0-rc.4 → 4.0.1

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 (315) hide show
  1. package/.pi/agents/describe/pr-describer.md +14 -0
  2. package/.pi/agents/review/unified-reviewer.md +31 -1
  3. package/.pi/agents/task/agents-md-updater.md +3 -1
  4. package/.pi/agents/task/review-issue-fixer.md +18 -1
  5. package/.pi/agents/visual/pr-explainer.md +205 -0
  6. package/.pi/workflows/github-pr-describe.yaml +10 -7
  7. package/.pi/workflows/github-pr-fix-review-issues-stacked.yaml +148 -0
  8. package/.pi/workflows/github-pr-post-comment.yaml +10 -10
  9. package/.pi/workflows/github-pr-review-post.yaml +19 -8
  10. package/.pi/workflows/github-pr-review.yaml +348 -7
  11. package/.pi/workflows/github-pr-show-changes.yaml +8 -8
  12. package/.pi/workflows/github-pr-update-agents-md-stacked.yaml +103 -0
  13. package/.pi/workflows/github-pr-visual-explain.yaml +35 -0
  14. package/.pi/workflows/gitlab-mr-describe.yaml +8 -5
  15. package/.pi/workflows/gitlab-mr-fix-review-issues-stacked.yaml +144 -0
  16. package/.pi/workflows/gitlab-mr-post-comment.yaml +8 -8
  17. package/.pi/workflows/gitlab-mr-review.yaml +348 -5
  18. package/.pi/workflows/gitlab-mr-show-changes.yaml +6 -6
  19. package/.pi/workflows/gitlab-mr-update-agents-md-stacked.yaml +100 -0
  20. package/.pi/workflows/gitlab-mr-visual-explain.yaml +33 -0
  21. package/.pi/workflows/local-fix-review-issues.yaml +82 -13
  22. package/.pi/workflows/local-review.yaml +9 -2
  23. package/.pi/workflows/local-update-agents-md.yaml +1 -1
  24. package/.pi/workflows/local-visual-explain.yaml +31 -0
  25. package/.pi/workflows/release-changelog-finalize.yaml +47 -0
  26. package/.pi/workflows/tag-changelog-update.yaml +4 -4
  27. package/README.md +91 -27
  28. package/dist/ci/runner.d.ts.map +1 -1
  29. package/dist/ci/runner.js +3 -1
  30. package/dist/ci/runner.js.map +1 -1
  31. package/dist/cli/index.js +48 -6
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/run-agent.d.ts +2 -0
  34. package/dist/cli/run-agent.d.ts.map +1 -1
  35. package/dist/cli/run-agent.js +4 -0
  36. package/dist/cli/run-agent.js.map +1 -1
  37. package/dist/cli/workflow.d.ts +56 -2
  38. package/dist/cli/workflow.d.ts.map +1 -1
  39. package/dist/cli/workflow.js +2165 -85
  40. package/dist/cli/workflow.js.map +1 -1
  41. package/dist/github/client.d.ts +12 -0
  42. package/dist/github/client.d.ts.map +1 -1
  43. package/dist/github/client.js +27 -0
  44. package/dist/github/client.js.map +1 -1
  45. package/dist/github/platform-adapter.d.ts +6 -1
  46. package/dist/github/platform-adapter.d.ts.map +1 -1
  47. package/dist/github/platform-adapter.js +84 -8
  48. package/dist/github/platform-adapter.js.map +1 -1
  49. package/dist/gitlab/client.d.ts +11 -0
  50. package/dist/gitlab/client.d.ts.map +1 -1
  51. package/dist/gitlab/client.js +11 -0
  52. package/dist/gitlab/client.js.map +1 -1
  53. package/dist/gitlab/platform-adapter.d.ts +3 -1
  54. package/dist/gitlab/platform-adapter.d.ts.map +1 -1
  55. package/dist/gitlab/platform-adapter.js +32 -1
  56. package/dist/gitlab/platform-adapter.js.map +1 -1
  57. package/dist/lib/comment-formatter.d.ts +8 -0
  58. package/dist/lib/comment-formatter.d.ts.map +1 -1
  59. package/dist/lib/comment-formatter.js +12 -4
  60. package/dist/lib/comment-formatter.js.map +1 -1
  61. package/dist/lib/comment-poster.d.ts.map +1 -1
  62. package/dist/lib/comment-poster.js +28 -1
  63. package/dist/lib/comment-poster.js.map +1 -1
  64. package/dist/lib/config.d.ts +50 -11
  65. package/dist/lib/config.d.ts.map +1 -1
  66. package/dist/lib/config.js +163 -28
  67. package/dist/lib/config.js.map +1 -1
  68. package/dist/lib/context-compression.d.ts +10 -0
  69. package/dist/lib/context-compression.d.ts.map +1 -1
  70. package/dist/lib/context-compression.js +101 -13
  71. package/dist/lib/context-compression.js.map +1 -1
  72. package/dist/lib/context-loader.d.ts +2 -1
  73. package/dist/lib/context-loader.d.ts.map +1 -1
  74. package/dist/lib/context-loader.js +70 -1
  75. package/dist/lib/context-loader.js.map +1 -1
  76. package/dist/lib/describe-core.d.ts.map +1 -1
  77. package/dist/lib/describe-core.js +3 -2
  78. package/dist/lib/describe-core.js.map +1 -1
  79. package/dist/lib/diff-lines.d.ts +9 -0
  80. package/dist/lib/diff-lines.d.ts.map +1 -1
  81. package/dist/lib/diff-lines.js +17 -9
  82. package/dist/lib/diff-lines.js.map +1 -1
  83. package/dist/lib/exit.js +4 -4
  84. package/dist/lib/exit.js.map +1 -1
  85. package/dist/lib/html-artifact.d.ts +14 -0
  86. package/dist/lib/html-artifact.d.ts.map +1 -0
  87. package/dist/lib/html-artifact.js +59 -0
  88. package/dist/lib/html-artifact.js.map +1 -0
  89. package/dist/lib/issue-parser.js +3 -3
  90. package/dist/lib/issue-parser.js.map +1 -1
  91. package/dist/lib/json-output-schema.d.ts +70 -0
  92. package/dist/lib/json-output-schema.d.ts.map +1 -1
  93. package/dist/lib/json-output-schema.js +40 -0
  94. package/dist/lib/json-output-schema.js.map +1 -1
  95. package/dist/lib/platform-client.d.ts +26 -0
  96. package/dist/lib/platform-client.d.ts.map +1 -1
  97. package/dist/lib/review-artifact.d.ts +69 -0
  98. package/dist/lib/review-artifact.d.ts.map +1 -0
  99. package/dist/lib/review-artifact.js +171 -0
  100. package/dist/lib/review-artifact.js.map +1 -0
  101. package/dist/lib/review-core.d.ts +6 -4
  102. package/dist/lib/review-core.d.ts.map +1 -1
  103. package/dist/lib/review-core.js +71 -151
  104. package/dist/lib/review-core.js.map +1 -1
  105. package/dist/lib/review-orchestrator.d.ts +23 -0
  106. package/dist/lib/review-orchestrator.d.ts.map +1 -1
  107. package/dist/lib/review-orchestrator.js +20 -13
  108. package/dist/lib/review-orchestrator.js.map +1 -1
  109. package/dist/lib/review-usage.d.ts +4 -0
  110. package/dist/lib/review-usage.d.ts.map +1 -1
  111. package/dist/lib/review-usage.js +25 -0
  112. package/dist/lib/review-usage.js.map +1 -1
  113. package/dist/lib/trace-collector.d.ts +105 -0
  114. package/dist/lib/trace-collector.d.ts.map +1 -0
  115. package/dist/lib/trace-collector.js +255 -0
  116. package/dist/lib/trace-collector.js.map +1 -0
  117. package/dist/lib/trace-html.d.ts +3 -0
  118. package/dist/lib/trace-html.d.ts.map +1 -0
  119. package/dist/lib/trace-html.js +349 -0
  120. package/dist/lib/trace-html.js.map +1 -0
  121. package/dist/lib/workflow-artifacts.d.ts +54 -0
  122. package/dist/lib/workflow-artifacts.d.ts.map +1 -0
  123. package/dist/lib/workflow-artifacts.js +150 -0
  124. package/dist/lib/workflow-artifacts.js.map +1 -0
  125. package/dist/pi/sdk.d.ts.map +1 -1
  126. package/dist/pi/sdk.js +570 -6
  127. package/dist/pi/sdk.js.map +1 -1
  128. package/dist/runtime/agent-loader.js +2 -2
  129. package/dist/runtime/client.d.ts +2 -0
  130. package/dist/runtime/client.d.ts.map +1 -1
  131. package/dist/runtime/client.js +11 -5
  132. package/dist/runtime/client.js.map +1 -1
  133. package/package.json +21 -15
  134. package/.pi/workflows/github-pr-describe-post.yaml +0 -24
  135. package/.pi/workflows/gitlab-mr-describe-post.yaml +0 -22
  136. package/.pi/workflows/gitlab-mr-review-code-quality.yaml +0 -31
  137. package/.pi/workflows/gitlab-mr-review-post-code-quality.yaml +0 -40
  138. package/.pi/workflows/gitlab-mr-review-post.yaml +0 -30
  139. package/.pi/workflows/local-staged-review.yaml +0 -17
  140. package/dist/cli/run-agent.test.d.ts +0 -2
  141. package/dist/cli/run-agent.test.d.ts.map +0 -1
  142. package/dist/cli/run-agent.test.js +0 -204
  143. package/dist/cli/run-agent.test.js.map +0 -1
  144. package/dist/cli/workflow.test.d.ts +0 -2
  145. package/dist/cli/workflow.test.d.ts.map +0 -1
  146. package/dist/cli/workflow.test.js +0 -1410
  147. package/dist/cli/workflow.test.js.map +0 -1
  148. package/dist/github/client.test.d.ts +0 -2
  149. package/dist/github/client.test.d.ts.map +0 -1
  150. package/dist/github/client.test.js +0 -206
  151. package/dist/github/client.test.js.map +0 -1
  152. package/dist/github/platform-adapter.test.d.ts +0 -2
  153. package/dist/github/platform-adapter.test.d.ts.map +0 -1
  154. package/dist/github/platform-adapter.test.js +0 -40
  155. package/dist/github/platform-adapter.test.js.map +0 -1
  156. package/dist/gitlab/diff-parser.test.d.ts +0 -2
  157. package/dist/gitlab/diff-parser.test.d.ts.map +0 -1
  158. package/dist/gitlab/diff-parser.test.js +0 -315
  159. package/dist/gitlab/diff-parser.test.js.map +0 -1
  160. package/dist/gitlab/platform-adapter.test.d.ts +0 -2
  161. package/dist/gitlab/platform-adapter.test.d.ts.map +0 -1
  162. package/dist/gitlab/platform-adapter.test.js +0 -21
  163. package/dist/gitlab/platform-adapter.test.js.map +0 -1
  164. package/dist/index.test.d.ts +0 -2
  165. package/dist/index.test.d.ts.map +0 -1
  166. package/dist/index.test.js +0 -7
  167. package/dist/index.test.js.map +0 -1
  168. package/dist/lib/code-quality-report.test.d.ts +0 -2
  169. package/dist/lib/code-quality-report.test.d.ts.map +0 -1
  170. package/dist/lib/code-quality-report.test.js +0 -327
  171. package/dist/lib/code-quality-report.test.js.map +0 -1
  172. package/dist/lib/comment-formatter.test.d.ts +0 -2
  173. package/dist/lib/comment-formatter.test.d.ts.map +0 -1
  174. package/dist/lib/comment-formatter.test.js +0 -727
  175. package/dist/lib/comment-formatter.test.js.map +0 -1
  176. package/dist/lib/comment-manager.test.d.ts +0 -2
  177. package/dist/lib/comment-manager.test.d.ts.map +0 -1
  178. package/dist/lib/comment-manager.test.js +0 -680
  179. package/dist/lib/comment-manager.test.js.map +0 -1
  180. package/dist/lib/comment-poster.test.d.ts +0 -5
  181. package/dist/lib/comment-poster.test.d.ts.map +0 -1
  182. package/dist/lib/comment-poster.test.js +0 -255
  183. package/dist/lib/comment-poster.test.js.map +0 -1
  184. package/dist/lib/config-model-overrides.test.d.ts +0 -2
  185. package/dist/lib/config-model-overrides.test.d.ts.map +0 -1
  186. package/dist/lib/config-model-overrides.test.js +0 -218
  187. package/dist/lib/config-model-overrides.test.js.map +0 -1
  188. package/dist/lib/config.test.d.ts +0 -2
  189. package/dist/lib/config.test.d.ts.map +0 -1
  190. package/dist/lib/config.test.js +0 -353
  191. package/dist/lib/config.test.js.map +0 -1
  192. package/dist/lib/context-compression.test.d.ts +0 -2
  193. package/dist/lib/context-compression.test.d.ts.map +0 -1
  194. package/dist/lib/context-compression.test.js +0 -337
  195. package/dist/lib/context-compression.test.js.map +0 -1
  196. package/dist/lib/context-loader.test.d.ts +0 -2
  197. package/dist/lib/context-loader.test.d.ts.map +0 -1
  198. package/dist/lib/context-loader.test.js +0 -212
  199. package/dist/lib/context-loader.test.js.map +0 -1
  200. package/dist/lib/cursor-fix-link.test.d.ts +0 -2
  201. package/dist/lib/cursor-fix-link.test.d.ts.map +0 -1
  202. package/dist/lib/cursor-fix-link.test.js +0 -70
  203. package/dist/lib/cursor-fix-link.test.js.map +0 -1
  204. package/dist/lib/describe-core.test.d.ts +0 -2
  205. package/dist/lib/describe-core.test.d.ts.map +0 -1
  206. package/dist/lib/describe-core.test.js +0 -208
  207. package/dist/lib/describe-core.test.js.map +0 -1
  208. package/dist/lib/describe-output-path.test.d.ts +0 -2
  209. package/dist/lib/describe-output-path.test.d.ts.map +0 -1
  210. package/dist/lib/describe-output-path.test.js +0 -51
  211. package/dist/lib/describe-output-path.test.js.map +0 -1
  212. package/dist/lib/describe-parser.test.d.ts +0 -2
  213. package/dist/lib/describe-parser.test.d.ts.map +0 -1
  214. package/dist/lib/describe-parser.test.js +0 -282
  215. package/dist/lib/describe-parser.test.js.map +0 -1
  216. package/dist/lib/description-executor.test.d.ts +0 -2
  217. package/dist/lib/description-executor.test.d.ts.map +0 -1
  218. package/dist/lib/description-executor.test.js +0 -135
  219. package/dist/lib/description-executor.test.js.map +0 -1
  220. package/dist/lib/description-formatter.test.d.ts +0 -2
  221. package/dist/lib/description-formatter.test.d.ts.map +0 -1
  222. package/dist/lib/description-formatter.test.js +0 -57
  223. package/dist/lib/description-formatter.test.js.map +0 -1
  224. package/dist/lib/diff-lines.test.d.ts +0 -2
  225. package/dist/lib/diff-lines.test.d.ts.map +0 -1
  226. package/dist/lib/diff-lines.test.js +0 -13
  227. package/dist/lib/diff-lines.test.js.map +0 -1
  228. package/dist/lib/diff-parser.test.d.ts +0 -2
  229. package/dist/lib/diff-parser.test.d.ts.map +0 -1
  230. package/dist/lib/diff-parser.test.js +0 -335
  231. package/dist/lib/diff-parser.test.js.map +0 -1
  232. package/dist/lib/error-comment-poster.test.d.ts +0 -2
  233. package/dist/lib/error-comment-poster.test.d.ts.map +0 -1
  234. package/dist/lib/error-comment-poster.test.js +0 -128
  235. package/dist/lib/error-comment-poster.test.js.map +0 -1
  236. package/dist/lib/exit.test.d.ts +0 -2
  237. package/dist/lib/exit.test.d.ts.map +0 -1
  238. package/dist/lib/exit.test.js +0 -120
  239. package/dist/lib/exit.test.js.map +0 -1
  240. package/dist/lib/issue-parser.test.d.ts +0 -2
  241. package/dist/lib/issue-parser.test.d.ts.map +0 -1
  242. package/dist/lib/issue-parser.test.js +0 -281
  243. package/dist/lib/issue-parser.test.js.map +0 -1
  244. package/dist/lib/json-output-schema.test.d.ts +0 -2
  245. package/dist/lib/json-output-schema.test.d.ts.map +0 -1
  246. package/dist/lib/json-output-schema.test.js +0 -92
  247. package/dist/lib/json-output-schema.test.js.map +0 -1
  248. package/dist/lib/json-output.test.d.ts +0 -2
  249. package/dist/lib/json-output.test.d.ts.map +0 -1
  250. package/dist/lib/json-output.test.js +0 -141
  251. package/dist/lib/json-output.test.js.map +0 -1
  252. package/dist/lib/logger.test.d.ts +0 -2
  253. package/dist/lib/logger.test.d.ts.map +0 -1
  254. package/dist/lib/logger.test.js +0 -324
  255. package/dist/lib/logger.test.js.map +0 -1
  256. package/dist/lib/position-validator.test.d.ts +0 -2
  257. package/dist/lib/position-validator.test.d.ts.map +0 -1
  258. package/dist/lib/position-validator.test.js +0 -128
  259. package/dist/lib/position-validator.test.js.map +0 -1
  260. package/dist/lib/prompt-budget.test.d.ts +0 -2
  261. package/dist/lib/prompt-budget.test.d.ts.map +0 -1
  262. package/dist/lib/prompt-budget.test.js +0 -55
  263. package/dist/lib/prompt-budget.test.js.map +0 -1
  264. package/dist/lib/repository-validator.test.d.ts +0 -5
  265. package/dist/lib/repository-validator.test.d.ts.map +0 -1
  266. package/dist/lib/repository-validator.test.js +0 -341
  267. package/dist/lib/repository-validator.test.js.map +0 -1
  268. package/dist/lib/review-core.test.d.ts +0 -2
  269. package/dist/lib/review-core.test.d.ts.map +0 -1
  270. package/dist/lib/review-core.test.js +0 -614
  271. package/dist/lib/review-core.test.js.map +0 -1
  272. package/dist/lib/review-orchestrator.test.d.ts +0 -2
  273. package/dist/lib/review-orchestrator.test.d.ts.map +0 -1
  274. package/dist/lib/review-orchestrator.test.js +0 -552
  275. package/dist/lib/review-orchestrator.test.js.map +0 -1
  276. package/dist/lib/review-output-path.test.d.ts +0 -2
  277. package/dist/lib/review-output-path.test.d.ts.map +0 -1
  278. package/dist/lib/review-output-path.test.js +0 -83
  279. package/dist/lib/review-output-path.test.js.map +0 -1
  280. package/dist/lib/review-parser.test.d.ts +0 -2
  281. package/dist/lib/review-parser.test.d.ts.map +0 -1
  282. package/dist/lib/review-parser.test.js +0 -130
  283. package/dist/lib/review-parser.test.js.map +0 -1
  284. package/dist/lib/review-usage.test.d.ts +0 -2
  285. package/dist/lib/review-usage.test.d.ts.map +0 -1
  286. package/dist/lib/review-usage.test.js +0 -83
  287. package/dist/lib/review-usage.test.js.map +0 -1
  288. package/dist/lib/unified-review-executor.d.ts +0 -58
  289. package/dist/lib/unified-review-executor.d.ts.map +0 -1
  290. package/dist/lib/unified-review-executor.js +0 -201
  291. package/dist/lib/unified-review-executor.js.map +0 -1
  292. package/dist/lib/unified-review-executor.test.d.ts +0 -5
  293. package/dist/lib/unified-review-executor.test.d.ts.map +0 -1
  294. package/dist/lib/unified-review-executor.test.js +0 -472
  295. package/dist/lib/unified-review-executor.test.js.map +0 -1
  296. package/dist/lib/write-json-output.test.d.ts +0 -2
  297. package/dist/lib/write-json-output.test.d.ts.map +0 -1
  298. package/dist/lib/write-json-output.test.js +0 -259
  299. package/dist/lib/write-json-output.test.js.map +0 -1
  300. package/dist/pi/sdk.test.d.ts +0 -2
  301. package/dist/pi/sdk.test.d.ts.map +0 -1
  302. package/dist/pi/sdk.test.js +0 -488
  303. package/dist/pi/sdk.test.js.map +0 -1
  304. package/dist/runtime/agent-loader.test.d.ts +0 -2
  305. package/dist/runtime/agent-loader.test.d.ts.map +0 -1
  306. package/dist/runtime/agent-loader.test.js +0 -277
  307. package/dist/runtime/agent-loader.test.js.map +0 -1
  308. package/dist/runtime/client.test.d.ts +0 -2
  309. package/dist/runtime/client.test.d.ts.map +0 -1
  310. package/dist/runtime/client.test.js +0 -772
  311. package/dist/runtime/client.test.js.map +0 -1
  312. package/dist/runtime/path-config.test.d.ts +0 -2
  313. package/dist/runtime/path-config.test.d.ts.map +0 -1
  314. package/dist/runtime/path-config.test.js +0 -112
  315. package/dist/runtime/path-config.test.js.map +0 -1
@@ -1,772 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { RuntimeClient, createRuntimeClient, createRuntimeClientInstance } from './client.js';
3
- function createRuntime(sessionOverrides = {}) {
4
- return {
5
- server: {
6
- url: 'pi://in-process',
7
- close: vi.fn(),
8
- },
9
- client: {
10
- session: {
11
- create: vi.fn(async () => ({ data: { id: 'session-123' } })),
12
- prompt: vi.fn(async () => { }),
13
- messages: vi.fn(async () => ({ data: [] })),
14
- delete: vi.fn(async () => { }),
15
- ...sessionOverrides,
16
- },
17
- },
18
- };
19
- }
20
- const mocks = vi.hoisted(() => ({
21
- createPiInProcessServer: vi.fn(),
22
- loadAgents: vi.fn(() => []),
23
- }));
24
- vi.mock('../pi/sdk.js', () => ({
25
- createPiInProcessServer: mocks.createPiInProcessServer,
26
- }));
27
- vi.mock('./agent-loader.js', () => ({
28
- loadAgents: mocks.loadAgents,
29
- }));
30
- describe('RuntimeClient', () => {
31
- beforeEach(() => {
32
- vi.clearAllMocks();
33
- vi.spyOn(console, 'log').mockImplementation(() => { });
34
- vi.spyOn(console, 'warn').mockImplementation(() => { });
35
- mocks.createPiInProcessServer.mockResolvedValue(createRuntime());
36
- mocks.loadAgents.mockReturnValue([]);
37
- });
38
- describe('constructor', () => {
39
- it('creates an instance with minimal config', () => {
40
- const client = new RuntimeClient({
41
- directory: '/test/dir',
42
- });
43
- expect(client).toBeInstanceOf(RuntimeClient);
44
- });
45
- it('supports optional model overrides and provider config', () => {
46
- const client = new RuntimeClient({
47
- modelOverrides: {
48
- 'review/security': 'anthropic/claude-opus-4-5-20251101',
49
- },
50
- provider: {
51
- custom: {
52
- baseUrl: 'https://api.custom.example/v1',
53
- apiKey: 'CUSTOM_API_KEY',
54
- api: 'openai-completions',
55
- models: [
56
- {
57
- id: 'model',
58
- name: 'model',
59
- },
60
- ],
61
- },
62
- },
63
- });
64
- expect(client).toBeInstanceOf(RuntimeClient);
65
- });
66
- });
67
- describe('initialize', () => {
68
- it('wires Pi runtime agent prompts and model overrides', async () => {
69
- mocks.loadAgents.mockReturnValue([
70
- {
71
- id: 'review/security',
72
- namespace: 'review',
73
- name: 'review/security',
74
- path: '/tmp/security.md',
75
- description: 'Security specialist',
76
- prompt: 'Security prompt',
77
- tools: { Read: true },
78
- },
79
- {
80
- id: 'review/quality',
81
- namespace: 'review',
82
- name: 'review/quality',
83
- path: '/tmp/quality.md',
84
- description: 'Quality specialist',
85
- prompt: 'Quality prompt',
86
- },
87
- ]);
88
- const client = await createRuntimeClientInstance({
89
- directory: process.cwd(),
90
- config: {
91
- review: {
92
- agents: ['review/security', 'review/quality'],
93
- },
94
- agents: {
95
- default: {
96
- skills: [],
97
- },
98
- },
99
- },
100
- modelOverrides: {
101
- 'review/security': 'anthropic/claude-security',
102
- },
103
- });
104
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
105
- config: expect.objectContaining({
106
- agent: expect.objectContaining({
107
- 'review/security': expect.objectContaining({
108
- prompt: 'Security prompt',
109
- model: 'anthropic/claude-security',
110
- tools: { Read: true },
111
- }),
112
- 'review/quality': expect.objectContaining({
113
- prompt: 'Quality prompt',
114
- }),
115
- }),
116
- }),
117
- }));
118
- await client.shutdown();
119
- });
120
- it('passes skill search paths and agent skill configuration to Pi runtime', async () => {
121
- const projectRoot = process.cwd();
122
- const config = {
123
- agents: {
124
- default: {
125
- skills: ['baseline-review'],
126
- },
127
- },
128
- review: {
129
- agents: [
130
- {
131
- name: 'review/security',
132
- skills: ['security-audit'],
133
- },
134
- ],
135
- },
136
- };
137
- mocks.loadAgents.mockReturnValueOnce([
138
- {
139
- id: 'review/security',
140
- namespace: 'review',
141
- name: 'security',
142
- path: '/tmp/security.md',
143
- description: 'Security agent',
144
- },
145
- ]);
146
- const client = await createRuntimeClientInstance({
147
- directory: projectRoot,
148
- config,
149
- });
150
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
151
- config: expect.objectContaining({
152
- skillSearchPaths: expect.any(Array),
153
- agentSkills: {
154
- 'review/security': ['baseline-review', 'security-audit'],
155
- },
156
- }),
157
- }));
158
- await client.shutdown();
159
- });
160
- it('includes unified reviewer in skill configuration when configured', async () => {
161
- const config = {
162
- agents: {
163
- default: {
164
- skills: ['cli-testing'],
165
- },
166
- },
167
- review: {
168
- agents: ['review/unified-reviewer'],
169
- },
170
- };
171
- mocks.loadAgents.mockReturnValueOnce([
172
- {
173
- id: 'review/unified-reviewer',
174
- namespace: 'review',
175
- name: 'unified-reviewer',
176
- path: '/tmp/unified-reviewer.md',
177
- description: 'Unified agent',
178
- },
179
- ]);
180
- const client = await createRuntimeClientInstance({
181
- directory: process.cwd(),
182
- config,
183
- });
184
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
185
- config: expect.objectContaining({
186
- agentSkills: expect.objectContaining({
187
- 'review/unified-reviewer': ['cli-testing'],
188
- }),
189
- }),
190
- }));
191
- await client.shutdown();
192
- });
193
- it('applies generic default skills to non-review agents', async () => {
194
- const config = {
195
- agents: {
196
- default: {
197
- skills: ['generic-skill'],
198
- },
199
- },
200
- review: {
201
- agents: ['review/security'],
202
- },
203
- };
204
- mocks.loadAgents.mockReturnValueOnce([
205
- {
206
- id: 'review/security',
207
- namespace: 'review',
208
- name: 'security',
209
- path: '/tmp/security.md',
210
- description: 'Security agent',
211
- },
212
- {
213
- id: 'describe/pr-describer',
214
- namespace: 'describe',
215
- name: 'pr-describer',
216
- path: '/tmp/pr-describer.md',
217
- description: 'Description agent',
218
- },
219
- ]);
220
- const client = await createRuntimeClientInstance({
221
- directory: process.cwd(),
222
- config,
223
- });
224
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
225
- config: expect.objectContaining({
226
- agentSkills: expect.objectContaining({
227
- 'describe/pr-describer': ['generic-skill'],
228
- }),
229
- }),
230
- }));
231
- await client.shutdown();
232
- });
233
- it('applies generic model defaults, namespaces, and overrides to all loaded agents', async () => {
234
- const config = {
235
- agents: {
236
- default: {
237
- model: 'provider/default-model',
238
- skills: [],
239
- },
240
- namespaces: {
241
- task: {
242
- model: 'provider/task-model',
243
- },
244
- },
245
- overrides: {
246
- 'task/specialist': {
247
- model: 'provider/specialist-override',
248
- },
249
- },
250
- },
251
- review: {
252
- agents: [],
253
- },
254
- };
255
- mocks.loadAgents.mockReturnValueOnce([
256
- {
257
- id: 'task/docs-updater',
258
- namespace: 'task',
259
- name: 'docs-updater',
260
- path: '/tmp/docs-updater.md',
261
- description: 'Docs updater',
262
- },
263
- {
264
- id: 'task/specialist',
265
- namespace: 'task',
266
- name: 'specialist',
267
- path: '/tmp/specialist.md',
268
- description: 'Specialist',
269
- model: 'provider/frontmatter-model',
270
- },
271
- {
272
- id: 'ops/deploy',
273
- namespace: 'ops',
274
- name: 'deploy',
275
- path: '/tmp/deploy.md',
276
- description: 'Deploy helper',
277
- },
278
- ]);
279
- const client = await createRuntimeClientInstance({
280
- directory: process.cwd(),
281
- config,
282
- });
283
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
284
- config: expect.objectContaining({
285
- agent: expect.objectContaining({
286
- 'task/docs-updater': expect.objectContaining({
287
- model: 'provider/task-model',
288
- }),
289
- 'task/specialist': expect.objectContaining({
290
- model: 'provider/specialist-override',
291
- }),
292
- 'ops/deploy': expect.objectContaining({
293
- model: 'provider/default-model',
294
- }),
295
- }),
296
- }),
297
- }));
298
- await client.shutdown();
299
- });
300
- it('passes per-agent tool overrides to Pi runtime config', async () => {
301
- mocks.loadAgents.mockReturnValue([
302
- {
303
- id: 'review/security',
304
- namespace: 'review',
305
- name: 'review/security',
306
- path: '/tmp/security.md',
307
- description: 'Security agent',
308
- prompt: 'Security prompt',
309
- tools: { Read: true, Bash: false, Edit: true },
310
- },
311
- {
312
- id: 'review/quality',
313
- namespace: 'review',
314
- name: 'review/quality',
315
- path: '/tmp/quality.md',
316
- description: 'Quality agent',
317
- prompt: 'Quality prompt',
318
- // No tools override — uses global defaults
319
- },
320
- ]);
321
- const client = await createRuntimeClientInstance({
322
- directory: process.cwd(),
323
- config: {
324
- review: {
325
- agents: ['review/security', 'review/quality'],
326
- },
327
- agents: {
328
- default: { skills: [] },
329
- },
330
- },
331
- });
332
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
333
- config: expect.objectContaining({
334
- agent: expect.objectContaining({
335
- 'review/security': expect.objectContaining({
336
- tools: { Read: true, Bash: false, Edit: true },
337
- }),
338
- 'review/quality': expect.not.objectContaining({
339
- tools: expect.anything(),
340
- }),
341
- }),
342
- }),
343
- }));
344
- await client.shutdown();
345
- });
346
- it('passes provider and model headers through to Pi runtime config', async () => {
347
- const client = await createRuntimeClientInstance({
348
- directory: process.cwd(),
349
- provider: {
350
- custom: {
351
- baseUrl: 'https://api.custom.example/v1',
352
- apiKey: 'CUSTOM_API_KEY',
353
- api: 'openai-completions',
354
- headers: {
355
- 'X-Provider-Header': 'provider-value',
356
- },
357
- models: [
358
- {
359
- id: 'custom-model',
360
- headers: {
361
- 'X-Model-Header': 'model-value',
362
- },
363
- },
364
- ],
365
- },
366
- },
367
- });
368
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
369
- config: expect.objectContaining({
370
- provider: expect.objectContaining({
371
- custom: expect.objectContaining({
372
- headers: {
373
- 'X-Provider-Header': 'provider-value',
374
- },
375
- models: [
376
- expect.objectContaining({
377
- id: 'custom-model',
378
- headers: {
379
- 'X-Model-Header': 'model-value',
380
- },
381
- }),
382
- ],
383
- }),
384
- }),
385
- }),
386
- }));
387
- await client.shutdown();
388
- });
389
- it('passes configured provider retry settings to Pi runtime config', async () => {
390
- const client = await createRuntimeClientInstance({
391
- directory: process.cwd(),
392
- providerRetry: {
393
- timeoutMs: 45000,
394
- maxRetries: 2,
395
- maxRetryDelayMs: 15000,
396
- },
397
- });
398
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
399
- config: expect.objectContaining({
400
- retry: {
401
- provider: {
402
- timeoutMs: 45000,
403
- maxRetries: 2,
404
- maxRetryDelayMs: 15000,
405
- },
406
- },
407
- }),
408
- }));
409
- await client.shutdown();
410
- });
411
- });
412
- describe('createSession', () => {
413
- it('throws error if not initialized', async () => {
414
- const client = new RuntimeClient({});
415
- await expect(client.createSession({
416
- agent: 'review/security',
417
- message: 'Review this code',
418
- })).rejects.toThrow('Runtime client not initialized');
419
- });
420
- it('maps authentication errors to actionable messages', async () => {
421
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
422
- create: vi.fn(async () => {
423
- throw new Error('401 Unauthorized');
424
- }),
425
- }));
426
- const client = await createRuntimeClientInstance({
427
- directory: process.cwd(),
428
- });
429
- await expect(client.createSession({
430
- agent: 'review/security',
431
- message: 'Review this code',
432
- })).rejects.toThrow('Authentication failed with the configured model provider');
433
- });
434
- it('maps model resolution errors to actionable messages', async () => {
435
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
436
- create: vi.fn(async () => {
437
- throw new Error('Failed to resolve model "anthropic/does-not-exist"');
438
- }),
439
- }));
440
- const client = await createRuntimeClientInstance({
441
- directory: process.cwd(),
442
- });
443
- await expect(client.createSession({
444
- agent: 'review/security',
445
- message: 'Review this code',
446
- })).rejects.toThrow('Model configuration is invalid or unavailable');
447
- });
448
- it('includes local-runtime hint when connectivity errors occur', async () => {
449
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
450
- create: vi.fn(async () => {
451
- throw new Error('fetch failed');
452
- }),
453
- }));
454
- const client = await createRuntimeClientInstance({
455
- directory: process.cwd(),
456
- });
457
- await expect(client.createSession({
458
- agent: 'review/security',
459
- message: 'Review this code',
460
- })).rejects.toThrow('Verify local Pi runtime setup and model provider connectivity');
461
- });
462
- it('fails fast when session creation hangs', async () => {
463
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
464
- create: vi.fn(() => new Promise(() => { })),
465
- }));
466
- const client = await createRuntimeClientInstance({
467
- directory: process.cwd(),
468
- operationTimeoutMs: 25,
469
- });
470
- await expect(client.createSession({
471
- agent: 'review/security',
472
- message: 'Review this code',
473
- })).rejects.toThrow('Create session timed out');
474
- });
475
- });
476
- describe('lifecycle and helper methods', () => {
477
- it('streamMessages throws if not initialized', async () => {
478
- const client = new RuntimeClient({});
479
- const generator = client.streamMessages('session-123');
480
- await expect(generator.next()).rejects.toThrow('Runtime client not initialized');
481
- });
482
- it('applies configured model pricing when runtime cost is missing or zero', async () => {
483
- const runtimeMessages = [
484
- {
485
- info: {
486
- id: 'msg-1',
487
- role: 'assistant',
488
- time: { completed: Date.now() },
489
- provider: 'opencode',
490
- model: 'glm-5-free',
491
- usage: {
492
- input: 1000,
493
- output: 100,
494
- cacheRead: 0,
495
- cacheWrite: 0,
496
- totalTokens: 1100,
497
- },
498
- },
499
- parts: [{ text: 'done' }],
500
- },
501
- ];
502
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
503
- messages: vi.fn(async () => ({ data: runtimeMessages })),
504
- }));
505
- const client = await createRuntimeClientInstance({
506
- directory: process.cwd(),
507
- config: {
508
- review: {
509
- agents: [],
510
- },
511
- agents: {
512
- default: {
513
- skills: [],
514
- },
515
- },
516
- pricing: {
517
- models: {
518
- 'opencode/glm-5-free': {
519
- input: 2,
520
- output: 8,
521
- },
522
- },
523
- },
524
- },
525
- });
526
- const collected = [];
527
- for await (const message of client.streamMessages('session-123')) {
528
- collected.push(message);
529
- }
530
- expect(collected).toHaveLength(1);
531
- expect(collected[0].usage?.cost).toBeCloseTo(0.0028, 10);
532
- await client.shutdown();
533
- });
534
- it('fails fast when message polling exceeds stream timeout', async () => {
535
- const runtimeMessages = [
536
- {
537
- info: {
538
- id: 'msg-1',
539
- role: 'assistant',
540
- },
541
- parts: [{ text: 'still running' }],
542
- },
543
- ];
544
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
545
- messages: vi.fn(async () => ({ data: runtimeMessages })),
546
- }));
547
- const client = await createRuntimeClientInstance({
548
- directory: process.cwd(),
549
- streamTimeoutMs: 30,
550
- streamPollIntervalMs: 10,
551
- });
552
- const collect = async () => {
553
- const collected = [];
554
- for await (const message of client.streamMessages('session-123')) {
555
- collected.push(message);
556
- }
557
- return collected;
558
- };
559
- await expect(collect()).rejects.toThrow('Session session-123 timed out');
560
- await client.shutdown();
561
- });
562
- it('prefers env timeout overrides over constructor values', async () => {
563
- const previousOpTimeout = process.env.DRS_RUNTIME_OPERATION_TIMEOUT_MS;
564
- const previousStreamTimeout = process.env.DRS_RUNTIME_STREAM_TIMEOUT_MS;
565
- const previousPollInterval = process.env.DRS_RUNTIME_STREAM_POLL_INTERVAL_MS;
566
- process.env.DRS_RUNTIME_OPERATION_TIMEOUT_MS = '10';
567
- process.env.DRS_RUNTIME_STREAM_TIMEOUT_MS = '30';
568
- process.env.DRS_RUNTIME_STREAM_POLL_INTERVAL_MS = '10';
569
- try {
570
- mocks.createPiInProcessServer.mockResolvedValueOnce(createRuntime({
571
- create: vi.fn(() => new Promise(() => { })),
572
- }));
573
- const client = await createRuntimeClientInstance({
574
- directory: process.cwd(),
575
- operationTimeoutMs: 1000,
576
- streamTimeoutMs: 1000,
577
- streamPollIntervalMs: 1000,
578
- });
579
- await expect(client.createSession({
580
- agent: 'review/security',
581
- message: 'Review this code',
582
- })).rejects.toThrow('Create session timed out');
583
- await client.shutdown();
584
- }
585
- finally {
586
- if (previousOpTimeout === undefined) {
587
- delete process.env.DRS_RUNTIME_OPERATION_TIMEOUT_MS;
588
- }
589
- else {
590
- process.env.DRS_RUNTIME_OPERATION_TIMEOUT_MS = previousOpTimeout;
591
- }
592
- if (previousStreamTimeout === undefined) {
593
- delete process.env.DRS_RUNTIME_STREAM_TIMEOUT_MS;
594
- }
595
- else {
596
- process.env.DRS_RUNTIME_STREAM_TIMEOUT_MS = previousStreamTimeout;
597
- }
598
- if (previousPollInterval === undefined) {
599
- delete process.env.DRS_RUNTIME_STREAM_POLL_INTERVAL_MS;
600
- }
601
- else {
602
- process.env.DRS_RUNTIME_STREAM_POLL_INTERVAL_MS = previousPollInterval;
603
- }
604
- }
605
- });
606
- it('closeSession throws if not initialized', async () => {
607
- const client = new RuntimeClient({});
608
- await expect(client.closeSession('session-123')).rejects.toThrow('Runtime client not initialized');
609
- });
610
- it('getServerUrl throws when server is not initialized', () => {
611
- const client = new RuntimeClient({});
612
- expect(() => client.getServerUrl()).toThrow('Server not initialized');
613
- });
614
- it('shutdown does not throw when no runtime is active', async () => {
615
- const client = new RuntimeClient({});
616
- await expect(client.shutdown()).resolves.toBeUndefined();
617
- });
618
- });
619
- describe('environment variable resolution', () => {
620
- it('resolves provider apiKey env references before runtime init', async () => {
621
- const originalEnv = process.env.TEST_API_KEY;
622
- process.env.TEST_API_KEY = 'test-key-123';
623
- try {
624
- const client = await createRuntimeClientInstance({
625
- directory: process.cwd(),
626
- provider: {
627
- 'test-provider': {
628
- baseUrl: 'https://api.test.com/v1',
629
- apiKey: '{env:TEST_API_KEY}',
630
- api: 'openai-completions',
631
- models: [
632
- {
633
- id: 'test-model',
634
- },
635
- ],
636
- },
637
- },
638
- });
639
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
640
- config: expect.objectContaining({
641
- provider: expect.objectContaining({
642
- 'test-provider': expect.objectContaining({
643
- apiKey: 'test-key-123',
644
- }),
645
- }),
646
- }),
647
- }));
648
- await client.shutdown();
649
- }
650
- finally {
651
- if (originalEnv === undefined) {
652
- delete process.env.TEST_API_KEY;
653
- }
654
- else {
655
- process.env.TEST_API_KEY = originalEnv;
656
- }
657
- }
658
- });
659
- it('resolves env references inside provider and model headers', async () => {
660
- const originalProviderHeaderEnv = process.env.TEST_PROVIDER_HEADER;
661
- const originalModelHeaderEnv = process.env.TEST_MODEL_HEADER;
662
- process.env.TEST_PROVIDER_HEADER = 'provider-header-value';
663
- process.env.TEST_MODEL_HEADER = 'model-header-value';
664
- try {
665
- const client = await createRuntimeClientInstance({
666
- directory: process.cwd(),
667
- provider: {
668
- 'test-provider': {
669
- baseUrl: 'https://api.test.com/v1',
670
- apiKey: 'TEST_API_KEY',
671
- api: 'openai-completions',
672
- headers: {
673
- 'X-Provider-Header': '{env:TEST_PROVIDER_HEADER}',
674
- },
675
- models: [
676
- {
677
- id: 'test-model',
678
- headers: {
679
- 'X-Model-Header': '{env:TEST_MODEL_HEADER}',
680
- },
681
- },
682
- ],
683
- },
684
- },
685
- });
686
- expect(mocks.createPiInProcessServer).toHaveBeenCalledWith(expect.objectContaining({
687
- config: expect.objectContaining({
688
- provider: expect.objectContaining({
689
- 'test-provider': expect.objectContaining({
690
- headers: {
691
- 'X-Provider-Header': 'provider-header-value',
692
- },
693
- models: [
694
- expect.objectContaining({
695
- id: 'test-model',
696
- headers: {
697
- 'X-Model-Header': 'model-header-value',
698
- },
699
- }),
700
- ],
701
- }),
702
- }),
703
- }),
704
- }));
705
- await client.shutdown();
706
- }
707
- finally {
708
- if (originalProviderHeaderEnv === undefined) {
709
- delete process.env.TEST_PROVIDER_HEADER;
710
- }
711
- else {
712
- process.env.TEST_PROVIDER_HEADER = originalProviderHeaderEnv;
713
- }
714
- if (originalModelHeaderEnv === undefined) {
715
- delete process.env.TEST_MODEL_HEADER;
716
- }
717
- else {
718
- process.env.TEST_MODEL_HEADER = originalModelHeaderEnv;
719
- }
720
- }
721
- });
722
- it('warns when referenced environment variables are missing', async () => {
723
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
724
- const originalEnv = process.env.NONEXISTENT_VAR;
725
- delete process.env.NONEXISTENT_VAR;
726
- try {
727
- const client = await createRuntimeClientInstance({
728
- directory: process.cwd(),
729
- provider: {
730
- 'test-provider': {
731
- baseUrl: 'https://api.test.com/v1',
732
- apiKey: '{env:NONEXISTENT_VAR}',
733
- api: 'openai-completions',
734
- models: [
735
- {
736
- id: 'test-model',
737
- },
738
- ],
739
- },
740
- },
741
- });
742
- // Logger outputs warning via console.log (human format)
743
- const allOutput = logSpy.mock.calls.map((c) => String(c[0])).join('\n');
744
- expect(allOutput).toContain('NONEXISTENT_VAR is not set');
745
- await client.shutdown();
746
- }
747
- finally {
748
- logSpy.mockRestore();
749
- if (originalEnv !== undefined) {
750
- process.env.NONEXISTENT_VAR = originalEnv;
751
- }
752
- }
753
- });
754
- });
755
- describe('factory functions', () => {
756
- it('createRuntimeClient returns an uninitialized client instance', () => {
757
- const client = createRuntimeClient({
758
- directory: process.cwd(),
759
- });
760
- expect(client).toBeInstanceOf(RuntimeClient);
761
- });
762
- it('createRuntimeClientInstance initializes in-process runtime', async () => {
763
- const client = await createRuntimeClientInstance({
764
- directory: process.cwd(),
765
- });
766
- expect(client).toBeInstanceOf(RuntimeClient);
767
- expect(mocks.createPiInProcessServer).toHaveBeenCalled();
768
- await client.shutdown();
769
- });
770
- });
771
- });
772
- //# sourceMappingURL=client.test.js.map