@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,192 @@
1
+ /**
2
+ * Platform service permissions
3
+ */
4
+ export interface PlatformPermissions {
5
+ /** LLM access */
6
+ llm?: boolean | { models?: string[] };
7
+ /** Vector store access */
8
+ vectorStore?: boolean | { collections?: string[] };
9
+ /** Cache access */
10
+ cache?: boolean | string[]; // true = all, string[] = namespaces
11
+ /**
12
+ * Storage access (file/blob storage)
13
+ *
14
+ * **Formats:**
15
+ * - `true` - full access (all read/write)
16
+ * - `false` - no access
17
+ * - `string[]` - path prefixes (both read + write)
18
+ * - `{ read?: string[]; write?: string[] }` - granular read/write
19
+ *
20
+ * **Examples:**
21
+ * ```typescript
22
+ * storage: true // Full access
23
+ * storage: ['.kb/artifacts/**'] // Read + write .kb/artifacts
24
+ * storage: { read: ['**'], write: ['.kb/artifacts/**'] } // Read all, write only .kb/artifacts
25
+ * ```
26
+ */
27
+ storage?: boolean | string[] | { read?: string[]; write?: string[] };
28
+ /**
29
+ * Database access (SQL, Document, KV, TimeSeries)
30
+ *
31
+ * **Formats:**
32
+ * - `true` - full access to all databases
33
+ * - `false` - no access
34
+ * - Granular per database type:
35
+ * - `sql?: { tables?: string[] }` - SQL table access
36
+ * - `document?: { collections?: string[] }` - Document collection access
37
+ * - `kv?: { prefixes?: string[] }` - KV key prefix access
38
+ * - `timeseries?: { metrics?: string[] }` - TimeSeries metric access
39
+ *
40
+ * **Examples:**
41
+ * ```typescript
42
+ * database: true // Full access to all databases
43
+ * database: {
44
+ * sql: { tables: ['incidents', 'audit_log'] },
45
+ * document: { collections: ['workflows'] }
46
+ * }
47
+ * ```
48
+ */
49
+ database?:
50
+ | boolean
51
+ | {
52
+ sql?: boolean | { tables?: string[] };
53
+ document?: boolean | { collections?: string[] };
54
+ kv?: boolean | { prefixes?: string[] };
55
+ timeseries?: boolean | { metrics?: string[] };
56
+ };
57
+ /** Analytics access */
58
+ analytics?: boolean;
59
+ /** Embeddings access */
60
+ embeddings?: boolean;
61
+ /** Event bus access */
62
+ eventBus?: boolean | { publish?: string[]; subscribe?: string[] };
63
+ }
64
+
65
+ /**
66
+ * Permission specification for preset building (declarative format)
67
+ * This is the format used when defining presets and combining them.
68
+ *
69
+ * Design:
70
+ * - Presets declare what plugin WANTS (allow-list only)
71
+ * - No deny - platform enforces hardcoded security patterns
72
+ */
73
+ export interface PermissionSpec {
74
+ /** File system permissions */
75
+ fs?: {
76
+ mode?: 'read' | 'readWrite';
77
+ allow?: string[];
78
+ };
79
+ /** Environment variable permissions */
80
+ env?: {
81
+ read?: string[];
82
+ };
83
+ /** Network permissions */
84
+ network?: {
85
+ fetch?: string[];
86
+ };
87
+ /** Platform service permissions */
88
+ platform?: PlatformPermissions;
89
+ /** Shell execution permissions */
90
+ shell?: {
91
+ allow?: string[];
92
+ };
93
+ /** Resource quotas */
94
+ quotas?: {
95
+ timeoutMs?: number;
96
+ memoryMb?: number;
97
+ cpuMs?: number;
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Runtime permission specification (explicit format)
103
+ * This is the format used by plugin-runtime fs-shim.
104
+ * Matches PermissionSpec from @kb-labs/plugin-contracts.
105
+ */
106
+ export interface RuntimePermissionSpec {
107
+ /** File system permissions - explicit read/write lists */
108
+ fs?: {
109
+ read?: string[];
110
+ write?: string[];
111
+ };
112
+ /** Environment variable permissions */
113
+ env?: {
114
+ read?: string[];
115
+ };
116
+ /** Network permissions */
117
+ network?: {
118
+ fetch?: string[];
119
+ };
120
+ /** Platform service permissions */
121
+ platform?: PlatformPermissions;
122
+ /** Shell execution permissions */
123
+ shell?: {
124
+ allow?: string[];
125
+ };
126
+ /** Resource quotas */
127
+ quotas?: {
128
+ timeoutMs?: number;
129
+ memoryMb?: number;
130
+ cpuMs?: number;
131
+ };
132
+ }
133
+
134
+ /**
135
+ * A preset is a named permission configuration
136
+ */
137
+ export interface PermissionPreset {
138
+ /** Unique preset identifier */
139
+ id: string;
140
+ /** Human-readable description */
141
+ description: string;
142
+ /** The permission spec this preset provides */
143
+ permissions: PermissionSpec;
144
+ }
145
+
146
+ /**
147
+ * Builder for combining multiple presets
148
+ */
149
+ export interface PresetBuilder {
150
+ /** Add a preset to the combination */
151
+ with(preset: PermissionPreset | PermissionSpec): PresetBuilder;
152
+ /** Add environment variables to read */
153
+ withEnv(vars: string[]): PresetBuilder;
154
+ /** Add file system permissions */
155
+ withFs(fs: PermissionSpec['fs']): PresetBuilder;
156
+ /** Add network permissions */
157
+ withNetwork(network: PermissionSpec['network']): PresetBuilder;
158
+ /** Add platform service permissions */
159
+ withPlatform(platform: PlatformPermissions): PresetBuilder;
160
+ /** Add shell execution permissions */
161
+ withShell(shell: PermissionSpec['shell']): PresetBuilder;
162
+ /** Add quotas */
163
+ withQuotas(quotas: PermissionSpec['quotas']): PresetBuilder;
164
+ /**
165
+ * Add storage permissions.
166
+ *
167
+ * @param storage - Storage permissions (true, string[], or { read, write })
168
+ * @returns Builder for chaining
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * .withStorage({ read: ['**'], write: ['.kb/artifacts/**'] })
173
+ * .withStorage(['.kb/data/**']) // Both read + write
174
+ * ```
175
+ */
176
+ withStorage(storage: PlatformPermissions['storage']): PresetBuilder;
177
+ /**
178
+ * Add database permissions.
179
+ *
180
+ * @param database - Database permissions (true or granular per type)
181
+ * @returns Builder for chaining
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * .withDatabase({ sql: { tables: ['incidents'] } })
186
+ * .withDatabase(true) // Full database access
187
+ * ```
188
+ */
189
+ withDatabase(database: PlatformPermissions['database']): PresetBuilder;
190
+ /** Build the final permission spec (converts to runtime format) */
191
+ build(): RuntimePermissionSpec;
192
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "baseUrl": ".",
6
+ "paths": {}
7
+ },
8
+ "include": [
9
+ "src/**/*"
10
+ ],
11
+ "exclude": [
12
+ "dist",
13
+ "node_modules"
14
+ ]
15
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@kb-labs/devkit/tsconfig/node.json",
4
+ "compilerOptions": {
5
+ "rootDir": "src",
6
+ "outDir": "dist"
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'tsup';
2
+ import nodePreset from '@kb-labs/devkit/tsup/node';
3
+
4
+ export default defineConfig({
5
+ ...nodePreset,
6
+ entry: ['src/index.ts'],
7
+ tsconfig: 'tsconfig.build.json',
8
+ });
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: false,
6
+ environment: 'node',
7
+ include: ['src/__tests__/**/*.test.ts'],
8
+ },
9
+ });
@@ -0,0 +1,20 @@
1
+ ## [1.1.0] - 2026-03-24
2
+
3
+ **5 packages** bumped to v1.1.0
4
+
5
+ | Package | Previous | Bump |
6
+ |---------|----------|------|
7
+ | `@kb-labs/perm-presets` | 1.0.0 | minor |
8
+ | `@kb-labs/shared-command-kit` | 1.0.0 | minor |
9
+ | `@kb-labs/shared-tool-kit` | 1.0.0 | minor |
10
+ | `@kb-labs/shared-cli-ui` | 1.0.0 | minor |
11
+ | `@kb-labs/shared-testing` | 1.0.0 | minor |
12
+
13
+ ### ✨ New Features
14
+
15
+ - **config**: Introduces new configuration files for package management, allowing users to easily manage dependencies and ensure consistent environments across projects.
16
+ - **github**: Implements GitHub workflows for CI/CD, streamlining the development process and enabling quicker, more reliable software updates for users.
17
+
18
+ ### 🐛 Bug Fixes
19
+
20
+ - **tests**: Improves code clarity by using an object type instead of a placeholder, which helps prevent misunderstandings in the codebase and enhances maintainability. Additionally, it resolves a linting issue that could lead to confusion when no value is returned from certain functions.
@@ -0,0 +1,430 @@
1
+ # @kb-labs/shared-testing
2
+
3
+ Test utilities for KB Labs plugin development — mock builders, platform setup, test context.
4
+
5
+ ## Problem
6
+
7
+ Testing plugins is hard because of the **singleton gap**: composables like `useLLM()`, `useCache()`, `useLogger()` read from the global platform singleton (`@kb-labs/core-runtime`), but `createTestContext()` only populates `ctx.platform`. Code inside handlers that uses composables gets the uninitialized singleton instead of test mocks.
8
+
9
+ Additionally, existing mocks are noop functions — no `vi.fn()` spies, no way to configure specific responses, no call tracking.
10
+
11
+ ## Solution
12
+
13
+ This package provides:
14
+
15
+ - **`setupTestPlatform()`** — bridges `ctx.platform` and the global singleton
16
+ - **`mockLLM()`** — fluent builder with prompt matching, streaming, error simulation, tool calls
17
+ - **`mockCache()`** — working in-memory cache with TTL and sorted sets
18
+ - **`mockStorage()`** — virtual filesystem on `Map<string, Buffer>`
19
+ - **`mockLogger()`** — logger with message recording
20
+ - **`createTestContext()`** — enhanced context factory that syncs everything automatically
21
+
22
+ All methods are `vi.fn()` spies — you get full assertion capabilities out of the box.
23
+
24
+ ## Installation
25
+
26
+ Available as workspace dependency:
27
+
28
+ ```json
29
+ {
30
+ "devDependencies": {
31
+ "@kb-labs/shared-testing": "link:../../../kb-labs-shared/packages/shared-testing"
32
+ }
33
+ }
34
+ ```
35
+
36
+ Or via SDK re-export:
37
+
38
+ ```typescript
39
+ import { mockLLM, createTestContext } from '@kb-labs/sdk/testing';
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```typescript
45
+ import { createTestContext, mockLLM } from '@kb-labs/sdk/testing';
46
+ import { useLLM } from '@kb-labs/sdk';
47
+ import { describe, it, expect, afterEach } from 'vitest';
48
+
49
+ describe('my handler', () => {
50
+ let cleanup: () => void;
51
+
52
+ afterEach(() => cleanup());
53
+
54
+ it('generates a commit message', async () => {
55
+ const llm = mockLLM()
56
+ .onComplete('commit').respondWith('feat: add login page')
57
+ .onAnyComplete().respondWith('ok');
58
+
59
+ const { ctx, cleanup: c } = createTestContext({ platform: { llm } });
60
+ cleanup = c;
61
+
62
+ // Both work — same mock instance:
63
+ expect(ctx.platform.llm).toBe(llm);
64
+ expect(useLLM()).toBe(llm); // singleton gap solved!
65
+
66
+ const res = await ctx.platform.llm.complete('Generate commit message');
67
+ expect(res.content).toBe('feat: add login page');
68
+ expect(llm.complete).toHaveBeenCalledOnce();
69
+ });
70
+ });
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### `setupTestPlatform(options)`
76
+
77
+ Sets mock adapters into the global platform singleton. Call `cleanup()` in `afterEach()`.
78
+
79
+ ```typescript
80
+ import { setupTestPlatform, mockLLM, mockCache } from '@kb-labs/shared-testing';
81
+
82
+ const { platform, cleanup } = setupTestPlatform({
83
+ llm: mockLLM(),
84
+ cache: mockCache(),
85
+ });
86
+
87
+ // Now useLLM() and useCache() return these mocks
88
+ ```
89
+
90
+ **Options:** `llm`, `cache`, `embeddings`, `vectorStore`, `storage`, `analytics`, `logger`, `eventBus` — all optional.
91
+
92
+ ### `mockLLM()`
93
+
94
+ Fluent builder that creates an `ILLM` instance with `vi.fn()` spies and call tracking.
95
+
96
+ #### Prompt matching
97
+
98
+ ```typescript
99
+ const llm = mockLLM()
100
+ // String match (substring)
101
+ .onComplete('commit').respondWith('feat: add feature')
102
+ // Regex match
103
+ .onComplete(/explain/i).respondWith('This code does X')
104
+ // Function match
105
+ .onComplete(p => p.length > 100).respondWith('Long prompt handled')
106
+ // Default fallback
107
+ .onAnyComplete().respondWith('default answer');
108
+ ```
109
+
110
+ #### Dynamic responses
111
+
112
+ ```typescript
113
+ const llm = mockLLM()
114
+ .onAnyComplete().respondWith(prompt => `Echo: ${prompt}`);
115
+
116
+ // Or return a full LLMResponse object
117
+ const llm = mockLLM()
118
+ .onAnyComplete().respondWith({
119
+ content: 'hello',
120
+ model: 'gpt-4',
121
+ usage: { promptTokens: 10, completionTokens: 5 },
122
+ });
123
+ ```
124
+
125
+ #### Streaming
126
+
127
+ ```typescript
128
+ const llm = mockLLM().streaming(['chunk1', ' ', 'chunk2']);
129
+
130
+ for await (const chunk of llm.stream('test')) {
131
+ console.log(chunk); // 'chunk1', ' ', 'chunk2'
132
+ }
133
+ ```
134
+
135
+ #### Error simulation
136
+
137
+ ```typescript
138
+ const llm = mockLLM().failing(new Error('rate limit exceeded'));
139
+
140
+ await llm.complete('test'); // throws Error('rate limit exceeded')
141
+ ```
142
+
143
+ #### Tool calling
144
+
145
+ ```typescript
146
+ const llm = mockLLM().withToolCalls([
147
+ { id: 'call-1', name: 'search', input: { query: 'test' } },
148
+ ]);
149
+
150
+ const res = await llm.chatWithTools!(messages, { tools });
151
+ expect(res.toolCalls).toHaveLength(1);
152
+ expect(res.toolCalls![0].name).toBe('search');
153
+ ```
154
+
155
+ #### Call tracking
156
+
157
+ ```typescript
158
+ const llm = mockLLM();
159
+ await llm.complete('hello');
160
+ await llm.complete('world');
161
+
162
+ // vi.fn() assertions
163
+ expect(llm.complete).toHaveBeenCalledTimes(2);
164
+ expect(llm.complete).toHaveBeenCalledWith('hello');
165
+
166
+ // Structured call history
167
+ expect(llm.calls).toHaveLength(2);
168
+ expect(llm.calls[0].prompt).toBe('hello');
169
+ expect(llm.calls[0].response.content).toBe('mock response');
170
+ expect(llm.lastCall?.prompt).toBe('world');
171
+
172
+ // Tool call history
173
+ expect(llm.toolCalls).toHaveLength(0);
174
+
175
+ // Reset everything
176
+ llm.resetCalls();
177
+ expect(llm.calls).toHaveLength(0);
178
+ expect(llm.complete).not.toHaveBeenCalled();
179
+ ```
180
+
181
+ ### `mockCache()`
182
+
183
+ Working in-memory cache with TTL support and sorted sets. All methods are `vi.fn()` spies.
184
+
185
+ ```typescript
186
+ const cache = mockCache();
187
+
188
+ await cache.set('key', { data: 123 }, 5000); // TTL: 5 seconds
189
+ const val = await cache.get('key');
190
+ expect(val).toEqual({ data: 123 });
191
+
192
+ // Sorted sets
193
+ await cache.zadd('scores', 100, 'alice');
194
+ await cache.zadd('scores', 200, 'bob');
195
+ const top = await cache.zrangebyscore('scores', 0, 150);
196
+ expect(top).toEqual(['alice']);
197
+
198
+ // Spy assertions
199
+ expect(cache.set).toHaveBeenCalledWith('key', { data: 123 }, 5000);
200
+
201
+ // Direct state access
202
+ expect(cache.data.size).toBe(1);
203
+
204
+ // Reset
205
+ cache.reset();
206
+ expect(cache.data.size).toBe(0);
207
+ ```
208
+
209
+ ### `mockStorage()`
210
+
211
+ Virtual filesystem backed by `Map<string, Buffer>`.
212
+
213
+ ```typescript
214
+ // Initialize with files
215
+ const storage = mockStorage({
216
+ 'config.json': '{"key": "value"}',
217
+ 'data.bin': Buffer.from([0x01, 0x02]),
218
+ });
219
+
220
+ const content = await storage.read('config.json');
221
+ expect(content).toBe('{"key": "value"}');
222
+
223
+ await storage.write('new.txt', 'hello');
224
+ expect(await storage.exists('new.txt')).toBe(true);
225
+
226
+ const files = await storage.list('/');
227
+ expect(files).toContain('new.txt');
228
+
229
+ // Direct state access
230
+ expect(storage.files.size).toBe(3);
231
+ ```
232
+
233
+ ### `mockLogger()`
234
+
235
+ Logger with message recording and `vi.fn()` spies.
236
+
237
+ ```typescript
238
+ const logger = mockLogger();
239
+
240
+ logger.info('Server started', { port: 3000 });
241
+ logger.error('Connection failed', new Error('timeout'));
242
+ logger.warn('Deprecated API');
243
+
244
+ // Message history
245
+ expect(logger.messages).toHaveLength(3);
246
+ expect(logger.messages[0]).toEqual({
247
+ level: 'info',
248
+ message: 'Server started',
249
+ meta: { port: 3000 },
250
+ });
251
+
252
+ // Spy assertions
253
+ expect(logger.info).toHaveBeenCalledWith('Server started', { port: 3000 });
254
+ expect(logger.error).toHaveBeenCalledWith('Connection failed', expect.any(Error));
255
+
256
+ // Child loggers share the same messages array
257
+ const child = logger.child({ service: 'db' });
258
+ child.info('Connected');
259
+ expect(logger.messages).toHaveLength(4);
260
+ ```
261
+
262
+ ### `testCommand(handler, options)`
263
+
264
+ Single-function test runner for plugin command handlers. No boilerplate — import your handler, call `testCommand()`, assert on result.
265
+
266
+ Works with any handler type: `CommandHandlerV3` (from `defineCommand`), `RouteHandler` (from `defineRoute`), `Handler` (from `defineHandler`), or any object with `execute(ctx, input)`.
267
+
268
+ #### CLI command
269
+
270
+ ```typescript
271
+ import { testCommand } from '@kb-labs/sdk/testing';
272
+ import handler from '../src/commands/greet.js';
273
+
274
+ it('greets the user', async () => {
275
+ const result = await testCommand(handler, {
276
+ flags: { name: 'Alice' },
277
+ });
278
+
279
+ expect(result.exitCode).toBe(0);
280
+ expect(result.result).toEqual({ message: 'Hello, Alice!' });
281
+ expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');
282
+ result.cleanup();
283
+ });
284
+ ```
285
+
286
+ #### With LLM mock
287
+
288
+ ```typescript
289
+ import { testCommand, mockLLM } from '@kb-labs/sdk/testing';
290
+ import handler from '../src/commands/analyze.js';
291
+
292
+ it('analyzes file with LLM', async () => {
293
+ const llm = mockLLM().onAnyComplete().respondWith('looks good');
294
+
295
+ const result = await testCommand(handler, {
296
+ flags: { file: 'index.ts' },
297
+ platform: { llm },
298
+ });
299
+
300
+ expect(result.exitCode).toBe(0);
301
+ expect(llm.complete).toHaveBeenCalled();
302
+ result.cleanup();
303
+ });
304
+ ```
305
+
306
+ #### REST handler
307
+
308
+ ```typescript
309
+ import { testCommand } from '@kb-labs/sdk/testing';
310
+ import handler from '../src/routes/create-plan.js';
311
+
312
+ it('creates a plan', async () => {
313
+ const result = await testCommand(handler, {
314
+ host: 'rest',
315
+ body: { name: 'v2.0' },
316
+ query: { workspace: 'root' },
317
+ });
318
+
319
+ expect(result.result).toMatchObject({ name: 'v2.0' });
320
+ result.cleanup();
321
+ });
322
+ ```
323
+
324
+ #### Raw input (custom shape)
325
+
326
+ ```typescript
327
+ const result = await testCommand(handler, {
328
+ input: { custom: 'data', items: [1, 2, 3] },
329
+ });
330
+ ```
331
+
332
+ **Options:**
333
+
334
+ | Option | Type | Default | Description |
335
+ |--------|------|---------|-------------|
336
+ | `flags` | `Record<string, unknown>` | `{}` | CLI flags (builds `{ flags, argv }` input) |
337
+ | `argv` | `string[]` | `[]` | CLI positional arguments |
338
+ | `query` | `Record<string, unknown>` | — | REST query params (builds `{ query, body, params }` input) |
339
+ | `body` | `unknown` | — | REST request body |
340
+ | `params` | `Record<string, unknown>` | — | REST route params |
341
+ | `input` | `unknown` | — | Raw input (overrides flags/query/body) |
342
+ | `host` | `'cli' \| 'rest' \| 'workflow' \| 'webhook'` | `'cli'` | Host type |
343
+ | `config` | `TConfig` | `undefined` | Plugin config (`ctx.config`) |
344
+ | `cwd` | `string` | `process.cwd()` | Working directory |
345
+ | `tenantId` | `string` | `undefined` | Tenant identifier |
346
+ | `signal` | `AbortSignal` | `undefined` | Abort signal |
347
+ | `platform` | `Partial<PlatformServices>` | all mocks | Override platform services |
348
+ | `ui` | `Partial<UIFacade>` | all vi.fn() | Override UI methods |
349
+ | `syncSingleton` | `boolean` | `true` | Sync to global singleton |
350
+
351
+ **Result (`TestCommandResult`):**
352
+
353
+ | Field | Type | Description |
354
+ |-------|------|-------------|
355
+ | `exitCode` | `number` | Exit code (0 = success), extracted from `CommandResult` or 0 for raw data |
356
+ | `result` | `TResult \| undefined` | Data from `CommandResult.result` or raw return value |
357
+ | `meta` | `Record<string, unknown> \| undefined` | Custom metadata from `CommandResult.meta` |
358
+ | `raw` | `unknown` | Unprocessed return value from `handler.execute()` |
359
+ | `ui` | `UIFacade` | UI facade with `vi.fn()` spies on all methods |
360
+ | `ctx` | `PluginContextV3` | Full context passed to the handler |
361
+ | `cleanup` | `() => void` | Call in `afterEach()` to reset global singleton |
362
+
363
+ ### `createTestContext(options)`
364
+
365
+ Enhanced test context factory. Replaces the legacy SDK `createTestContext()`.
366
+
367
+ ```typescript
368
+ const { ctx, cleanup } = createTestContext({
369
+ pluginId: 'my-plugin',
370
+ host: 'cli',
371
+ config: { apiKey: 'test' },
372
+ platform: {
373
+ llm: mockLLM().onAnyComplete().respondWith('ok'),
374
+ cache: mockCache(),
375
+ },
376
+ });
377
+
378
+ // ctx.platform has the mocks
379
+ await ctx.platform.llm.complete('test');
380
+
381
+ // Global singleton also has them (syncSingleton defaults to true)
382
+ const llm = useLLM();
383
+ expect(llm).toBe(ctx.platform.llm);
384
+
385
+ // UI methods are vi.fn() spies too
386
+ ctx.ui.info('hello');
387
+ expect(ctx.ui.info).toHaveBeenCalledWith('hello');
388
+
389
+ // Runtime, API, trace — all mocked
390
+ await ctx.runtime.fs.readFile('test.txt');
391
+ expect(ctx.runtime.fs.readFile).toHaveBeenCalled();
392
+
393
+ // ALWAYS call cleanup in afterEach()
394
+ cleanup();
395
+ ```
396
+
397
+ **Options:**
398
+
399
+ | Option | Type | Default | Description |
400
+ |--------|------|---------|-------------|
401
+ | `pluginId` | `string` | `'test-plugin'` | Plugin identifier |
402
+ | `pluginVersion` | `string` | `'0.0.0'` | Plugin version |
403
+ | `host` | `'cli' \| 'rest' \| 'workflow' \| 'webhook'` | `'cli'` | Host type |
404
+ | `hostContext` | `HostContext` | auto-generated | Override host context |
405
+ | `config` | `unknown` | `undefined` | Plugin config |
406
+ | `cwd` | `string` | `process.cwd()` | Working directory |
407
+ | `outdir` | `string` | `{cwd}/.kb/output` | Output directory |
408
+ | `tenantId` | `string` | `undefined` | Tenant identifier |
409
+ | `signal` | `AbortSignal` | `undefined` | Abort signal |
410
+ | `platform` | `Partial<PlatformServices>` | all mocks | Override platform adapters |
411
+ | `ui` | `Partial<UIFacade>` | all vi.fn() | Override UI methods |
412
+ | `syncSingleton` | `boolean` | `true` | Sync to global singleton |
413
+
414
+ ## Architecture
415
+
416
+ ```
417
+ @kb-labs/shared-testing
418
+ ├── setup-platform.ts ← resetPlatform() + setAdapter() for each mock
419
+ ├── mock-llm.ts ← Proxy-based builder + ILLM instance
420
+ ├── mock-cache.ts ← In-memory Map with TTL + sorted sets
421
+ ├── mock-storage.ts ← Virtual FS on Map<string, Buffer>
422
+ ├── mock-logger.ts ← Message recording + child logger support
423
+ ├── create-test-context.ts ← PluginContextV3 factory + singleton sync
424
+ ├── test-command.ts ← testCommand() — single-function handler runner
425
+ └── index.ts ← Barrel exports
426
+
427
+ @kb-labs/sdk/testing ← Thin re-export layer
428
+ ```
429
+
430
+ The key insight: `setupTestPlatform()` calls `resetPlatform()` (clears the global singleton) then `platform.setAdapter()` for each provided mock. This ensures `useLLM()`, `useCache()`, etc. return the test mocks. `createTestContext()` calls `setupTestPlatform()` automatically when `syncSingleton: true` (the default).