@kb-labs/shared 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/.vscode/settings.json +23 -0
  21. package/CHANGELOG.md +33 -0
  22. package/CONTRIBUTING.md +117 -0
  23. package/LICENSE +21 -0
  24. package/README.md +306 -0
  25. package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
  26. package/docs/DOCUMENTATION.md +70 -0
  27. package/docs/adr/0000-template.md +52 -0
  28. package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
  29. package/docs/adr/0002-plugins-and-extensibility.md +44 -0
  30. package/docs/adr/0003-package-and-module-boundaries.md +35 -0
  31. package/docs/adr/0004-versioning-and-release-policy.md +36 -0
  32. package/docs/adr/0005-reactive-loader-pattern.md +179 -0
  33. package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
  34. package/eslint.config.js +27 -0
  35. package/kb-labs.config.json +5 -0
  36. package/package.json +88 -0
  37. package/package.json.bin +25 -0
  38. package/package.json.lib +30 -0
  39. package/packages/shared-cli-ui/CHANGELOG.md +20 -0
  40. package/packages/shared-cli-ui/README.md +342 -0
  41. package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
  42. package/packages/shared-cli-ui/eslint.config.js +27 -0
  43. package/packages/shared-cli-ui/package.json +72 -0
  44. package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
  45. package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
  46. package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
  47. package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
  48. package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
  49. package/packages/shared-cli-ui/src/colors.ts +142 -0
  50. package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
  51. package/packages/shared-cli-ui/src/command-output.ts +153 -0
  52. package/packages/shared-cli-ui/src/command-result.ts +267 -0
  53. package/packages/shared-cli-ui/src/command-runner.ts +310 -0
  54. package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
  55. package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
  56. package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
  57. package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
  58. package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
  59. package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
  60. package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
  61. package/packages/shared-cli-ui/src/debug/index.ts +56 -0
  62. package/packages/shared-cli-ui/src/debug/types.ts +57 -0
  63. package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
  64. package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
  65. package/packages/shared-cli-ui/src/format.ts +412 -0
  66. package/packages/shared-cli-ui/src/index.ts +34 -0
  67. package/packages/shared-cli-ui/src/loader.ts +196 -0
  68. package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
  69. package/packages/shared-cli-ui/src/modern-format.ts +271 -0
  70. package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
  71. package/packages/shared-cli-ui/src/table.ts +134 -0
  72. package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
  73. package/packages/shared-cli-ui/src/utils/context.ts +12 -0
  74. package/packages/shared-cli-ui/src/utils/env.ts +164 -0
  75. package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
  76. package/packages/shared-cli-ui/src/utils/path.ts +8 -0
  77. package/packages/shared-cli-ui/tsconfig.build.json +15 -0
  78. package/packages/shared-cli-ui/tsconfig.json +9 -0
  79. package/packages/shared-cli-ui/tsup.config.ts +11 -0
  80. package/packages/shared-cli-ui/vitest.config.ts +15 -0
  81. package/packages/shared-command-kit/CHANGELOG.md +20 -0
  82. package/packages/shared-command-kit/LICENSE +22 -0
  83. package/packages/shared-command-kit/README.md +1030 -0
  84. package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
  85. package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
  86. package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
  87. package/packages/shared-command-kit/eslint.config.js +27 -0
  88. package/packages/shared-command-kit/eslint.config.ts +14 -0
  89. package/packages/shared-command-kit/package.json +76 -0
  90. package/packages/shared-command-kit/prettierrc.json +5 -0
  91. package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
  92. package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
  93. package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
  94. package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
  95. package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
  96. package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
  97. package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
  98. package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
  99. package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
  100. package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
  101. package/packages/shared-command-kit/src/analytics/index.ts +6 -0
  102. package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
  103. package/packages/shared-command-kit/src/define-action.ts +100 -0
  104. package/packages/shared-command-kit/src/define-command.ts +113 -0
  105. package/packages/shared-command-kit/src/define-route.ts +113 -0
  106. package/packages/shared-command-kit/src/define-system-command.ts +362 -0
  107. package/packages/shared-command-kit/src/define-webhook.ts +115 -0
  108. package/packages/shared-command-kit/src/define-websocket.ts +308 -0
  109. package/packages/shared-command-kit/src/errors/factory.ts +282 -0
  110. package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
  111. package/packages/shared-command-kit/src/errors/format.ts +92 -0
  112. package/packages/shared-command-kit/src/errors/index.ts +9 -0
  113. package/packages/shared-command-kit/src/errors/types.ts +32 -0
  114. package/packages/shared-command-kit/src/flags/define.ts +92 -0
  115. package/packages/shared-command-kit/src/flags/index.ts +9 -0
  116. package/packages/shared-command-kit/src/flags/types.ts +153 -0
  117. package/packages/shared-command-kit/src/flags/validate.ts +358 -0
  118. package/packages/shared-command-kit/src/helpers/context.ts +8 -0
  119. package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
  120. package/packages/shared-command-kit/src/helpers/index.ts +42 -0
  121. package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
  122. package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
  123. package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
  124. package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
  125. package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
  126. package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
  127. package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
  128. package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
  129. package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
  130. package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
  131. package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
  132. package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
  133. package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
  134. package/packages/shared-command-kit/src/index.ts +410 -0
  135. package/packages/shared-command-kit/src/jobs.ts +132 -0
  136. package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
  137. package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
  138. package/packages/shared-command-kit/src/manifest.ts +127 -0
  139. package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
  140. package/packages/shared-command-kit/src/rest/index.ts +11 -0
  141. package/packages/shared-command-kit/src/studio/index.ts +12 -0
  142. package/packages/shared-command-kit/src/validation/index.ts +6 -0
  143. package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
  144. package/packages/shared-command-kit/src/ws-types.ts +106 -0
  145. package/packages/shared-command-kit/tsconfig.build.json +15 -0
  146. package/packages/shared-command-kit/tsconfig.json +9 -0
  147. package/packages/shared-command-kit/tsup.config.ts +30 -0
  148. package/packages/shared-command-kit/vitest.config.ts +4 -0
  149. package/packages/shared-http/package.json +67 -0
  150. package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
  151. package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
  152. package/packages/shared-http/src/http-observability-collector.ts +363 -0
  153. package/packages/shared-http/src/index.ts +36 -0
  154. package/packages/shared-http/src/log-correlation.ts +89 -0
  155. package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
  156. package/packages/shared-http/src/register-openapi.ts +108 -0
  157. package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
  158. package/packages/shared-http/src/schemas.ts +29 -0
  159. package/packages/shared-http/src/service-observability.ts +63 -0
  160. package/packages/shared-http/tsconfig.build.json +15 -0
  161. package/packages/shared-http/tsconfig.json +9 -0
  162. package/packages/shared-http/tsup.config.ts +23 -0
  163. package/packages/shared-http/vitest.config.ts +13 -0
  164. package/packages/shared-perm-presets/CHANGELOG.md +20 -0
  165. package/packages/shared-perm-presets/README.md +78 -0
  166. package/packages/shared-perm-presets/eslint.config.js +27 -0
  167. package/packages/shared-perm-presets/package.json +45 -0
  168. package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
  169. package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
  170. package/packages/shared-perm-presets/src/combine.ts +278 -0
  171. package/packages/shared-perm-presets/src/index.ts +18 -0
  172. package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
  173. package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
  174. package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
  175. package/packages/shared-perm-presets/src/presets/index.ts +8 -0
  176. package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
  177. package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
  178. package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
  179. package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
  180. package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
  181. package/packages/shared-perm-presets/src/types.ts +192 -0
  182. package/packages/shared-perm-presets/tsconfig.build.json +15 -0
  183. package/packages/shared-perm-presets/tsconfig.json +9 -0
  184. package/packages/shared-perm-presets/tsup.config.ts +8 -0
  185. package/packages/shared-perm-presets/vitest.config.ts +9 -0
  186. package/packages/shared-testing/CHANGELOG.md +20 -0
  187. package/packages/shared-testing/README.md +430 -0
  188. package/packages/shared-testing/package.json +51 -0
  189. package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
  190. package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
  191. package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
  192. package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
  193. package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
  194. package/packages/shared-testing/src/create-test-context.ts +550 -0
  195. package/packages/shared-testing/src/index.ts +77 -0
  196. package/packages/shared-testing/src/mock-cache.ts +179 -0
  197. package/packages/shared-testing/src/mock-llm.ts +319 -0
  198. package/packages/shared-testing/src/mock-logger.ts +97 -0
  199. package/packages/shared-testing/src/mock-storage.ts +108 -0
  200. package/packages/shared-testing/src/setup-platform.ts +101 -0
  201. package/packages/shared-testing/src/test-command.ts +288 -0
  202. package/packages/shared-testing/tsconfig.build.json +15 -0
  203. package/packages/shared-testing/tsconfig.json +9 -0
  204. package/packages/shared-testing/tsup.config.ts +20 -0
  205. package/packages/shared-testing/vitest.config.ts +3 -0
  206. package/packages/shared-tool-kit/CHANGELOG.md +20 -0
  207. package/packages/shared-tool-kit/package.json +47 -0
  208. package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
  209. package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
  210. package/packages/shared-tool-kit/src/factory.ts +126 -0
  211. package/packages/shared-tool-kit/src/index.ts +32 -0
  212. package/packages/shared-tool-kit/src/testing/index.ts +84 -0
  213. package/packages/shared-tool-kit/tsconfig.build.json +15 -0
  214. package/packages/shared-tool-kit/tsconfig.json +9 -0
  215. package/packages/shared-tool-kit/tsup.config.ts +21 -0
  216. package/pnpm-workspace.yaml +11070 -0
  217. package/prettierrc.json +1 -0
  218. package/scripts/devkit-sync.mjs +37 -0
  219. package/scripts/hooks/post-push +9 -0
  220. package/scripts/hooks/pre-commit +9 -0
  221. package/scripts/hooks/pre-push +9 -0
  222. package/tsconfig.base.json +9 -0
  223. package/tsconfig.build.json +15 -0
  224. package/tsconfig.json +9 -0
  225. package/tsconfig.paths.json +50 -0
  226. package/tsconfig.tools.json +18 -0
  227. package/tsup.config.bin.ts +34 -0
  228. package/tsup.config.cli.ts +41 -0
  229. package/tsup.config.dual.ts +46 -0
  230. package/tsup.config.ts +36 -0
  231. package/tsup.external.json +104 -0
  232. package/vitest.config.ts +48 -0
@@ -0,0 +1,550 @@
1
+ /**
2
+ * @module @kb-labs/shared-testing/create-test-context
3
+ *
4
+ * Enhanced test context factory that bridges ctx.platform and the global singleton.
5
+ *
6
+ * Unlike the original createTestContext() from SDK, this version:
7
+ * - Uses mockLLM/mockCache/mockLogger with vi.fn() spies (not noop functions)
8
+ * - Syncs ctx.platform adapters with the global singleton via setupTestPlatform()
9
+ * - Provides a cleanup function to reset the singleton in afterEach()
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';
14
+ *
15
+ * const llm = mockLLM().onAnyComplete().respondWith('hello');
16
+ * const { ctx, cleanup } = createTestContext({ platform: { llm } });
17
+ *
18
+ * // Both work — ctx.platform and useLLM() return the same mock
19
+ * await handler.execute(ctx, args);
20
+ * expect(llm.complete).toHaveBeenCalled();
21
+ *
22
+ * cleanup(); // Reset singleton in afterEach
23
+ * ```
24
+ */
25
+
26
+ import type {
27
+ PluginContextV3,
28
+ HostContext,
29
+ PlatformServices,
30
+ UIFacade,
31
+ TraceContext,
32
+ RuntimeAPI,
33
+ PluginAPI,
34
+ FSShim,
35
+ FetchShim,
36
+ EnvShim,
37
+ InvokeAPI,
38
+ EnvironmentAPI,
39
+ WorkspaceAPI,
40
+ SnapshotAPI,
41
+ } from '@kb-labs/plugin-contracts';
42
+
43
+ import { setupTestPlatform } from './setup-platform.js';
44
+ import { mockLLM } from './mock-llm.js';
45
+ import { mockCache } from './mock-cache.js';
46
+ import { mockStorage } from './mock-storage.js';
47
+ import { mockLogger } from './mock-logger.js';
48
+ import { vi } from 'vitest';
49
+
50
+ // ────────────────────────────────────────────────────────────────────
51
+ // Types
52
+ // ────────────────────────────────────────────────────────────────────
53
+
54
+ export interface CreateTestContextOptions {
55
+ pluginId?: string;
56
+ pluginVersion?: string;
57
+ host?: 'cli' | 'rest' | 'workflow' | 'webhook';
58
+ hostContext?: HostContext;
59
+ config?: unknown;
60
+ cwd?: string;
61
+ outdir?: string;
62
+ tenantId?: string;
63
+ signal?: AbortSignal;
64
+ /** Override platform services. Also synced to global singleton. */
65
+ platform?: Partial<PlatformServices>;
66
+ /** Override UI facade */
67
+ ui?: Partial<UIFacade>;
68
+ /**
69
+ * If true, sync platform adapters to the global singleton
70
+ * so that useLLM(), useCache(), etc. return the test mocks.
71
+ * @default true
72
+ */
73
+ syncSingleton?: boolean;
74
+ }
75
+
76
+ export interface TestContextResult<TConfig = unknown> {
77
+ /** The plugin context with test mocks */
78
+ ctx: PluginContextV3<TConfig>;
79
+ /** Call in afterEach() to reset the global singleton */
80
+ cleanup: () => void;
81
+ }
82
+
83
+ // ────────────────────────────────────────────────────────────────────
84
+ // Mock factories
85
+ // ────────────────────────────────────────────────────────────────────
86
+
87
+ export function createMockTrace(): TraceContext {
88
+ return {
89
+ traceId: 'test-trace-id',
90
+ spanId: 'test-span-id',
91
+ parentSpanId: undefined,
92
+ addEvent: () => {},
93
+ setAttribute: () => {},
94
+ recordError: () => {},
95
+ };
96
+ }
97
+
98
+ export function createMockUI(): UIFacade {
99
+ const messages: string[] = [];
100
+
101
+ const mockColor = (text: string) => text;
102
+ const mockColors = {
103
+ success: mockColor,
104
+ error: mockColor,
105
+ warning: mockColor,
106
+ info: mockColor,
107
+ primary: mockColor,
108
+ accent: mockColor,
109
+ highlight: mockColor,
110
+ secondary: mockColor,
111
+ emphasis: mockColor,
112
+ muted: mockColor,
113
+ foreground: mockColor,
114
+ dim: mockColor,
115
+ bold: mockColor,
116
+ underline: mockColor,
117
+ inverse: mockColor,
118
+ };
119
+
120
+ return {
121
+ colors: mockColors,
122
+ symbols: {
123
+ success: '+',
124
+ error: 'x',
125
+ warning: '!',
126
+ info: 'i',
127
+ bullet: '-',
128
+ clock: 'T',
129
+ folder: 'D',
130
+ package: 'P',
131
+ pointer: '>',
132
+ section: '#',
133
+ separator: '-',
134
+ border: '|',
135
+ topLeft: '+',
136
+ topRight: '+',
137
+ bottomLeft: '+',
138
+ bottomRight: '+',
139
+ leftT: '+',
140
+ rightT: '+',
141
+ },
142
+ write: vi.fn((text) => messages.push(`WRITE: ${text}`)),
143
+ info: vi.fn((msg) => messages.push(`INFO: ${msg}`)),
144
+ success: vi.fn((msg) => messages.push(`SUCCESS: ${msg}`)),
145
+ warn: vi.fn((msg) => messages.push(`WARN: ${msg}`)),
146
+ error: vi.fn((err) => messages.push(`ERROR: ${err instanceof Error ? err.message : err}`)),
147
+ debug: vi.fn((msg) => messages.push(`DEBUG: ${msg}`)),
148
+ spinner: vi.fn((msg) => {
149
+ messages.push(`SPINNER: ${msg}`);
150
+ return {
151
+ update: vi.fn((m: string) => messages.push(`SPINNER UPDATE: ${m}`)),
152
+ succeed: vi.fn((m?: string) => messages.push(`SPINNER SUCCEED: ${m ?? msg}`)),
153
+ fail: vi.fn((m?: string) => messages.push(`SPINNER FAIL: ${m ?? msg}`)),
154
+ stop: vi.fn(),
155
+ };
156
+ }),
157
+ table: vi.fn((data) => messages.push(`TABLE: ${JSON.stringify(data)}`)),
158
+ json: vi.fn((data) => messages.push(`JSON: ${JSON.stringify(data)}`)),
159
+ newline: vi.fn(() => messages.push('')),
160
+ divider: vi.fn(() => messages.push('-'.repeat(40))),
161
+ box: vi.fn((content, title) => {
162
+ if (title) {messages.push(`+- ${title} -+`);}
163
+ messages.push(content);
164
+ if (title) {messages.push(`+${'-'.repeat(title.length + 4)}+`);}
165
+ }),
166
+ sideBox: vi.fn((options) => {
167
+ messages.push(`+- ${options.title} -+`);
168
+ if (options.summary) {
169
+ Object.entries(options.summary).forEach(([key, value]) => {
170
+ messages.push(` ${key}: ${value}`);
171
+ });
172
+ }
173
+ if (options.sections) {
174
+ options.sections.forEach((section: any) => {
175
+ if (section.header) {messages.push(` ${section.header}:`);}
176
+ section.items.forEach((item: string) => messages.push(` ${item}`));
177
+ });
178
+ }
179
+ if (options.timing) {
180
+ messages.push(` Timing: ${options.timing}ms`);
181
+ }
182
+ messages.push('+' + '-'.repeat(options.title.length + 4) + '+');
183
+ }),
184
+ confirm: vi.fn(async () => true),
185
+ prompt: vi.fn(async () => ''),
186
+ };
187
+ }
188
+
189
+ export function createMockRuntime(): RuntimeAPI {
190
+ const mockFS: FSShim = {
191
+ readFile: vi.fn(async () => ''),
192
+ readFileBuffer: vi.fn(async () => new Uint8Array()),
193
+ writeFile: vi.fn(async () => {}),
194
+ readdir: vi.fn(async () => []),
195
+ readdirWithStats: vi.fn(async () => []),
196
+ mkdir: vi.fn(async () => {}),
197
+ rm: vi.fn(async () => {}),
198
+ copy: vi.fn(async () => {}),
199
+ move: vi.fn(async () => {}),
200
+ glob: vi.fn(async () => []),
201
+ stat: vi.fn(async () => ({
202
+ isFile: () => false,
203
+ isDirectory: () => false,
204
+ size: 0,
205
+ mtime: Date.now(),
206
+ ctime: Date.now(),
207
+ })),
208
+ exists: vi.fn(async () => false),
209
+ resolve: (path: string) => path,
210
+ relative: (path: string) => path,
211
+ join: (...segments: string[]) => segments.join('/'),
212
+ dirname: (path: string) => path.split('/').slice(0, -1).join('/'),
213
+ basename: (path: string) => path.split('/').pop() ?? '',
214
+ extname: (path: string) => {
215
+ const base = path.split('/').pop() ?? '';
216
+ const idx = base.lastIndexOf('.');
217
+ return idx > 0 ? base.slice(idx) : '';
218
+ },
219
+ };
220
+
221
+ const mockFetch: FetchShim = vi.fn(async () => new Response('mock'));
222
+ const mockEnv: EnvShim = vi.fn((_key: string) => undefined) as EnvShim;
223
+
224
+ return {
225
+ fs: mockFS,
226
+ fetch: mockFetch,
227
+ env: mockEnv,
228
+ };
229
+ }
230
+
231
+ export function createMockEnvironmentAPI(): EnvironmentAPI {
232
+ return {
233
+ create: vi.fn(async () => ({
234
+ environmentId: 'env_mock_1',
235
+ provider: 'mock',
236
+ status: 'ready' as const,
237
+ createdAt: new Date().toISOString(),
238
+ updatedAt: new Date().toISOString(),
239
+ })),
240
+ status: vi.fn(async (environmentId: string) => ({
241
+ environmentId,
242
+ status: 'ready' as const,
243
+ updatedAt: new Date().toISOString(),
244
+ })),
245
+ destroy: vi.fn(async () => {}),
246
+ renewLease: vi.fn(async () => ({
247
+ leaseId: 'lease_mock_1',
248
+ acquiredAt: new Date().toISOString(),
249
+ expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
250
+ })),
251
+ };
252
+ }
253
+
254
+ export function createMockWorkspaceAPI(): WorkspaceAPI {
255
+ return {
256
+ materialize: vi.fn(async () => ({
257
+ workspaceId: 'ws_mock_1',
258
+ provider: 'mock',
259
+ status: 'ready' as const,
260
+ rootPath: '/tmp/ws_mock_1',
261
+ createdAt: new Date().toISOString(),
262
+ updatedAt: new Date().toISOString(),
263
+ })),
264
+ attach: vi.fn(async (request) => ({
265
+ workspaceId: request.workspaceId,
266
+ environmentId: request.environmentId,
267
+ mountPath: request.mountPath ?? '/workspace',
268
+ attachedAt: new Date().toISOString(),
269
+ })),
270
+ release: vi.fn(async () => {}),
271
+ status: vi.fn(async (workspaceId: string) => ({
272
+ workspaceId,
273
+ status: 'ready' as const,
274
+ updatedAt: new Date().toISOString(),
275
+ })),
276
+ };
277
+ }
278
+
279
+ export function createMockSnapshotAPI(): SnapshotAPI {
280
+ return {
281
+ capture: vi.fn(async (request) => ({
282
+ snapshotId: request.snapshotId ?? 'snap_mock_1',
283
+ provider: 'mock',
284
+ status: 'ready' as const,
285
+ createdAt: new Date().toISOString(),
286
+ updatedAt: new Date().toISOString(),
287
+ workspaceId: request.workspaceId,
288
+ environmentId: request.environmentId,
289
+ })),
290
+ restore: vi.fn(async (request) => ({
291
+ snapshotId: request.snapshotId,
292
+ restoredAt: new Date().toISOString(),
293
+ workspaceId: request.workspaceId,
294
+ environmentId: request.environmentId,
295
+ targetPath: request.targetPath,
296
+ })),
297
+ status: vi.fn(async (snapshotId: string) => ({
298
+ snapshotId,
299
+ status: 'ready' as const,
300
+ updatedAt: new Date().toISOString(),
301
+ })),
302
+ delete: vi.fn(async () => {}),
303
+ gc: vi.fn(async (request) => ({
304
+ scanned: 0,
305
+ deleted: 0,
306
+ dryRun: request?.dryRun ?? false,
307
+ })),
308
+ };
309
+ }
310
+
311
+ export function createInfraApiMocks(): Pick<PluginAPI, 'environment' | 'workspace' | 'snapshot'> {
312
+ return {
313
+ environment: createMockEnvironmentAPI(),
314
+ workspace: createMockWorkspaceAPI(),
315
+ snapshot: createMockSnapshotAPI(),
316
+ };
317
+ }
318
+
319
+ export function createMockPluginAPI(): PluginAPI {
320
+ const infra = createInfraApiMocks();
321
+ return {
322
+ lifecycle: {
323
+ onCleanup: vi.fn(),
324
+ },
325
+ state: {
326
+ get: vi.fn(async () => undefined),
327
+ set: vi.fn(async () => {}),
328
+ delete: vi.fn(async () => {}),
329
+ has: vi.fn(async () => false),
330
+ getMany: vi.fn(async () => new Map()),
331
+ setMany: vi.fn(async () => {}),
332
+ },
333
+ artifacts: {
334
+ write: vi.fn(async () => '/mock/path'),
335
+ list: vi.fn(async () => []),
336
+ read: vi.fn(async () => ''),
337
+ readBuffer: vi.fn(async () => new Uint8Array()),
338
+ exists: vi.fn(async () => false),
339
+ path: vi.fn(() => '/mock/path'),
340
+ },
341
+ shell: {
342
+ exec: vi.fn(async () => ({ code: 0, stdout: '', stderr: '', ok: true })),
343
+ },
344
+ events: {
345
+ emit: vi.fn(async () => {}),
346
+ },
347
+ invoke: {
348
+ call: vi.fn(async (_pluginId: string, _input?: unknown, _options?: unknown) => undefined) as InvokeAPI['call'],
349
+ },
350
+ workflows: {
351
+ run: vi.fn(async () => 'mock-run-id'),
352
+ wait: vi.fn(async () => undefined),
353
+ status: vi.fn(async () => null),
354
+ cancel: vi.fn(async () => {}),
355
+ list: vi.fn(async () => []),
356
+ },
357
+ jobs: {
358
+ submit: vi.fn(async () => 'mock-job-id'),
359
+ schedule: vi.fn(async () => 'mock-scheduled-job-id'),
360
+ wait: vi.fn(async () => undefined),
361
+ status: vi.fn(async () => null),
362
+ cancel: vi.fn(async () => false),
363
+ list: vi.fn(async () => []),
364
+ },
365
+ cron: {
366
+ register: vi.fn(async () => {}),
367
+ unregister: vi.fn(async () => {}),
368
+ list: vi.fn(async () => []),
369
+ pause: vi.fn(async () => {}),
370
+ resume: vi.fn(async () => {}),
371
+ trigger: vi.fn(async () => {}),
372
+ },
373
+ environment: infra.environment,
374
+ workspace: infra.workspace,
375
+ snapshot: infra.snapshot,
376
+ };
377
+ }
378
+
379
+ export const createMockPlatformApi = createMockPluginAPI;
380
+
381
+ export function createMockPluginContextV3<TConfig = unknown>(
382
+ options: CreateTestContextOptions = {}
383
+ ): TestContextResult<TConfig> {
384
+ return createTestContext<TConfig>(options);
385
+ }
386
+
387
+ // ────────────────────────────────────────────────────────────────────
388
+ // Public API
389
+ // ────────────────────────────────────────────────────────────────────
390
+
391
+ /**
392
+ * Create a test context for plugin development.
393
+ *
394
+ * Unlike the original SDK version, this:
395
+ * - Uses mock builders with vi.fn() spies (not noop functions)
396
+ * - Syncs platform adapters to the global singleton by default
397
+ * - Returns a cleanup function for afterEach()
398
+ *
399
+ * @example
400
+ * ```typescript
401
+ * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';
402
+ *
403
+ * describe('my handler', () => {
404
+ * let cleanup: () => void;
405
+ *
406
+ * beforeEach(() => {
407
+ * const llm = mockLLM().onAnyComplete().respondWith('ok');
408
+ * const result = createTestContext({ platform: { llm } });
409
+ * cleanup = result.cleanup;
410
+ * // Use result.ctx in your tests
411
+ * });
412
+ *
413
+ * afterEach(() => cleanup());
414
+ * });
415
+ * ```
416
+ */
417
+ export function createTestContext<TConfig = unknown>(
418
+ options: CreateTestContextOptions = {}
419
+ ): TestContextResult<TConfig> {
420
+ const {
421
+ pluginId = 'test-plugin',
422
+ pluginVersion = '0.0.0',
423
+ host = 'cli',
424
+ hostContext,
425
+ config,
426
+ cwd = process.cwd(),
427
+ outdir,
428
+ tenantId,
429
+ signal,
430
+ platform: platformOverrides,
431
+ ui: uiOverrides,
432
+ syncSingleton = true,
433
+ } = options;
434
+
435
+ const resolvedOutdir = outdir ?? `${cwd}/.kb/output`;
436
+
437
+ // Build default host context
438
+ const defaultHostContext: HostContext =
439
+ hostContext ??
440
+ (() => {
441
+ switch (host) {
442
+ case 'cli':
443
+ return { host: 'cli', argv: ['test'], flags: {} };
444
+ case 'rest':
445
+ return {
446
+ host: 'rest',
447
+ method: 'GET',
448
+ path: '/test',
449
+ requestId: 'test-req-001',
450
+ traceId: 'test-trace-001',
451
+ };
452
+ case 'workflow':
453
+ return { host: 'workflow', workflowId: 'test-wf', runId: 'test-run', stepId: 'test-step' };
454
+ case 'webhook':
455
+ return { host: 'webhook', event: 'test:event' };
456
+ }
457
+ })();
458
+
459
+ // Create default mocks (with spies, not noop)
460
+ const defaultLogger = mockLogger();
461
+ const defaultLLM = mockLLM();
462
+ const defaultCache = mockCache();
463
+ const defaultStorage = mockStorage();
464
+
465
+ const defaultPlatform: PlatformServices = {
466
+ logger: defaultLogger,
467
+ llm: defaultLLM,
468
+ embeddings: {
469
+ embed: vi.fn(async () => []),
470
+ embedBatch: vi.fn(async () => [[]]),
471
+ dimensions: 1536,
472
+ getDimensions: vi.fn(async () => 1536),
473
+ },
474
+ vectorStore: {
475
+ search: vi.fn(async () => []),
476
+ upsert: vi.fn(async () => {}),
477
+ delete: vi.fn(async () => {}),
478
+ count: vi.fn(async () => 0),
479
+ },
480
+ cache: defaultCache,
481
+ storage: defaultStorage,
482
+ analytics: {
483
+ track: vi.fn(async () => {}),
484
+ identify: vi.fn(async () => {}),
485
+ flush: vi.fn(async () => {}),
486
+ },
487
+ eventBus: {
488
+ publish: vi.fn(async () => {}),
489
+ subscribe: vi.fn(() => () => {}),
490
+ },
491
+ logs: {
492
+ query: vi.fn(async () => ({ logs: [], total: 0, hasMore: false, source: 'buffer' as const })),
493
+ getById: vi.fn(async () => null),
494
+ search: vi.fn(async () => ({ logs: [], total: 0, hasMore: false })),
495
+ subscribe: vi.fn(() => () => {}),
496
+ getStats: vi.fn(async () => ({})),
497
+ getCapabilities: vi.fn(() => ({ hasBuffer: false, hasPersistence: false, hasSearch: false, hasStreaming: false })),
498
+ },
499
+ };
500
+
501
+ // Merge overrides
502
+ const finalPlatform: PlatformServices = {
503
+ ...defaultPlatform,
504
+ ...platformOverrides,
505
+ };
506
+
507
+ // Sync to global singleton
508
+ let cleanupFn = () => {};
509
+ if (syncSingleton) {
510
+ const result = setupTestPlatform({
511
+ llm: finalPlatform.llm,
512
+ cache: finalPlatform.cache,
513
+ storage: finalPlatform.storage,
514
+ logger: finalPlatform.logger,
515
+ analytics: finalPlatform.analytics,
516
+ embeddings: finalPlatform.embeddings,
517
+ vectorStore: finalPlatform.vectorStore,
518
+ eventBus: finalPlatform.eventBus,
519
+ });
520
+ cleanupFn = result.cleanup;
521
+ }
522
+
523
+ // UI
524
+ const defaultUI = createMockUI();
525
+ const finalUI: UIFacade = {
526
+ ...defaultUI,
527
+ ...uiOverrides,
528
+ };
529
+
530
+ // Build context
531
+ const ctx: PluginContextV3<TConfig> = {
532
+ host,
533
+ requestId: 'test-trace:test-span',
534
+ pluginId,
535
+ pluginVersion,
536
+ tenantId,
537
+ cwd,
538
+ outdir: resolvedOutdir,
539
+ config: config as TConfig,
540
+ signal,
541
+ trace: createMockTrace(),
542
+ hostContext: defaultHostContext,
543
+ ui: finalUI,
544
+ platform: finalPlatform,
545
+ runtime: createMockRuntime(),
546
+ api: createMockPluginAPI(),
547
+ };
548
+
549
+ return { ctx, cleanup: cleanupFn };
550
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @kb-labs/shared-testing
3
+ *
4
+ * Test utilities for KB Labs plugin development.
5
+ *
6
+ * Provides mock builders, platform setup, and test context factory
7
+ * that solve the singleton gap between ctx.platform and composables.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import {
12
+ * createTestContext,
13
+ * setupTestPlatform,
14
+ * mockLLM,
15
+ * mockCache,
16
+ * mockStorage,
17
+ * mockLogger,
18
+ * } from '@kb-labs/shared-testing';
19
+ * ```
20
+ */
21
+
22
+ // Platform setup (singleton gap fix)
23
+ export {
24
+ setupTestPlatform,
25
+ type TestPlatformOptions,
26
+ type TestPlatformResult,
27
+ } from './setup-platform.js';
28
+
29
+ // Mock builders
30
+ export {
31
+ mockLLM,
32
+ type MockLLM,
33
+ type MockLLMInstance,
34
+ type LLMCall,
35
+ type LLMToolCallRecord,
36
+ } from './mock-llm.js';
37
+
38
+ export {
39
+ mockCache,
40
+ type MockCacheInstance,
41
+ } from './mock-cache.js';
42
+
43
+ export {
44
+ mockStorage,
45
+ type MockStorageInstance,
46
+ } from './mock-storage.js';
47
+
48
+ export {
49
+ mockLogger,
50
+ type MockLoggerInstance,
51
+ type LogEntry,
52
+ } from './mock-logger.js';
53
+
54
+ // Test context factory
55
+ export {
56
+ createTestContext,
57
+ createMockPluginContextV3,
58
+ createMockPlatformApi,
59
+ createMockPluginAPI,
60
+ createMockEnvironmentAPI,
61
+ createMockWorkspaceAPI,
62
+ createMockSnapshotAPI,
63
+ createInfraApiMocks,
64
+ createMockRuntime,
65
+ createMockUI,
66
+ createMockTrace,
67
+ type CreateTestContextOptions,
68
+ type TestContextResult,
69
+ } from './create-test-context.js';
70
+
71
+ // Command test runner
72
+ export {
73
+ testCommand,
74
+ type TestableHandler,
75
+ type TestCommandOptions,
76
+ type TestCommandResult,
77
+ } from './test-command.js';