@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.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-testing/setup-platform
|
|
3
|
+
*
|
|
4
|
+
* Solves the "singleton gap" problem: composables (useLLM, useCache, etc.)
|
|
5
|
+
* read from the global platform singleton, but createTestContext() only
|
|
6
|
+
* populates ctx.platform. This module bridges the gap by setting test mocks
|
|
7
|
+
* directly into the global singleton.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { platform, resetPlatform } from '@kb-labs/core-runtime';
|
|
11
|
+
import type {
|
|
12
|
+
ILLM,
|
|
13
|
+
ICache,
|
|
14
|
+
IEmbeddings,
|
|
15
|
+
IVectorStore,
|
|
16
|
+
IStorage,
|
|
17
|
+
IAnalytics,
|
|
18
|
+
ILogger,
|
|
19
|
+
IEventBus,
|
|
20
|
+
} from '@kb-labs/core-platform';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for setting up the test platform.
|
|
24
|
+
* Only adapters that are provided will be registered.
|
|
25
|
+
* Omitted adapters will use the PlatformContainer's built-in fallbacks.
|
|
26
|
+
*/
|
|
27
|
+
export interface TestPlatformOptions {
|
|
28
|
+
llm?: ILLM;
|
|
29
|
+
cache?: ICache;
|
|
30
|
+
embeddings?: IEmbeddings;
|
|
31
|
+
vectorStore?: IVectorStore;
|
|
32
|
+
storage?: IStorage;
|
|
33
|
+
analytics?: IAnalytics;
|
|
34
|
+
logger?: ILogger;
|
|
35
|
+
eventBus?: IEventBus;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TestPlatformResult {
|
|
39
|
+
/** The global platform singleton (with test mocks applied) */
|
|
40
|
+
platform: typeof platform;
|
|
41
|
+
/** Call in afterEach() to reset the singleton to a clean state */
|
|
42
|
+
cleanup: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Setup the global platform singleton with test mocks.
|
|
47
|
+
*
|
|
48
|
+
* This ensures that useLLM(), useCache() and other composables
|
|
49
|
+
* return the test mocks instead of uninitialized/stale adapters.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* import { setupTestPlatform, mockLLM } from '@kb-labs/shared-testing';
|
|
54
|
+
*
|
|
55
|
+
* describe('my handler', () => {
|
|
56
|
+
* let cleanup: () => void;
|
|
57
|
+
*
|
|
58
|
+
* beforeEach(() => {
|
|
59
|
+
* const result = setupTestPlatform({
|
|
60
|
+
* llm: mockLLM().onAnyComplete().respondWith('hello'),
|
|
61
|
+
* });
|
|
62
|
+
* cleanup = result.cleanup;
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* afterEach(() => cleanup());
|
|
66
|
+
*
|
|
67
|
+
* it('uses LLM', async () => {
|
|
68
|
+
* const llm = useLLM(); // Returns the test mock!
|
|
69
|
+
* const res = await llm!.complete('test');
|
|
70
|
+
* expect(res.content).toBe('hello');
|
|
71
|
+
* });
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function setupTestPlatform(options: TestPlatformOptions = {}): TestPlatformResult {
|
|
76
|
+
// Reset everything first — clean slate
|
|
77
|
+
resetPlatform();
|
|
78
|
+
|
|
79
|
+
// Register provided adapters into the global singleton
|
|
80
|
+
const adapterMap: Array<[string, unknown]> = [
|
|
81
|
+
['llm', options.llm],
|
|
82
|
+
['cache', options.cache],
|
|
83
|
+
['embeddings', options.embeddings],
|
|
84
|
+
['vectorStore', options.vectorStore],
|
|
85
|
+
['storage', options.storage],
|
|
86
|
+
['analytics', options.analytics],
|
|
87
|
+
['logger', options.logger],
|
|
88
|
+
['eventBus', options.eventBus],
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
for (const [key, instance] of adapterMap) {
|
|
92
|
+
if (instance !== undefined) {
|
|
93
|
+
platform.setAdapter(key, instance);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
platform,
|
|
99
|
+
cleanup: () => resetPlatform(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-testing/test-command
|
|
3
|
+
*
|
|
4
|
+
* Single-function test runner for plugin command handlers.
|
|
5
|
+
*
|
|
6
|
+
* Eliminates boilerplate: no manual context creation, no descriptor setup,
|
|
7
|
+
* no mock wiring. Import your handler, call testCommand(), assert on result.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { testCommand, mockLLM } from '@kb-labs/shared-testing';
|
|
12
|
+
* import handler from '../src/commands/greet.js';
|
|
13
|
+
*
|
|
14
|
+
* it('greets the user', async () => {
|
|
15
|
+
* const result = await testCommand(handler, {
|
|
16
|
+
* flags: { name: 'Alice' },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* expect(result.exitCode).toBe(0);
|
|
20
|
+
* expect(result.result).toEqual({ message: 'Hello, Alice!' });
|
|
21
|
+
* expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type {
|
|
27
|
+
PluginContextV3,
|
|
28
|
+
CommandResult,
|
|
29
|
+
UIFacade,
|
|
30
|
+
PlatformServices,
|
|
31
|
+
} from '@kb-labs/plugin-contracts';
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
createTestContext,
|
|
35
|
+
type CreateTestContextOptions,
|
|
36
|
+
} from './create-test-context.js';
|
|
37
|
+
|
|
38
|
+
// ────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Types
|
|
40
|
+
// ────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Any handler that has an execute(ctx, input) method.
|
|
44
|
+
* Covers CommandHandlerV3, RouteHandler, Handler, and raw objects.
|
|
45
|
+
*/
|
|
46
|
+
export interface TestableHandler<TConfig = unknown, TInput = unknown, TResult = unknown> {
|
|
47
|
+
execute(
|
|
48
|
+
context: PluginContextV3<TConfig>,
|
|
49
|
+
input: TInput
|
|
50
|
+
): Promise<TResult> | TResult;
|
|
51
|
+
cleanup?(): Promise<void> | void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Options for testCommand().
|
|
56
|
+
*
|
|
57
|
+
* Provide only what you need — everything else gets sensible defaults.
|
|
58
|
+
*/
|
|
59
|
+
export interface TestCommandOptions<TConfig = unknown> {
|
|
60
|
+
// ── Input ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/** CLI flags (sugar for input: { flags, argv }) */
|
|
63
|
+
flags?: Record<string, unknown>;
|
|
64
|
+
|
|
65
|
+
/** CLI positional arguments (default: []) */
|
|
66
|
+
argv?: string[];
|
|
67
|
+
|
|
68
|
+
/** REST query params (sugar for input: { query }) */
|
|
69
|
+
query?: Record<string, unknown>;
|
|
70
|
+
|
|
71
|
+
/** REST request body (sugar for input: { body }) */
|
|
72
|
+
body?: unknown;
|
|
73
|
+
|
|
74
|
+
/** REST route params (sugar for input: { params }) */
|
|
75
|
+
params?: Record<string, unknown>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Raw input object. If provided, flags/argv/query/body/params are ignored.
|
|
79
|
+
* Use this when your handler expects a custom input shape.
|
|
80
|
+
*/
|
|
81
|
+
input?: unknown;
|
|
82
|
+
|
|
83
|
+
// ── Context ────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/** Host type (default: 'cli') */
|
|
86
|
+
host?: 'cli' | 'rest' | 'workflow' | 'webhook';
|
|
87
|
+
|
|
88
|
+
/** Plugin config passed as ctx.config */
|
|
89
|
+
config?: TConfig;
|
|
90
|
+
|
|
91
|
+
/** Working directory (default: process.cwd()) */
|
|
92
|
+
cwd?: string;
|
|
93
|
+
|
|
94
|
+
/** Tenant ID */
|
|
95
|
+
tenantId?: string;
|
|
96
|
+
|
|
97
|
+
/** Abort signal */
|
|
98
|
+
signal?: AbortSignal;
|
|
99
|
+
|
|
100
|
+
// ── Mocks ──────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** Override platform services (e.g., { llm: mockLLM().onAnyComplete().respondWith('ok') }) */
|
|
103
|
+
platform?: Partial<PlatformServices>;
|
|
104
|
+
|
|
105
|
+
/** Override UI facade (individual methods are merged with default spy UI) */
|
|
106
|
+
ui?: Partial<UIFacade>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Sync platform mocks to global singleton for composables (useLLM, useCache, etc.)
|
|
110
|
+
* @default true
|
|
111
|
+
*/
|
|
112
|
+
syncSingleton?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Result of testCommand() — everything you need for assertions.
|
|
117
|
+
*/
|
|
118
|
+
export interface TestCommandResult<TResult = unknown> {
|
|
119
|
+
/** Exit code from CommandResult (0 = success). Defaults to 0 if handler returns raw data. */
|
|
120
|
+
exitCode: number;
|
|
121
|
+
|
|
122
|
+
/** The result data from the handler */
|
|
123
|
+
result: TResult | undefined;
|
|
124
|
+
|
|
125
|
+
/** Custom metadata from CommandResult.meta */
|
|
126
|
+
meta: Record<string, unknown> | undefined;
|
|
127
|
+
|
|
128
|
+
/** The raw return value from handler.execute() — useful for non-CommandResult handlers */
|
|
129
|
+
raw: unknown;
|
|
130
|
+
|
|
131
|
+
/** UI facade with vi.fn() spies — assert on ui.success, ui.error, etc. */
|
|
132
|
+
ui: UIFacade;
|
|
133
|
+
|
|
134
|
+
/** The full context that was passed to the handler */
|
|
135
|
+
ctx: PluginContextV3;
|
|
136
|
+
|
|
137
|
+
/** Call in afterEach() to reset the global platform singleton */
|
|
138
|
+
cleanup: () => void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ────────────────────────────────────────────────────────────────────
|
|
142
|
+
// Implementation
|
|
143
|
+
// ────────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Test a plugin command handler with minimal boilerplate.
|
|
147
|
+
*
|
|
148
|
+
* Creates a test context, builds the input object, calls handler.execute(),
|
|
149
|
+
* and returns a result with UI spies for assertions.
|
|
150
|
+
*
|
|
151
|
+
* @example CLI command
|
|
152
|
+
* ```typescript
|
|
153
|
+
* import handler from '../src/commands/greet.js';
|
|
154
|
+
*
|
|
155
|
+
* const result = await testCommand(handler, {
|
|
156
|
+
* flags: { name: 'Alice' },
|
|
157
|
+
* });
|
|
158
|
+
* expect(result.exitCode).toBe(0);
|
|
159
|
+
* expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @example REST handler
|
|
163
|
+
* ```typescript
|
|
164
|
+
* import handler from '../src/routes/create-plan.js';
|
|
165
|
+
*
|
|
166
|
+
* const result = await testCommand(handler, {
|
|
167
|
+
* host: 'rest',
|
|
168
|
+
* body: { name: 'v2.0' },
|
|
169
|
+
* query: { workspace: 'root' },
|
|
170
|
+
* });
|
|
171
|
+
* expect(result.result).toMatchObject({ name: 'v2.0' });
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* @example With LLM mock
|
|
175
|
+
* ```typescript
|
|
176
|
+
* import { testCommand, mockLLM } from '@kb-labs/shared-testing';
|
|
177
|
+
* import handler from '../src/commands/analyze.js';
|
|
178
|
+
*
|
|
179
|
+
* const llm = mockLLM().onAnyComplete().respondWith('looks good');
|
|
180
|
+
* const result = await testCommand(handler, {
|
|
181
|
+
* flags: { file: 'index.ts' },
|
|
182
|
+
* platform: { llm },
|
|
183
|
+
* });
|
|
184
|
+
* expect(llm.complete).toHaveBeenCalled();
|
|
185
|
+
* expect(result.exitCode).toBe(0);
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export async function testCommand<TResult = unknown, TConfig = unknown>(
|
|
189
|
+
handler: TestableHandler<TConfig, any, any>,
|
|
190
|
+
options: TestCommandOptions<TConfig> = {}
|
|
191
|
+
): Promise<TestCommandResult<TResult>> {
|
|
192
|
+
const {
|
|
193
|
+
flags,
|
|
194
|
+
argv = [],
|
|
195
|
+
query,
|
|
196
|
+
body,
|
|
197
|
+
params,
|
|
198
|
+
input: rawInput,
|
|
199
|
+
host = 'cli',
|
|
200
|
+
config,
|
|
201
|
+
cwd,
|
|
202
|
+
tenantId,
|
|
203
|
+
signal,
|
|
204
|
+
platform,
|
|
205
|
+
ui,
|
|
206
|
+
syncSingleton = true,
|
|
207
|
+
} = options;
|
|
208
|
+
|
|
209
|
+
// ── Build context ──────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
const ctxOptions: CreateTestContextOptions = {
|
|
212
|
+
host,
|
|
213
|
+
config,
|
|
214
|
+
cwd,
|
|
215
|
+
tenantId,
|
|
216
|
+
signal,
|
|
217
|
+
platform,
|
|
218
|
+
ui,
|
|
219
|
+
syncSingleton,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const { ctx, cleanup } = createTestContext<TConfig>(ctxOptions);
|
|
223
|
+
|
|
224
|
+
// ── Build input ────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
let input: unknown;
|
|
227
|
+
|
|
228
|
+
if (rawInput !== undefined) {
|
|
229
|
+
// Explicit raw input takes precedence
|
|
230
|
+
input = rawInput;
|
|
231
|
+
} else if (host === 'rest') {
|
|
232
|
+
// REST handlers expect { query?, body?, params? }
|
|
233
|
+
input = {
|
|
234
|
+
...(query !== undefined ? { query } : {}),
|
|
235
|
+
...(body !== undefined ? { body } : {}),
|
|
236
|
+
...(params !== undefined ? { params } : {}),
|
|
237
|
+
};
|
|
238
|
+
} else {
|
|
239
|
+
// CLI/workflow handlers expect { flags, argv }
|
|
240
|
+
input = { flags: flags ?? {}, argv };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Execute ────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
let raw: unknown;
|
|
246
|
+
try {
|
|
247
|
+
raw = await handler.execute(ctx as PluginContextV3<TConfig>, input as any);
|
|
248
|
+
} finally {
|
|
249
|
+
// Call cleanup if handler has one
|
|
250
|
+
await handler.cleanup?.();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── Normalize result ───────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
// Handler may return CommandResult<T>, void, or raw data.
|
|
256
|
+
// Detect by checking for exitCode property.
|
|
257
|
+
let exitCode = 0;
|
|
258
|
+
let result: TResult | undefined;
|
|
259
|
+
let meta: Record<string, unknown> | undefined;
|
|
260
|
+
|
|
261
|
+
if (raw != null && typeof raw === 'object' && 'exitCode' in raw) {
|
|
262
|
+
// CommandResult shape
|
|
263
|
+
const cmdResult = raw as CommandResult<TResult>;
|
|
264
|
+
exitCode = cmdResult.exitCode;
|
|
265
|
+
result = cmdResult.result;
|
|
266
|
+
meta = cmdResult.meta;
|
|
267
|
+
} else if (raw === undefined || raw === null) {
|
|
268
|
+
// void return (RouteHandler can return void)
|
|
269
|
+
exitCode = 0;
|
|
270
|
+
result = undefined;
|
|
271
|
+
meta = undefined;
|
|
272
|
+
} else {
|
|
273
|
+
// Raw data (Handler from defineHandler returns data directly)
|
|
274
|
+
exitCode = 0;
|
|
275
|
+
result = raw as TResult;
|
|
276
|
+
meta = undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
exitCode,
|
|
281
|
+
result,
|
|
282
|
+
meta,
|
|
283
|
+
raw,
|
|
284
|
+
ui: ctx.ui,
|
|
285
|
+
ctx: ctx as PluginContextV3,
|
|
286
|
+
cleanup,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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\/.*/, // All workspace packages - let runtime resolve them
|
|
18
|
+
'vitest',
|
|
19
|
+
],
|
|
20
|
+
});
|
|
@@ -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,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/shared-tool-kit",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Tool factory and mock utilities for KB Labs agent tool development",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"import": "./dist/testing/index.js",
|
|
15
|
+
"types": "./dist/testing/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"build": "tsup --config tsup.config.ts",
|
|
26
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
27
|
+
"type-check": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint src",
|
|
29
|
+
"lint:fix": "eslint src --fix"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
|
|
33
|
+
"@types/node": "^24.3.3",
|
|
34
|
+
"rimraf": "^6.0.1",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"typescript": "^5.6.3",
|
|
37
|
+
"vitest": "^3.2.4"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.0.0",
|
|
41
|
+
"pnpm": ">=9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"packageManager": "pnpm@9.11.0",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { createTool } from '../factory.js';
|
|
3
|
+
|
|
4
|
+
interface TestCtx {
|
|
5
|
+
workingDir: string;
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const spec = {
|
|
10
|
+
name: 'test_tool',
|
|
11
|
+
description: 'A test tool',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object' as const,
|
|
14
|
+
properties: {
|
|
15
|
+
value: { type: 'string', description: 'A string value' },
|
|
16
|
+
},
|
|
17
|
+
required: ['value'],
|
|
18
|
+
},
|
|
19
|
+
execute: async ({ value }: { value: string }, ctx: TestCtx) => ({
|
|
20
|
+
success: true,
|
|
21
|
+
output: `${value} from ${ctx.workingDir}`,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('createTool', () => {
|
|
26
|
+
it('returns a factory function', () => {
|
|
27
|
+
const factory = createTool(spec);
|
|
28
|
+
expect(typeof factory).toBe('function');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('factory returns tool with correct definition', () => {
|
|
32
|
+
const factory = createTool(spec);
|
|
33
|
+
const tool = factory({ workingDir: '/project' });
|
|
34
|
+
|
|
35
|
+
expect(tool.definition.type).toBe('function');
|
|
36
|
+
expect(tool.definition.function.name).toBe('test_tool');
|
|
37
|
+
expect(tool.definition.function.description).toBe('A test tool');
|
|
38
|
+
expect(tool.definition.function.parameters.required).toContain('value');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('tool executor calls spec.execute with input and context', async () => {
|
|
42
|
+
const execute = vi.fn(async () => ({ success: true, output: 'ok' }));
|
|
43
|
+
const factory = createTool({ ...spec, execute });
|
|
44
|
+
|
|
45
|
+
const ctx: TestCtx = { workingDir: '/my/project' };
|
|
46
|
+
const tool = factory(ctx);
|
|
47
|
+
await tool.executor({ value: 'hello' });
|
|
48
|
+
|
|
49
|
+
expect(execute).toHaveBeenCalledWith({ value: 'hello' }, ctx);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('executor returns result from spec.execute', async () => {
|
|
53
|
+
const factory = createTool(spec);
|
|
54
|
+
const tool = factory({ workingDir: '/root' });
|
|
55
|
+
|
|
56
|
+
const result = await tool.executor({ value: 'test' }) as any;
|
|
57
|
+
|
|
58
|
+
expect(result.success).toBe(true);
|
|
59
|
+
expect(result.output).toBe('test from /root');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('each factory call creates independent tool bound to its context', async () => {
|
|
63
|
+
const execute = vi.fn(async (_input: unknown, ctx: TestCtx) => ({
|
|
64
|
+
success: true,
|
|
65
|
+
output: ctx.workingDir,
|
|
66
|
+
}));
|
|
67
|
+
const factory = createTool({ ...spec, execute });
|
|
68
|
+
|
|
69
|
+
const tool1 = factory({ workingDir: '/dir1' });
|
|
70
|
+
const tool2 = factory({ workingDir: '/dir2' });
|
|
71
|
+
|
|
72
|
+
const r1 = await tool1.executor({}) as any;
|
|
73
|
+
const r2 = await tool2.executor({}) as any;
|
|
74
|
+
|
|
75
|
+
expect(r1.output).toBe('/dir1');
|
|
76
|
+
expect(r2.output).toBe('/dir2');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('parameters shape is preserved verbatim', () => {
|
|
80
|
+
const customParams = {
|
|
81
|
+
type: 'object' as const,
|
|
82
|
+
properties: {
|
|
83
|
+
foo: { type: 'number' },
|
|
84
|
+
bar: { type: 'boolean' },
|
|
85
|
+
},
|
|
86
|
+
required: ['foo'],
|
|
87
|
+
};
|
|
88
|
+
const factory = createTool({ ...spec, parameters: customParams });
|
|
89
|
+
const tool = factory({ workingDir: '/' });
|
|
90
|
+
|
|
91
|
+
expect(tool.definition.function.parameters).toEqual(customParams);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('executor propagates errors from spec.execute', async () => {
|
|
95
|
+
const factory = createTool({
|
|
96
|
+
...spec,
|
|
97
|
+
execute: async () => { throw new Error('something went wrong'); },
|
|
98
|
+
});
|
|
99
|
+
const tool = factory({ workingDir: '/' });
|
|
100
|
+
|
|
101
|
+
await expect(tool.executor({})).rejects.toThrow('something went wrong');
|
|
102
|
+
});
|
|
103
|
+
});
|