@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,403 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { combine, combinePresets } from '../combine';
3
+ import { minimal, gitWorkflow, npmPublish, kbPlatform } from '../presets';
4
+ import type { PermissionPreset } from '../types';
5
+
6
+ describe('combine()', () => {
7
+ it('should return empty spec when nothing added', () => {
8
+ const result = combine().build();
9
+ expect(result).toEqual({});
10
+ });
11
+
12
+ it('should add a single preset', () => {
13
+ const result = combine().with(minimal).build();
14
+ expect(result.env?.read).toContain('PATH');
15
+ expect(result.env?.read).toContain('NODE_ENV');
16
+ });
17
+
18
+ it('should merge two presets', () => {
19
+ const result = combine()
20
+ .with(minimal)
21
+ .with(gitWorkflow)
22
+ .build();
23
+
24
+ const envVars = result.env?.read ?? [];
25
+ // From minimal
26
+ expect(envVars).toContain('PATH');
27
+ expect(envVars).toContain('NODE_ENV');
28
+ // From gitWorkflow
29
+ expect(envVars).toContain('HOME');
30
+ expect(envVars).toContain('USER');
31
+ expect(envVars).toContain('GIT_*');
32
+ });
33
+
34
+ it('should deduplicate env vars', () => {
35
+ const result = combine()
36
+ .with(gitWorkflow)
37
+ .with(npmPublish)
38
+ .build();
39
+
40
+ const envVars = result.env?.read ?? [];
41
+ // Both have HOME, USER, PATH - should appear once
42
+ const homeCount = envVars.filter(v => v === 'HOME').length;
43
+ expect(homeCount).toBe(1);
44
+ });
45
+
46
+ it('should merge fs.allow arrays and convert to runtime format', () => {
47
+ const result = combine()
48
+ .with(gitWorkflow)
49
+ .with(npmPublish)
50
+ .build();
51
+
52
+ // After build(), format is runtime (read/write arrays)
53
+ const read = result.fs?.read ?? [];
54
+ // From gitWorkflow
55
+ expect(read).toContain('**/.git/**');
56
+ // From npmPublish
57
+ expect(read).toContain('**/package.json');
58
+ });
59
+
60
+ it('should merge network domains', () => {
61
+ const result = combine()
62
+ .with(npmPublish)
63
+ .withNetwork({ fetch: ['api.example.com'] })
64
+ .build();
65
+
66
+ const domains = result.network?.fetch ?? [];
67
+ expect(domains).toContain('registry.npmjs.org');
68
+ expect(domains).toContain('api.example.com');
69
+ });
70
+
71
+ describe('withEnv()', () => {
72
+ it('should add custom env vars', () => {
73
+ const result = combine()
74
+ .with(minimal)
75
+ .withEnv(['MY_CUSTOM_VAR', 'ANOTHER_VAR'])
76
+ .build();
77
+
78
+ const envVars = result.env?.read ?? [];
79
+ expect(envVars).toContain('MY_CUSTOM_VAR');
80
+ expect(envVars).toContain('ANOTHER_VAR');
81
+ expect(envVars).toContain('PATH');
82
+ });
83
+ });
84
+
85
+ describe('withFs()', () => {
86
+ it('should add fs permissions and convert to runtime format', () => {
87
+ const result = combine()
88
+ .with(minimal)
89
+ .withFs({ mode: 'readWrite', allow: ['./data/**'] })
90
+ .build();
91
+
92
+ // After build(), format is runtime (read/write arrays)
93
+ expect(result.fs?.read).toContain('./data/**');
94
+ expect(result.fs?.write).toContain('./data/**');
95
+ });
96
+
97
+ it('should merge with existing fs', () => {
98
+ const result = combine()
99
+ .with(gitWorkflow)
100
+ .withFs({ allow: ['./custom/**'] })
101
+ .build();
102
+
103
+ const read = result.fs?.read ?? [];
104
+ expect(read).toContain('**/.git/**');
105
+ expect(read).toContain('./custom/**');
106
+ });
107
+ });
108
+
109
+ describe('withQuotas()', () => {
110
+ it('should add quotas', () => {
111
+ const result = combine()
112
+ .with(minimal)
113
+ .withQuotas({ timeoutMs: 30000, memoryMb: 512 })
114
+ .build();
115
+
116
+ expect(result.quotas?.timeoutMs).toBe(30000);
117
+ expect(result.quotas?.memoryMb).toBe(512);
118
+ });
119
+
120
+ it('should override quotas (last wins)', () => {
121
+ const result = combine()
122
+ .withQuotas({ timeoutMs: 10000 })
123
+ .withQuotas({ timeoutMs: 30000 })
124
+ .build();
125
+
126
+ expect(result.quotas?.timeoutMs).toBe(30000);
127
+ });
128
+ });
129
+
130
+ describe('withPlatform()', () => {
131
+ it('should add platform permissions with booleans', () => {
132
+ const result = combine()
133
+ .with(minimal)
134
+ .withPlatform({
135
+ llm: true,
136
+ cache: true,
137
+ analytics: true,
138
+ })
139
+ .build();
140
+
141
+ expect(result.platform?.llm).toBe(true);
142
+ expect(result.platform?.cache).toBe(true);
143
+ expect(result.platform?.analytics).toBe(true);
144
+ });
145
+
146
+ it('should add platform permissions with arrays', () => {
147
+ const result = combine()
148
+ .with(minimal)
149
+ .withPlatform({
150
+ cache: ['git-status:', 'workflow:'],
151
+ storage: ['uploads/', 'temp/'],
152
+ })
153
+ .build();
154
+
155
+ expect(result.platform?.cache).toEqual(['git-status:', 'workflow:']);
156
+ expect(result.platform?.storage).toEqual(['uploads/', 'temp/']);
157
+ });
158
+
159
+ it('should add platform permissions with objects', () => {
160
+ const result = combine()
161
+ .with(minimal)
162
+ .withPlatform({
163
+ llm: { models: ['gpt-4', 'claude-3'] },
164
+ vectorStore: { collections: ['docs', 'code'] },
165
+ eventBus: { publish: ['events.*'], subscribe: ['jobs.*'] },
166
+ })
167
+ .build();
168
+
169
+ expect(result.platform?.llm).toEqual({ models: ['gpt-4', 'claude-3'] });
170
+ expect(result.platform?.vectorStore).toEqual({ collections: ['docs', 'code'] });
171
+ expect(result.platform?.eventBus).toEqual({ publish: ['events.*'], subscribe: ['jobs.*'] });
172
+ });
173
+
174
+ it('should merge platform arrays (union)', () => {
175
+ const result = combine()
176
+ .withPlatform({
177
+ cache: ['git-status:'],
178
+ storage: ['uploads/'],
179
+ })
180
+ .withPlatform({
181
+ cache: ['workflow:'],
182
+ storage: ['temp/'],
183
+ })
184
+ .build();
185
+
186
+ // Arrays should be merged (union)
187
+ expect(result.platform?.cache).toEqual(['git-status:', 'workflow:']);
188
+ expect(result.platform?.storage).toEqual(['uploads/', 'temp/']);
189
+ });
190
+
191
+ it('should merge platform objects (spread)', () => {
192
+ const result = combine()
193
+ .withPlatform({
194
+ llm: { models: ['gpt-4'] },
195
+ vectorStore: { collections: ['docs'] },
196
+ })
197
+ .withPlatform({
198
+ llm: { models: ['claude-3'] },
199
+ vectorStore: { collections: ['code'] },
200
+ })
201
+ .build();
202
+
203
+ // Objects should be merged (last wins for same keys)
204
+ expect(result.platform?.llm).toEqual({ models: ['claude-3'] });
205
+ expect(result.platform?.vectorStore).toEqual({ collections: ['code'] });
206
+ });
207
+
208
+ it('should override booleans (last wins)', () => {
209
+ const result = combine()
210
+ .withPlatform({ analytics: false })
211
+ .withPlatform({ analytics: true })
212
+ .build();
213
+
214
+ expect(result.platform?.analytics).toBe(true);
215
+ });
216
+
217
+ it('should merge with existing platform permissions from presets', () => {
218
+ const presetWithPlatform: PermissionPreset = {
219
+ id: 'test-preset',
220
+ description: 'Test preset with platform',
221
+ permissions: {
222
+ platform: {
223
+ llm: true,
224
+ cache: ['preset:'],
225
+ },
226
+ },
227
+ };
228
+
229
+ const result = combine()
230
+ .with(presetWithPlatform)
231
+ .withPlatform({
232
+ cache: ['custom:'],
233
+ analytics: true,
234
+ })
235
+ .build();
236
+
237
+ expect(result.platform?.llm).toBe(true);
238
+ expect(result.platform?.cache).toEqual(['preset:', 'custom:']);
239
+ expect(result.platform?.analytics).toBe(true);
240
+ });
241
+
242
+ it('should deduplicate array values in platform permissions', () => {
243
+ const result = combine()
244
+ .withPlatform({
245
+ cache: ['git-status:', 'workflow:'],
246
+ })
247
+ .withPlatform({
248
+ cache: ['git-status:', 'cache:'], // git-status: is duplicate
249
+ })
250
+ .build();
251
+
252
+ // Should deduplicate git-status:
253
+ const cache = result.platform?.cache as string[];
254
+ expect(cache).toHaveLength(3);
255
+ expect(cache).toContain('git-status:');
256
+ expect(cache).toContain('workflow:');
257
+ expect(cache).toContain('cache:');
258
+ });
259
+
260
+ it('should handle undefined platform gracefully', () => {
261
+ const result = combine()
262
+ .with(minimal)
263
+ .withPlatform(undefined as any)
264
+ .build();
265
+
266
+ expect(result.platform).toBeUndefined();
267
+ });
268
+
269
+ it('should pass through platform to runtime format unchanged', () => {
270
+ const result = combine()
271
+ .withPlatform({
272
+ llm: { models: ['gpt-4'] },
273
+ cache: ['git:'],
274
+ analytics: true,
275
+ })
276
+ .build();
277
+
278
+ // Platform permissions should pass through to runtime format unchanged
279
+ expect(result.platform?.llm).toEqual({ models: ['gpt-4'] });
280
+ expect(result.platform?.cache).toEqual(['git:']);
281
+ expect(result.platform?.analytics).toBe(true);
282
+ });
283
+ });
284
+
285
+ describe('with raw PermissionSpec', () => {
286
+ it('should accept raw spec without id/description', () => {
287
+ const result = combine()
288
+ .with({ env: { read: ['CUSTOM_VAR'] } })
289
+ .build();
290
+
291
+ expect(result.env?.read).toContain('CUSTOM_VAR');
292
+ });
293
+ });
294
+ });
295
+
296
+ describe('combinePresets()', () => {
297
+ it('should combine multiple presets at once', () => {
298
+ const result = combinePresets(minimal, gitWorkflow, kbPlatform);
299
+
300
+ const envVars = result.env?.read ?? [];
301
+ expect(envVars).toContain('PATH');
302
+ expect(envVars).toContain('HOME');
303
+ expect(envVars).toContain('KB_*');
304
+ });
305
+
306
+ it('should work with single preset', () => {
307
+ const result = combinePresets(gitWorkflow);
308
+ expect(result.env?.read).toContain('HOME');
309
+ });
310
+
311
+ it('should return empty spec with no presets', () => {
312
+ const result = combinePresets();
313
+ expect(result).toEqual({});
314
+ });
315
+ });
316
+
317
+ describe('Real-world scenarios', () => {
318
+ it('should create commit-plugin permissions (git + kb-platform)', () => {
319
+ const result = combine()
320
+ .with(gitWorkflow)
321
+ .with(kbPlatform)
322
+ .withEnv(['OPENAI_API_KEY'])
323
+ .build();
324
+
325
+ const envVars = result.env?.read ?? [];
326
+ // Git needs
327
+ expect(envVars).toContain('HOME');
328
+ expect(envVars).toContain('USER');
329
+ expect(envVars).toContain('GIT_*');
330
+ // KB platform needs
331
+ expect(envVars).toContain('KB_*');
332
+ // Custom
333
+ expect(envVars).toContain('OPENAI_API_KEY');
334
+ });
335
+
336
+ it('should create release-plugin permissions (git + npm + kb-platform)', () => {
337
+ const result = combine()
338
+ .with(gitWorkflow)
339
+ .with(npmPublish)
340
+ .with(kbPlatform)
341
+ .withQuotas({ timeoutMs: 300000 })
342
+ .build();
343
+
344
+ const envVars = result.env?.read ?? [];
345
+ expect(envVars).toContain('HOME');
346
+ expect(envVars).toContain('GIT_*');
347
+ expect(envVars).toContain('NPM_TOKEN');
348
+ expect(envVars).toContain('KB_*');
349
+
350
+ const domains = result.network?.fetch ?? [];
351
+ expect(domains).toContain('registry.npmjs.org');
352
+
353
+ expect(result.quotas?.timeoutMs).toBe(300000);
354
+ });
355
+
356
+ it('should create commit-plugin with platform permissions (git + kb + llm + cache)', () => {
357
+ const result = combine()
358
+ .with(gitWorkflow)
359
+ .with(kbPlatform)
360
+ .withEnv(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'])
361
+ .withFs({
362
+ mode: 'readWrite',
363
+ allow: ['.kb/commit/**'],
364
+ })
365
+ .withPlatform({
366
+ llm: true,
367
+ cache: ['git-status:'],
368
+ analytics: true,
369
+ })
370
+ .withQuotas({
371
+ timeoutMs: 600000,
372
+ memoryMb: 512,
373
+ })
374
+ .build();
375
+
376
+ // Env vars
377
+ const envVars = result.env?.read ?? [];
378
+ expect(envVars).toContain('HOME');
379
+ expect(envVars).toContain('USER');
380
+ expect(envVars).toContain('GIT_*');
381
+ expect(envVars).toContain('KB_*');
382
+ expect(envVars).toContain('OPENAI_API_KEY');
383
+ expect(envVars).toContain('ANTHROPIC_API_KEY');
384
+
385
+ // File system (runtime format)
386
+ const read = result.fs?.read ?? [];
387
+ const write = result.fs?.write ?? [];
388
+ expect(read).toContain('**/.git/**');
389
+ expect(read).toContain('.kb/**');
390
+ expect(read).toContain('.kb/commit/**');
391
+ expect(write).toContain('.kb/**');
392
+ expect(write).toContain('.kb/commit/**');
393
+
394
+ // Platform services
395
+ expect(result.platform?.llm).toBe(true);
396
+ expect(result.platform?.cache).toEqual(['git-status:']);
397
+ expect(result.platform?.analytics).toBe(true);
398
+
399
+ // Quotas
400
+ expect(result.quotas?.timeoutMs).toBe(600000);
401
+ expect(result.quotas?.memoryMb).toBe(512);
402
+ });
403
+ });
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ minimal,
4
+ gitWorkflow,
5
+ npmPublish,
6
+ fullEnv,
7
+ kbPlatform,
8
+ llmAccess,
9
+ vectorStore,
10
+ ciEnvironment,
11
+ } from '../presets';
12
+
13
+ describe('Permission Presets', () => {
14
+ describe('minimal', () => {
15
+ it('should have id and description', () => {
16
+ expect(minimal.id).toBe('minimal');
17
+ expect(minimal.description).toBeTruthy();
18
+ });
19
+
20
+ it('should include basic system env vars', () => {
21
+ const envVars = minimal.permissions.env?.read ?? [];
22
+ expect(envVars).toContain('PATH');
23
+ expect(envVars).toContain('NODE_ENV');
24
+ expect(envVars).toContain('LANG');
25
+ });
26
+
27
+ it('should NOT include HOME or USER', () => {
28
+ const envVars = minimal.permissions.env?.read ?? [];
29
+ expect(envVars).not.toContain('HOME');
30
+ expect(envVars).not.toContain('USER');
31
+ });
32
+ });
33
+
34
+ describe('gitWorkflow', () => {
35
+ it('should have id and description', () => {
36
+ expect(gitWorkflow.id).toBe('git-workflow');
37
+ expect(gitWorkflow.description).toBeTruthy();
38
+ });
39
+
40
+ it('should include HOME for git config', () => {
41
+ const envVars = gitWorkflow.permissions.env?.read ?? [];
42
+ expect(envVars).toContain('HOME');
43
+ });
44
+
45
+ it('should include USER for git author', () => {
46
+ const envVars = gitWorkflow.permissions.env?.read ?? [];
47
+ expect(envVars).toContain('USER');
48
+ });
49
+
50
+ it('should include GIT_* wildcard', () => {
51
+ const envVars = gitWorkflow.permissions.env?.read ?? [];
52
+ expect(envVars).toContain('GIT_*');
53
+ });
54
+
55
+ it('should include SSH env vars for auth', () => {
56
+ const envVars = gitWorkflow.permissions.env?.read ?? [];
57
+ expect(envVars).toContain('SSH_AUTH_SOCK');
58
+ expect(envVars).toContain('SSH_AGENT_PID');
59
+ });
60
+
61
+ it('should allow .git directory access', () => {
62
+ const allow = gitWorkflow.permissions.fs?.allow ?? [];
63
+ expect(allow).toContain('**/.git/**');
64
+ });
65
+ });
66
+
67
+ describe('npmPublish', () => {
68
+ it('should have id and description', () => {
69
+ expect(npmPublish.id).toBe('npm-publish');
70
+ expect(npmPublish.description).toBeTruthy();
71
+ });
72
+
73
+ it('should include HOME for .npmrc', () => {
74
+ const envVars = npmPublish.permissions.env?.read ?? [];
75
+ expect(envVars).toContain('HOME');
76
+ });
77
+
78
+ it('should include npm token vars', () => {
79
+ const envVars = npmPublish.permissions.env?.read ?? [];
80
+ expect(envVars).toContain('NPM_TOKEN');
81
+ expect(envVars).toContain('NPM_AUTH_TOKEN');
82
+ expect(envVars).toContain('NODE_AUTH_TOKEN');
83
+ });
84
+
85
+ it('should include npm_* wildcard', () => {
86
+ const envVars = npmPublish.permissions.env?.read ?? [];
87
+ expect(envVars).toContain('npm_*');
88
+ });
89
+
90
+ it('should allow package.json and lock files', () => {
91
+ const allow = npmPublish.permissions.fs?.allow ?? [];
92
+ expect(allow).toContain('**/package.json');
93
+ expect(allow).toContain('**/package-lock.json');
94
+ expect(allow).toContain('**/pnpm-lock.yaml');
95
+ });
96
+
97
+ it('should allow npm registry domains', () => {
98
+ const domains = npmPublish.permissions.network?.fetch ?? [];
99
+ expect(domains).toContain('registry.npmjs.org');
100
+ expect(domains).toContain('npm.pkg.github.com');
101
+ });
102
+ });
103
+
104
+ describe('fullEnv', () => {
105
+ it('should have id and description', () => {
106
+ expect(fullEnv.id).toBe('full-env');
107
+ expect(fullEnv.description).toBeTruthy();
108
+ });
109
+
110
+ it('should include wildcard for all env vars', () => {
111
+ const envVars = fullEnv.permissions.env?.read ?? [];
112
+ expect(envVars).toContain('*');
113
+ });
114
+ });
115
+
116
+ describe('kbPlatform', () => {
117
+ it('should have id and description', () => {
118
+ expect(kbPlatform.id).toBe('kb-platform');
119
+ expect(kbPlatform.description).toBeTruthy();
120
+ });
121
+
122
+ it('should include KB_* wildcard', () => {
123
+ const envVars = kbPlatform.permissions.env?.read ?? [];
124
+ expect(envVars).toContain('KB_*');
125
+ });
126
+
127
+ it('should allow .kb directory access', () => {
128
+ const allow = kbPlatform.permissions.fs?.allow ?? [];
129
+ expect(allow).toContain('.kb/**');
130
+ });
131
+ });
132
+
133
+ describe('llmAccess', () => {
134
+ it('should have id and description', () => {
135
+ expect(llmAccess.id).toBe('llm-access');
136
+ expect(llmAccess.description).toBeTruthy();
137
+ });
138
+
139
+ it('should include OpenAI env vars', () => {
140
+ const envVars = llmAccess.permissions.env?.read ?? [];
141
+ expect(envVars).toContain('OPENAI_API_KEY');
142
+ expect(envVars).toContain('OPENAI_ORG_ID');
143
+ });
144
+
145
+ it('should include Anthropic env vars', () => {
146
+ const envVars = llmAccess.permissions.env?.read ?? [];
147
+ expect(envVars).toContain('ANTHROPIC_API_KEY');
148
+ });
149
+
150
+ it('should allow LLM API domains', () => {
151
+ const domains = llmAccess.permissions.network?.fetch ?? [];
152
+ expect(domains).toContain('api.openai.com');
153
+ expect(domains).toContain('api.anthropic.com');
154
+ });
155
+ });
156
+
157
+ describe('vectorStore', () => {
158
+ it('should have id and description', () => {
159
+ expect(vectorStore.id).toBe('vector-store');
160
+ expect(vectorStore.description).toBeTruthy();
161
+ });
162
+
163
+ it('should include Qdrant env vars', () => {
164
+ const envVars = vectorStore.permissions.env?.read ?? [];
165
+ expect(envVars).toContain('QDRANT_URL');
166
+ expect(envVars).toContain('QDRANT_API_KEY');
167
+ });
168
+
169
+ it('should include Pinecone env vars', () => {
170
+ const envVars = vectorStore.permissions.env?.read ?? [];
171
+ expect(envVars).toContain('PINECONE_API_KEY');
172
+ });
173
+
174
+ it('should allow vector store domains', () => {
175
+ const domains = vectorStore.permissions.network?.fetch ?? [];
176
+ expect(domains).toContain('localhost');
177
+ expect(domains).toContain('*.qdrant.io');
178
+ expect(domains).toContain('*.pinecone.io');
179
+ });
180
+ });
181
+
182
+ describe('ciEnvironment', () => {
183
+ it('should have id and description', () => {
184
+ expect(ciEnvironment.id).toBe('ci-environment');
185
+ expect(ciEnvironment.description).toBeTruthy();
186
+ });
187
+
188
+ it('should include CI indicator', () => {
189
+ const envVars = ciEnvironment.permissions.env?.read ?? [];
190
+ expect(envVars).toContain('CI');
191
+ });
192
+
193
+ it('should include GitHub Actions vars', () => {
194
+ const envVars = ciEnvironment.permissions.env?.read ?? [];
195
+ expect(envVars).toContain('GITHUB_TOKEN');
196
+ expect(envVars).toContain('GITHUB_*');
197
+ });
198
+
199
+ it('should include GitLab CI vars', () => {
200
+ const envVars = ciEnvironment.permissions.env?.read ?? [];
201
+ expect(envVars).toContain('GITLAB_*');
202
+ expect(envVars).toContain('CI_JOB_TOKEN');
203
+ });
204
+ });
205
+ });