@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,107 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import type { ServiceOperationSample } from '@kb-labs/core-contracts';
3
+
4
+ type StatusStats = {
5
+ count: number;
6
+ totalDurationMs: number;
7
+ maxDurationMs: number;
8
+ };
9
+
10
+ type OperationStats = {
11
+ totalCount: number;
12
+ totalDurationMs: number;
13
+ maxDurationMs: number;
14
+ errorCount: number;
15
+ byStatus: Record<OperationStatus, StatusStats>;
16
+ };
17
+
18
+ export type OperationStatus = 'ok' | 'error';
19
+
20
+ export interface OperationObserver {
21
+ recordOperation(operation: string, durationMs?: number, status?: OperationStatus, count?: number): void;
22
+ observeOperation<T>(operation: string, work: () => T | Promise<T>): Promise<T>;
23
+ }
24
+
25
+ function createEmptyStats(): OperationStats {
26
+ return {
27
+ totalCount: 0,
28
+ totalDurationMs: 0,
29
+ maxDurationMs: 0,
30
+ errorCount: 0,
31
+ byStatus: {
32
+ ok: { count: 0, totalDurationMs: 0, maxDurationMs: 0 },
33
+ error: { count: 0, totalDurationMs: 0, maxDurationMs: 0 },
34
+ },
35
+ };
36
+ }
37
+
38
+ export class OperationMetricsTracker implements OperationObserver {
39
+ private readonly stats = new Map<string, OperationStats>();
40
+
41
+ reset(): void {
42
+ this.stats.clear();
43
+ }
44
+
45
+ recordOperation(operation: string, durationMs = 0, status: OperationStatus = 'ok', count = 1): void {
46
+ if (typeof operation !== 'string' || operation.length === 0 || !Number.isFinite(count) || count <= 0) {
47
+ return;
48
+ }
49
+
50
+ const normalizedDuration = Number.isFinite(durationMs) ? Math.max(durationMs, 0) : 0;
51
+ const entry = this.stats.get(operation) ?? createEmptyStats();
52
+ entry.totalCount += count;
53
+ entry.totalDurationMs += normalizedDuration;
54
+ entry.maxDurationMs = Math.max(entry.maxDurationMs, normalizedDuration);
55
+ entry.byStatus[status].count += count;
56
+ entry.byStatus[status].totalDurationMs += normalizedDuration;
57
+ entry.byStatus[status].maxDurationMs = Math.max(entry.byStatus[status].maxDurationMs, normalizedDuration);
58
+ if (status === 'error') {
59
+ entry.errorCount += count;
60
+ }
61
+ this.stats.set(operation, entry);
62
+ }
63
+
64
+ async observeOperation<T>(operation: string, work: () => T | Promise<T>): Promise<T> {
65
+ const startedAt = performance.now();
66
+ try {
67
+ const result = await work();
68
+ this.recordOperation(operation, performance.now() - startedAt, 'ok');
69
+ return result;
70
+ } catch (error) {
71
+ this.recordOperation(operation, performance.now() - startedAt, 'error');
72
+ throw error;
73
+ }
74
+ }
75
+
76
+ getTopOperations(limit = 5): ServiceOperationSample[] {
77
+ return Array.from(this.stats.entries())
78
+ .sort((a, b) => b[1].totalCount - a[1].totalCount || b[1].maxDurationMs - a[1].maxDurationMs)
79
+ .slice(0, limit)
80
+ .map(([operation, stats]) => ({
81
+ operation,
82
+ count: stats.totalCount,
83
+ avgDurationMs: stats.totalCount > 0 ? stats.totalDurationMs / stats.totalCount : 0,
84
+ maxDurationMs: stats.maxDurationMs,
85
+ errorCount: stats.errorCount,
86
+ }));
87
+ }
88
+
89
+ getMetricLines(): string[] {
90
+ const lines: string[] = [];
91
+ for (const [operation, stats] of this.stats.entries()) {
92
+ for (const status of ['ok', 'error'] as const) {
93
+ const statusStats = stats.byStatus[status];
94
+ if (statusStats.count <= 0) {
95
+ continue;
96
+ }
97
+ lines.push(`service_operation_total{operation="${operation}",status="${status}"} ${statusStats.count}`);
98
+ lines.push(
99
+ `service_operation_duration_ms{operation="${operation}",status="${status}"} ${Number(
100
+ statusStats.totalDurationMs.toFixed(2),
101
+ )}`,
102
+ );
103
+ }
104
+ }
105
+ return lines;
106
+ }
107
+ }
@@ -0,0 +1,108 @@
1
+ import { jsonSchemaTransform } from 'fastify-type-provider-zod';
2
+
3
+ /** Minimal structural interface to accept any Fastify instance regardless of version. */
4
+ interface FastifyLike {
5
+ register(plugin: unknown, opts?: unknown): unknown;
6
+ get(path: string, handler: (req: unknown, reply: unknown) => unknown): void;
7
+ swagger?(): unknown;
8
+ }
9
+
10
+ export interface OpenAPIOptions {
11
+ title: string;
12
+ description?: string;
13
+ version?: string;
14
+ /** Route prefix for Swagger UI. Default: '/docs' */
15
+ docsPath?: string;
16
+ /** Route for the raw OpenAPI JSON spec. Default: '/openapi.json' */
17
+ specPath?: string;
18
+ servers?: Array<{ url: string; description?: string }>;
19
+ /**
20
+ * Set to false to skip Swagger UI registration (e.g. in production).
21
+ * The spec endpoint (/openapi.json) is still registered.
22
+ * Default: true
23
+ */
24
+ ui?: boolean;
25
+ }
26
+
27
+ /**
28
+ * Wraps jsonSchemaTransform to safely handle routes with plain JSON schemas.
29
+ * ftzp v6 only accepts Zod schemas — plain JSON objects cause InvalidSchemaError.
30
+ * Routes without `tags` are hidden anyway (hideUntagged), so we skip transform
31
+ * for them to avoid the error.
32
+ */
33
+ function safeJsonSchemaTransform(input: { schema: Record<string, unknown>; url: string; [k: string]: unknown }) {
34
+ const { schema } = input;
35
+
36
+ // No schema or explicitly hidden — pass through
37
+ if (!schema || schema.hide) {
38
+ return input;
39
+ }
40
+
41
+ // Routes without tags will be excluded by hideUntagged.
42
+ // Skip Zod transform for them to avoid errors on plain JSON schemas.
43
+ if (!schema.tags || (Array.isArray(schema.tags) && schema.tags.length === 0)) {
44
+ return { schema: { ...schema, hide: true }, url: input.url };
45
+ }
46
+
47
+ // Tagged routes — delegate to ftzp's jsonSchemaTransform (expects Zod schemas)
48
+ return jsonSchemaTransform(input as unknown as Parameters<typeof jsonSchemaTransform>[0]);
49
+ }
50
+
51
+ /**
52
+ * Registers @fastify/swagger + @fastify/swagger-ui on a Fastify instance.
53
+ *
54
+ * Call BEFORE registering routes, AFTER creating the Fastify instance.
55
+ *
56
+ * Routes without `tags:` are excluded from the spec (hideUntagged: true).
57
+ * Use this as the visibility toggle — internal/undocumented routes simply
58
+ * omit `tags:` and they won't appear in /docs or /openapi.json.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { registerOpenAPI } from '@kb-labs/shared-http';
63
+ *
64
+ * await registerOpenAPI(server, {
65
+ * title: 'My Service',
66
+ * version: '1.0.0',
67
+ * servers: [{ url: 'http://localhost:3000', description: 'Local dev' }],
68
+ * ui: process.env.NODE_ENV !== 'production',
69
+ * });
70
+ * ```
71
+ */
72
+ export async function registerOpenAPI(server: FastifyLike, options: OpenAPIOptions): Promise<void> {
73
+ const swagger = await import('@fastify/swagger');
74
+
75
+ await server.register(swagger.default ?? swagger, {
76
+ openapi: {
77
+ info: {
78
+ title: options.title,
79
+ description: options.description ?? '',
80
+ version: options.version ?? '1.0.0',
81
+ },
82
+ servers: options.servers ?? [],
83
+ },
84
+ transform: safeJsonSchemaTransform,
85
+ hideUntagged: true,
86
+ });
87
+
88
+ const specPath = options.specPath ?? '/openapi.json';
89
+
90
+ await server.register(async (scope: FastifyLike) => {
91
+ scope.get(specPath, (_req, reply) => {
92
+ const s = server as { swagger?: () => unknown };
93
+ return (reply as { send(v: unknown): unknown }).send(s.swagger?.());
94
+ });
95
+ });
96
+
97
+ if (options.ui !== false) {
98
+ const swaggerUi = await import('@fastify/swagger-ui');
99
+
100
+ await server.register(swaggerUi.default ?? swaggerUi, {
101
+ routePrefix: options.docsPath ?? '/docs',
102
+ uiConfig: {
103
+ docExpansion: 'list',
104
+ deepLinking: true,
105
+ },
106
+ });
107
+ }
108
+ }
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+
4
+ export interface ZodSchemaRef {
5
+ /** Reference in format "package-name#ExportedSchemaName" */
6
+ zod: string;
7
+ }
8
+
9
+ export interface JsonSchemaRef {
10
+ $ref: string;
11
+ }
12
+
13
+ export type SchemaRef = ZodSchemaRef | JsonSchemaRef;
14
+
15
+ /**
16
+ * Resolves a SchemaRef from a plugin manifest to a plain JSON Schema object.
17
+ *
18
+ * Supports two formats:
19
+ * - `{ $ref: "..." }` — returned as-is (already a JSON Schema reference)
20
+ * - `{ zod: "pkg#ExportName" }` — dynamically imports the package, grabs the
21
+ * named export, and converts the ZodType to JSON Schema via zod-to-json-schema
22
+ *
23
+ * Fail-open: if the module cannot be imported or the export is missing/not a
24
+ * ZodType, returns null and logs a warning. The route will still mount — it just
25
+ * won't have schema documentation.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const schema = await resolveSchemaRef({
30
+ * zod: '@kb-labs/commit-contracts#GenerateRequestSchema'
31
+ * });
32
+ * // → { type: 'object', properties: { ... }, required: [...] }
33
+ * ```
34
+ */
35
+ export async function resolveSchemaRef(ref: SchemaRef): Promise<Record<string, unknown> | null> {
36
+ if ('$ref' in ref) {
37
+ return { $ref: ref.$ref };
38
+ }
39
+
40
+ if ('zod' in ref) {
41
+ const hashIdx = ref.zod.lastIndexOf('#');
42
+ if (hashIdx === -1) {
43
+ console.warn(`[shared-http] resolveSchemaRef: invalid zod ref format "${ref.zod}" (expected "pkg#ExportName")`);
44
+ return null;
45
+ }
46
+
47
+ const modulePath = ref.zod.slice(0, hashIdx);
48
+ const exportName = ref.zod.slice(hashIdx + 1);
49
+
50
+ if (!modulePath || !exportName) {
51
+ console.warn(`[shared-http] resolveSchemaRef: empty module or export in "${ref.zod}"`);
52
+ return null;
53
+ }
54
+
55
+ try {
56
+ const mod = await import(modulePath);
57
+ const schema = mod[exportName];
58
+
59
+ if (!(schema instanceof z.ZodType)) {
60
+ console.warn(`[shared-http] resolveSchemaRef: "${ref.zod}" export is not a ZodType`);
61
+ return null;
62
+ }
63
+
64
+ // $refStrategy: 'none' inlines all nested schemas — avoids $ref chains
65
+ // that Fastify v4's JSON Schema validator may not resolve correctly.
66
+
67
+ return zodToJsonSchema(schema as any, { $refStrategy: 'none' }) as Record<string, unknown>;
68
+ } catch (err) {
69
+ console.warn(`[shared-http] resolveSchemaRef: failed to import "${modulePath}": ${(err as Error).message}`);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ return null;
75
+ }
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Standard error response schema.
5
+ * Use in route `response:` blocks for error status codes.
6
+ */
7
+ export const ErrorResponseSchema = z.object({
8
+ ok: z.literal(false),
9
+ error: z.string(),
10
+ code: z.string().optional(),
11
+ });
12
+
13
+ /**
14
+ * Wraps a data schema in a standard success envelope.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * response: {
19
+ * 200: OkResponseSchema(z.object({ jobs: z.array(JobSchema) }))
20
+ * }
21
+ * ```
22
+ */
23
+ export const OkResponseSchema = <T extends z.ZodTypeAny>(data: T) =>
24
+ z.object({
25
+ ok: z.literal(true),
26
+ data,
27
+ });
28
+
29
+ export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
@@ -0,0 +1,63 @@
1
+ import {
2
+ validateServiceObservabilityDescribe,
3
+ validateServiceObservabilityHealth,
4
+ } from '@kb-labs/core-contracts';
5
+
6
+ export interface VersionedObservabilityShape {
7
+ schema: string;
8
+ contractVersion: string;
9
+ }
10
+
11
+ export interface ServiceReadyResponse {
12
+ schema: 'kb.ready/1';
13
+ ts: string;
14
+ ready: boolean;
15
+ status: 'ready' | 'degraded' | 'initializing';
16
+ reason: string;
17
+ components: Record<string, unknown>;
18
+ }
19
+
20
+ /**
21
+ * Shared builder for versioned service describe payloads.
22
+ * Keeps services on a single construction path instead of ad hoc objects.
23
+ */
24
+ export function createServiceObservabilityDescribe<T extends VersionedObservabilityShape>(options: T): T {
25
+ const result = validateServiceObservabilityDescribe(options);
26
+ if (!result.ok) {
27
+ throw new Error(
28
+ `Invalid observability describe payload: ${result.issues.map((issue: { path: string; message: string }) => `${issue.path}: ${issue.message}`).join(', ')}`,
29
+ );
30
+ }
31
+ return options;
32
+ }
33
+
34
+ /**
35
+ * Shared builder for versioned service health payloads.
36
+ * Callers provide the contract-specific shape while keeping a common entry point.
37
+ */
38
+ export function createServiceObservabilityHealth<T extends VersionedObservabilityShape>(options: T): T {
39
+ const result = validateServiceObservabilityHealth(options);
40
+ if (!result.ok) {
41
+ throw new Error(
42
+ `Invalid observability health payload: ${result.issues.map((issue: { path: string; message: string }) => `${issue.path}: ${issue.message}`).join(', ')}`,
43
+ );
44
+ }
45
+ return options;
46
+ }
47
+
48
+ export function createServiceReadyResponse(options: {
49
+ ready: boolean;
50
+ status?: ServiceReadyResponse['status'];
51
+ reason?: string;
52
+ components?: Record<string, unknown>;
53
+ ts?: string;
54
+ }): ServiceReadyResponse {
55
+ return {
56
+ schema: 'kb.ready/1',
57
+ ts: options.ts ?? new Date().toISOString(),
58
+ ready: options.ready,
59
+ status: options.status ?? (options.ready ? 'ready' : 'initializing'),
60
+ reason: options.reason ?? (options.ready ? 'ready' : 'initializing'),
61
+ components: options.components ?? {},
62
+ };
63
+ }
@@ -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,23 @@
1
+ import { defineConfig } from 'tsup';
2
+ import nodePreset from '@kb-labs/devkit/tsup/node';
3
+
4
+ export default defineConfig({
5
+ ...nodePreset,
6
+ entry: {
7
+ index: 'src/index.ts',
8
+ },
9
+ tsconfig: 'tsconfig.build.json',
10
+ dts: {
11
+ resolve: true,
12
+ entry: {
13
+ index: 'src/index.ts',
14
+ },
15
+ },
16
+ external: [
17
+ /^@kb-labs\/.*/,
18
+ 'fastify',
19
+ '@fastify/swagger',
20
+ '@fastify/swagger-ui',
21
+ 'fastify-type-provider-zod',
22
+ ],
23
+ });
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import nodePreset from '@kb-labs/devkit/vitest/node.js';
3
+
4
+ export default defineConfig({
5
+ ...nodePreset,
6
+ test: {
7
+ ...nodePreset.test,
8
+ coverage: {
9
+ ...nodePreset.test?.coverage,
10
+ enabled: false,
11
+ },
12
+ },
13
+ });
@@ -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,78 @@
1
+ # @kb-labs/perm-presets
2
+
3
+ > Composable permission presets for KB Labs plugin manifests.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @kb-labs/perm-presets
9
+ ```
10
+
11
+ Or use via `@kb-labs/sdk` (already bundled):
12
+
13
+ ```typescript
14
+ import { minimalPreset, combinePermissions } from '@kb-labs/sdk';
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { combine, minimal, llmAccess, gitWorkflow } from '@kb-labs/perm-presets';
21
+ import { defineManifest } from '@kb-labs/sdk';
22
+
23
+ export default defineManifest({
24
+ schema: 'kb.plugin/3',
25
+ id: 'my-plugin',
26
+ version: '1.0.0',
27
+ permissions: combine(minimal, llmAccess, gitWorkflow),
28
+ });
29
+ ```
30
+
31
+ ## Available Presets
32
+
33
+ | Preset | Description |
34
+ |--------|-------------|
35
+ | `minimal` | Minimal permissions (read-only workspace) |
36
+ | `gitWorkflow` | Git read/write operations |
37
+ | `npmPublish` | npm publish permissions |
38
+ | `fullEnv` | Full environment variable access |
39
+ | `kbPlatform` | KB Labs platform API access |
40
+ | `llmAccess` | LLM API access |
41
+ | `vectorStore` | Vector store access |
42
+ | `ciEnvironment` | CI/CD environment permissions |
43
+
44
+ ## API
45
+
46
+ ### `combine(...presets)`
47
+
48
+ Merge multiple presets into a single `PermissionSpec`:
49
+
50
+ ```typescript
51
+ import { combine, minimal, llmAccess } from '@kb-labs/perm-presets';
52
+
53
+ const permissions = combine(minimal, llmAccess);
54
+ ```
55
+
56
+ ### `combinePresets(presets[])`
57
+
58
+ Same as `combine` but accepts an array:
59
+
60
+ ```typescript
61
+ import { combinePresets, presets } from '@kb-labs/perm-presets';
62
+
63
+ const permissions = combinePresets([presets.minimal, presets.llmAccess]);
64
+ ```
65
+
66
+ ### `presets` namespace
67
+
68
+ All presets accessible as a namespace:
69
+
70
+ ```typescript
71
+ import { presets, combine } from '@kb-labs/perm-presets';
72
+
73
+ const permissions = combine(presets.minimal, presets.kbPlatform);
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Standard ESLint configuration template
3
+ *
4
+ * This is the canonical template for all @kb-labs packages.
5
+ * DO NOT modify this file locally - it is synced from @kb-labs/devkit
6
+ *
7
+ * Customization guidelines:
8
+ * - DevKit preset already includes all standard ignores
9
+ * - Only add project-specific ignores if absolutely necessary
10
+ * - Document why custom ignores are needed
11
+ *
12
+ * @see https://github.com/kb-labs/devkit#eslint-configuration
13
+ */
14
+ import nodePreset from '@kb-labs/devkit/eslint/node.js';
15
+
16
+ export default [
17
+ ...nodePreset,
18
+
19
+ // OPTIONAL: Add project-specific ignores only if needed
20
+ // DevKit preset already ignores: dist/, coverage/, node_modules/, *.d.ts, scripts/, etc.
21
+ // {
22
+ // ignores: [
23
+ // // Add ONLY project-specific patterns here
24
+ // // Example: '**/*.generated.ts',
25
+ // ]
26
+ // }
27
+ ];
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@kb-labs/perm-presets",
3
+ "version": "1.1.0",
4
+ "description": "Composable permission presets for KB Labs plugins",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "clean": "rimraf dist",
22
+ "test": "vitest run --passWithNoTests",
23
+ "test:watch": "vitest",
24
+ "dev": "tsup --config tsup.config.ts --watch",
25
+ "lint": "eslint src --ext .ts",
26
+ "lint:fix": "eslint . --fix",
27
+ "type-check": "tsc --noEmit"
28
+ },
29
+ "dependencies": {},
30
+ "devDependencies": {
31
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
32
+ "typescript": "^5.6.3",
33
+ "tsup": "^8.5.0",
34
+ "rimraf": "^6.0.1",
35
+ "vitest": "^3.2.4",
36
+ "@types/node": "^24.3.3"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "engines": {
42
+ "node": ">=20.0.0",
43
+ "pnpm": ">=9.0.0"
44
+ }
45
+ }