@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,310 @@
|
|
|
1
|
+
import { box, keyValue, safeKeyValue, hasAnsi } from './format';
|
|
2
|
+
import { formatTiming } from './command-output';
|
|
3
|
+
import { safeColors, safeSymbols } from './colors';
|
|
4
|
+
import {
|
|
5
|
+
displayArtifacts,
|
|
6
|
+
type ArtifactInfo,
|
|
7
|
+
type ArtifactDisplayOptions,
|
|
8
|
+
} from './artifacts-display';
|
|
9
|
+
import { TimingTracker } from './timing-tracker';
|
|
10
|
+
|
|
11
|
+
// Optional analytics types - using interface to avoid type resolution errors
|
|
12
|
+
// when @kb-labs/analytics-sdk-node is not installed
|
|
13
|
+
interface AnalyticsRunScope {
|
|
14
|
+
(options: { runId?: string; actor?: unknown; ctx?: Record<string, unknown> }, fn: (emit: AnalyticsEmit) => Promise<unknown>): Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AnalyticsEmit {
|
|
18
|
+
(event: Partial<{ type: string; payload: Record<string, unknown> }>): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CommandPresenter {
|
|
22
|
+
info(message: string): void;
|
|
23
|
+
warn?(message: string): void;
|
|
24
|
+
error(message: string): void;
|
|
25
|
+
write(payload: string): void;
|
|
26
|
+
json(payload: unknown): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CommandContext {
|
|
30
|
+
cwd: string;
|
|
31
|
+
presenter: CommandPresenter;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CommandExecutionResult {
|
|
35
|
+
summary: Record<string, string | number>;
|
|
36
|
+
artifacts?: ArtifactInfo[];
|
|
37
|
+
artifactsOptions?: ArtifactDisplayOptions;
|
|
38
|
+
timing?: number | Record<string, number>;
|
|
39
|
+
diagnostics?: string[];
|
|
40
|
+
warnings?: string[];
|
|
41
|
+
errors?: string[];
|
|
42
|
+
data?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AnalyticsConfig {
|
|
46
|
+
actor: string;
|
|
47
|
+
started: string;
|
|
48
|
+
finished: string;
|
|
49
|
+
getPayload?: (flags: Record<string, unknown>, result?: CommandExecutionResult) => Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CommandRunnerOptions {
|
|
53
|
+
title: string;
|
|
54
|
+
analytics?: AnalyticsConfig;
|
|
55
|
+
execute: (ctx: CommandContext, flags: Record<string, unknown>, tracker: TimingTracker) => Promise<CommandExecutionResult>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createCommandRunner(options: CommandRunnerOptions) {
|
|
59
|
+
return async (ctx: CommandContext, argv: string[], flags: Record<string, unknown>): Promise<number> => {
|
|
60
|
+
const tracker = new TimingTracker();
|
|
61
|
+
const jsonMode = flags.json === true;
|
|
62
|
+
const quietMode = flags.quiet === true;
|
|
63
|
+
const start = Date.now();
|
|
64
|
+
|
|
65
|
+
let runScope: AnalyticsRunScope | null = null;
|
|
66
|
+
if (options.analytics !== undefined) {
|
|
67
|
+
try {
|
|
68
|
+
// Dynamic import with type assertion to avoid TypeScript errors
|
|
69
|
+
// @kb-labs/analytics-sdk-node is an optional dependency
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
71
|
+
// @ts-ignore - optional dependency, types may not be available
|
|
72
|
+
const analyticsModule = await import('@kb-labs/analytics-sdk-node') as {
|
|
73
|
+
runScope?: AnalyticsRunScope;
|
|
74
|
+
};
|
|
75
|
+
if (analyticsModule.runScope) {
|
|
76
|
+
runScope = analyticsModule.runScope;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Analytics SDK not available, continue without it
|
|
80
|
+
runScope = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const execute = async (): Promise<number> => {
|
|
85
|
+
tracker.checkpoint('start');
|
|
86
|
+
try {
|
|
87
|
+
const result = await options.execute(ctx, flags, tracker);
|
|
88
|
+
tracker.checkpoint('complete');
|
|
89
|
+
formatSuccessOutput(ctx, options.title, result, tracker.total(), {
|
|
90
|
+
jsonMode,
|
|
91
|
+
quietMode,
|
|
92
|
+
});
|
|
93
|
+
return 0;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
tracker.checkpoint('error');
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
+
formatErrorOutput(ctx, message, tracker.total(), {
|
|
98
|
+
jsonMode,
|
|
99
|
+
quietMode,
|
|
100
|
+
});
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (!runScope || !options.analytics) {
|
|
106
|
+
return execute();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const totalStart = Date.now();
|
|
110
|
+
const result = await runScope(
|
|
111
|
+
{
|
|
112
|
+
actor: options.analytics.actor as never,
|
|
113
|
+
ctx: { workspace: ctx.cwd },
|
|
114
|
+
},
|
|
115
|
+
async (emit: AnalyticsEmit) => {
|
|
116
|
+
await emit({
|
|
117
|
+
type: options.analytics!.started,
|
|
118
|
+
payload: options.analytics!.getPayload?.(flags) ?? {},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let exitCode = 0;
|
|
122
|
+
try {
|
|
123
|
+
exitCode = await execute();
|
|
124
|
+
await emit({
|
|
125
|
+
type: options.analytics!.finished,
|
|
126
|
+
payload: {
|
|
127
|
+
...(options.analytics!.getPayload?.(flags) ?? {}),
|
|
128
|
+
durationMs: Date.now() - totalStart,
|
|
129
|
+
result: exitCode === 0 ? 'success' : 'error',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
await emit({
|
|
134
|
+
type: options.analytics!.finished,
|
|
135
|
+
payload: {
|
|
136
|
+
...(options.analytics!.getPayload?.(flags) ?? {}),
|
|
137
|
+
durationMs: Date.now() - totalStart,
|
|
138
|
+
result: 'error',
|
|
139
|
+
error: error instanceof Error ? error.message : String(error),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return exitCode;
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return result as number;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface OutputFlags {
|
|
154
|
+
jsonMode: boolean;
|
|
155
|
+
quietMode: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatSuccessOutput(
|
|
159
|
+
ctx: CommandContext,
|
|
160
|
+
title: string,
|
|
161
|
+
result: CommandExecutionResult,
|
|
162
|
+
durationMs: number,
|
|
163
|
+
flags: OutputFlags,
|
|
164
|
+
): void {
|
|
165
|
+
if (flags.jsonMode) {
|
|
166
|
+
const payload: Record<string, unknown> = {
|
|
167
|
+
ok: true,
|
|
168
|
+
summary: result.summary,
|
|
169
|
+
timing: result.timing ?? durationMs,
|
|
170
|
+
diagnostics: result.diagnostics,
|
|
171
|
+
warnings: result.warnings,
|
|
172
|
+
errors: result.errors,
|
|
173
|
+
};
|
|
174
|
+
if (result.artifacts) {
|
|
175
|
+
payload.artifacts = result.artifacts;
|
|
176
|
+
}
|
|
177
|
+
if (result.data) {
|
|
178
|
+
Object.assign(payload, result.data);
|
|
179
|
+
}
|
|
180
|
+
ctx.presenter.json(payload);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (flags.quietMode) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const statusEntry = extractStatusEntry(result.summary);
|
|
189
|
+
const summaryForDisplay: Record<string, string | number> = statusEntry
|
|
190
|
+
? { ...result.summary }
|
|
191
|
+
: result.summary;
|
|
192
|
+
|
|
193
|
+
if (statusEntry) {
|
|
194
|
+
delete summaryForDisplay[statusEntry.key];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const lines: string[] = [];
|
|
198
|
+
const summaryLines = keyValue(summaryForDisplay);
|
|
199
|
+
if (summaryLines.length > 0) {
|
|
200
|
+
lines.push(...summaryLines);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (result.artifacts && result.artifacts.length > 0) {
|
|
204
|
+
if (lines.length > 0) {
|
|
205
|
+
lines.push('');
|
|
206
|
+
}
|
|
207
|
+
lines.push(...displayArtifacts(result.artifacts, result.artifactsOptions));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const timing = result.timing ?? durationMs;
|
|
211
|
+
if (typeof timing !== 'number') {
|
|
212
|
+
const entries = Object.entries(timing);
|
|
213
|
+
if (entries.length > 0) {
|
|
214
|
+
if (lines.length > 0) {
|
|
215
|
+
lines.push('');
|
|
216
|
+
}
|
|
217
|
+
lines.push(safeColors.bold('Timing'));
|
|
218
|
+
for (const [label, value] of entries) {
|
|
219
|
+
lines.push(...safeKeyValue({ [label]: formatTiming(value) }, { indent: 2, pad: false }));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (result.diagnostics && result.diagnostics.length > 0) {
|
|
225
|
+
if (lines.length > 0) {
|
|
226
|
+
lines.push('');
|
|
227
|
+
}
|
|
228
|
+
lines.push(safeColors.bold('Diagnostics'));
|
|
229
|
+
result.diagnostics.forEach((item) => lines.push(safeColors.muted(`- ${item}`)));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
233
|
+
if (lines.length > 0) {
|
|
234
|
+
lines.push('');
|
|
235
|
+
}
|
|
236
|
+
lines.push(safeColors.bold('Warnings'));
|
|
237
|
+
result.warnings.forEach((item) => lines.push(safeColors.muted(`- ${item}`)));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (result.errors && result.errors.length > 0) {
|
|
241
|
+
if (lines.length > 0) {
|
|
242
|
+
lines.push('');
|
|
243
|
+
}
|
|
244
|
+
lines.push(safeColors.bold('Errors'));
|
|
245
|
+
result.errors.forEach((item) => lines.push(safeColors.muted(`- ${item}`)));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (lines.length > 0) {
|
|
249
|
+
lines.push('');
|
|
250
|
+
}
|
|
251
|
+
lines.push(formatStatusLine(statusEntry?.value, durationMs));
|
|
252
|
+
|
|
253
|
+
ctx.presenter.write(box(title, lines));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function formatErrorOutput(
|
|
257
|
+
ctx: CommandContext,
|
|
258
|
+
errorMessage: string,
|
|
259
|
+
durationMs: number,
|
|
260
|
+
flags: OutputFlags,
|
|
261
|
+
): void {
|
|
262
|
+
if (flags.jsonMode) {
|
|
263
|
+
ctx.presenter.json({
|
|
264
|
+
ok: false,
|
|
265
|
+
error: errorMessage,
|
|
266
|
+
timing: durationMs,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (flags.quietMode) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
ctx.presenter.error(errorMessage);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface StatusEntry {
|
|
279
|
+
key: string;
|
|
280
|
+
value: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function extractStatusEntry(summary: Record<string, string | number>): StatusEntry | null {
|
|
284
|
+
for (const [key, value] of Object.entries(summary)) {
|
|
285
|
+
if (typeof value === 'string' && key.toLowerCase().includes('status')) {
|
|
286
|
+
return { key, value };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function formatStatusLine(statusValue: string | undefined, durationMs: number): string {
|
|
293
|
+
const rawStatus = statusValue ?? 'Done';
|
|
294
|
+
const normalized = rawStatus.toLowerCase();
|
|
295
|
+
|
|
296
|
+
let symbol = safeSymbols.success;
|
|
297
|
+
let colorize = safeColors.success;
|
|
298
|
+
|
|
299
|
+
if (normalized.includes('error') || normalized.includes('fail')) {
|
|
300
|
+
symbol = safeSymbols.error;
|
|
301
|
+
colorize = safeColors.error;
|
|
302
|
+
} else if (normalized.includes('partial') || normalized.includes('warn')) {
|
|
303
|
+
symbol = safeSymbols.warning;
|
|
304
|
+
colorize = safeColors.warning;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const statusText = hasAnsi(rawStatus) ? rawStatus : colorize(rawStatus);
|
|
308
|
+
return `${symbol} ${statusText} · ${safeColors.muted(formatTiming(durationMs))}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command suggestions and validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CommandSuggestion {
|
|
6
|
+
id: string;
|
|
7
|
+
command: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
description: string;
|
|
10
|
+
impact: 'safe' | 'disruptive';
|
|
11
|
+
when: string;
|
|
12
|
+
available?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CommandRegistry {
|
|
16
|
+
commands: Set<string>;
|
|
17
|
+
groups: Map<string, Set<string>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a command registry from available commands
|
|
22
|
+
*/
|
|
23
|
+
export function createCommandRegistry(commands: string[]): CommandRegistry {
|
|
24
|
+
const commandSet = new Set(commands);
|
|
25
|
+
const groups = new Map<string, Set<string>>();
|
|
26
|
+
|
|
27
|
+
// Group commands by prefix (e.g., "devlink:apply" -> group "devlink")
|
|
28
|
+
for (const cmd of commands) {
|
|
29
|
+
const [group, ...rest] = cmd.split(':');
|
|
30
|
+
if (group && rest.length > 0) {
|
|
31
|
+
if (!groups.has(group)) {
|
|
32
|
+
groups.set(group, new Set());
|
|
33
|
+
}
|
|
34
|
+
groups.get(group)!.add(rest.join(':'));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
commands: commandSet,
|
|
40
|
+
groups
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a command is available in the registry
|
|
46
|
+
*/
|
|
47
|
+
export function isCommandAvailable(
|
|
48
|
+
command: string,
|
|
49
|
+
registry: CommandRegistry
|
|
50
|
+
): boolean {
|
|
51
|
+
// Handle full command strings like "kb devlink apply"
|
|
52
|
+
if (command.startsWith('kb ')) {
|
|
53
|
+
const parts = command.split(' ');
|
|
54
|
+
if (parts.length >= 3 && parts[1] && parts[2]) {
|
|
55
|
+
const group = parts[1];
|
|
56
|
+
const subcommand = parts[2];
|
|
57
|
+
const fullCommand = `${group}:${subcommand}`;
|
|
58
|
+
return registry.commands.has(fullCommand);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle direct command names like "devlink:apply"
|
|
63
|
+
return registry.commands.has(command);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate suggestions against available commands
|
|
68
|
+
*/
|
|
69
|
+
export function validateSuggestions(
|
|
70
|
+
suggestions: CommandSuggestion[],
|
|
71
|
+
registry: CommandRegistry
|
|
72
|
+
): CommandSuggestion[] {
|
|
73
|
+
return suggestions.map(suggestion => ({
|
|
74
|
+
...suggestion,
|
|
75
|
+
available: isCommandAvailable(suggestion.command, registry)
|
|
76
|
+
})).filter(suggestion => suggestion.available);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate common devlink suggestions
|
|
81
|
+
*/
|
|
82
|
+
export function generateDevlinkSuggestions(
|
|
83
|
+
warningCodes: Set<string>,
|
|
84
|
+
context: { undo?: { available: boolean } },
|
|
85
|
+
registry: CommandRegistry
|
|
86
|
+
): CommandSuggestion[] {
|
|
87
|
+
const suggestions: CommandSuggestion[] = [];
|
|
88
|
+
|
|
89
|
+
// LOCK_MISMATCH -> apply
|
|
90
|
+
if (warningCodes.has('LOCK_MISMATCH')) {
|
|
91
|
+
suggestions.push({
|
|
92
|
+
id: 'SYNC_LOCK',
|
|
93
|
+
command: 'kb devlink apply',
|
|
94
|
+
args: ['--yes'],
|
|
95
|
+
description: 'Apply changes to sync manifests',
|
|
96
|
+
impact: 'safe',
|
|
97
|
+
when: 'LOCK_MISMATCH'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// BACKUP_MISSING -> freeze
|
|
102
|
+
if (warningCodes.has('BACKUP_MISSING')) {
|
|
103
|
+
suggestions.push({
|
|
104
|
+
id: 'CREATE_BACKUP',
|
|
105
|
+
command: 'kb devlink freeze',
|
|
106
|
+
args: ['--replace'],
|
|
107
|
+
description: 'Create a fresh backup',
|
|
108
|
+
impact: 'safe',
|
|
109
|
+
when: 'BACKUP_MISSING'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// STALE_LOCK -> freeze
|
|
114
|
+
if (warningCodes.has('STALE_LOCK')) {
|
|
115
|
+
suggestions.push({
|
|
116
|
+
id: 'REFRESH_LOCK',
|
|
117
|
+
command: 'kb devlink freeze',
|
|
118
|
+
args: ['--pin=caret'],
|
|
119
|
+
description: 'Refresh lock file',
|
|
120
|
+
impact: 'safe',
|
|
121
|
+
when: 'STALE_LOCK'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// STALE_YALC_ARTIFACTS -> clean
|
|
126
|
+
if (warningCodes.has('STALE_YALC_ARTIFACTS')) {
|
|
127
|
+
suggestions.push({
|
|
128
|
+
id: 'CLEAN_YALC',
|
|
129
|
+
command: 'kb devlink clean',
|
|
130
|
+
args: [],
|
|
131
|
+
description: 'Remove yalc artifacts',
|
|
132
|
+
impact: 'safe',
|
|
133
|
+
when: 'STALE_YALC_ARTIFACTS'
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// PROTOCOL_CONFLICTS -> clean --hard
|
|
138
|
+
if (warningCodes.has('PROTOCOL_CONFLICTS')) {
|
|
139
|
+
suggestions.push({
|
|
140
|
+
id: 'FIX_PROTOCOLS',
|
|
141
|
+
command: 'kb devlink clean',
|
|
142
|
+
args: ['--hard'],
|
|
143
|
+
description: 'Reset all protocols and reapply',
|
|
144
|
+
impact: 'disruptive',
|
|
145
|
+
when: 'PROTOCOL_CONFLICTS'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Undo suggestion
|
|
150
|
+
if (context.undo?.available) {
|
|
151
|
+
suggestions.push({
|
|
152
|
+
id: 'UNDO_LAST',
|
|
153
|
+
command: 'kb devlink undo',
|
|
154
|
+
args: [],
|
|
155
|
+
description: 'Revert last operation',
|
|
156
|
+
impact: 'safe',
|
|
157
|
+
when: 'BACKUP_AVAILABLE'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Validate and return only available suggestions
|
|
162
|
+
return validateSuggestions(suggestions, registry);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generate quick actions for common scenarios
|
|
168
|
+
*/
|
|
169
|
+
export function generateQuickActions(
|
|
170
|
+
hasWarnings: boolean,
|
|
171
|
+
registry: CommandRegistry,
|
|
172
|
+
group: string = 'devlink'
|
|
173
|
+
): CommandSuggestion[] {
|
|
174
|
+
if (!hasWarnings) {return [];}
|
|
175
|
+
|
|
176
|
+
const quickActions: CommandSuggestion[] = [
|
|
177
|
+
{
|
|
178
|
+
id: 'QUICK_CLEAN',
|
|
179
|
+
command: `kb ${group} clean`,
|
|
180
|
+
args: [],
|
|
181
|
+
description: 'Clean artifacts',
|
|
182
|
+
impact: 'safe',
|
|
183
|
+
when: 'QUICK_ACTIONS'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'QUICK_PLAN',
|
|
187
|
+
command: `kb ${group} plan`,
|
|
188
|
+
args: [],
|
|
189
|
+
description: 'Create plan',
|
|
190
|
+
impact: 'safe',
|
|
191
|
+
when: 'QUICK_ACTIONS'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'QUICK_APPLY',
|
|
195
|
+
command: `kb ${group} apply`,
|
|
196
|
+
args: [],
|
|
197
|
+
description: 'Apply changes',
|
|
198
|
+
impact: 'safe',
|
|
199
|
+
when: 'QUICK_ACTIONS'
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
return validateSuggestions(quickActions, registry);
|
|
204
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { box } from '../../format';
|
|
2
|
+
import { formatDebugEntryAI, formatDebugEntriesAI, shouldUseAIFormat } from '../formatters/ai';
|
|
3
|
+
import {
|
|
4
|
+
formatDebugEntryHuman,
|
|
5
|
+
formatDebugEntriesHuman,
|
|
6
|
+
type HumanFormatterOptions,
|
|
7
|
+
} from '../formatters/human';
|
|
8
|
+
import { formatTimelineWithSummary } from '../formatters/timeline';
|
|
9
|
+
import type { DebugDetailLevel, DebugEntry, DebugFormat } from '../types';
|
|
10
|
+
|
|
11
|
+
export interface DebugOutputOptions extends HumanFormatterOptions {
|
|
12
|
+
format?: DebugFormat;
|
|
13
|
+
detailLevel?: DebugDetailLevel;
|
|
14
|
+
useTimeline?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatDebugOutput(entry: DebugEntry, options: DebugOutputOptions = {}): string {
|
|
18
|
+
if (shouldUseAIFormat(options.format)) {
|
|
19
|
+
return formatDebugEntryAI(entry);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return formatDebugEntryHuman(entry, options);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatDebugOutputs(entries: DebugEntry[], options: DebugOutputOptions = {}): string {
|
|
26
|
+
if (shouldUseAIFormat(options.format)) {
|
|
27
|
+
return formatDebugEntriesAI(entries);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (options.useTimeline) {
|
|
31
|
+
return formatTimelineWithSummary(entries);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return formatDebugEntriesHuman(entries, options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class DebugSection {
|
|
38
|
+
private readonly entries: DebugEntry[] = [];
|
|
39
|
+
private readonly title: string;
|
|
40
|
+
private readonly options: DebugOutputOptions;
|
|
41
|
+
|
|
42
|
+
constructor(title: string, options: DebugOutputOptions = {}) {
|
|
43
|
+
this.title = title;
|
|
44
|
+
this.options = options;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
add(entry: DebugEntry): void {
|
|
48
|
+
this.entries.push(entry);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addAll(entries: DebugEntry[]): void {
|
|
52
|
+
this.entries.push(...entries);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
clear(): void {
|
|
56
|
+
this.entries.length = 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
format(): string {
|
|
60
|
+
if (this.entries.length === 0) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = formatDebugOutputs(this.entries, this.options);
|
|
65
|
+
if (!content) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (shouldUseAIFormat(this.options.format)) {
|
|
70
|
+
return content;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return box(this.title, content.split('\n'));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get count(): number {
|
|
77
|
+
return this.entries.length;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class DebugOutput {
|
|
82
|
+
private readonly sections = new Map<string, DebugSection>();
|
|
83
|
+
private readonly globalEntries: DebugEntry[] = [];
|
|
84
|
+
private readonly options: DebugOutputOptions;
|
|
85
|
+
|
|
86
|
+
constructor(options: DebugOutputOptions = {}) {
|
|
87
|
+
this.options = options;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
add(entry: DebugEntry): void {
|
|
91
|
+
this.globalEntries.push(entry);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
addToSection(sectionName: string, entry: DebugEntry): void {
|
|
95
|
+
if (!this.sections.has(sectionName)) {
|
|
96
|
+
this.sections.set(sectionName, new DebugSection(sectionName, this.options));
|
|
97
|
+
}
|
|
98
|
+
this.sections.get(sectionName)!.add(entry);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getSection(sectionName: string): DebugSection {
|
|
102
|
+
if (!this.sections.has(sectionName)) {
|
|
103
|
+
this.sections.set(sectionName, new DebugSection(sectionName, this.options));
|
|
104
|
+
}
|
|
105
|
+
return this.sections.get(sectionName)!;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
format(): string {
|
|
109
|
+
const parts: string[] = [];
|
|
110
|
+
|
|
111
|
+
if (this.globalEntries.length > 0) {
|
|
112
|
+
const formatted = formatDebugOutputs(this.globalEntries, this.options);
|
|
113
|
+
if (formatted) {
|
|
114
|
+
parts.push(formatted);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const [, section] of this.sections) {
|
|
119
|
+
const formatted = section.format();
|
|
120
|
+
if (formatted) {
|
|
121
|
+
parts.push(formatted);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return parts.join('\n\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clear(): void {
|
|
129
|
+
this.globalEntries.length = 0;
|
|
130
|
+
this.sections.clear();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get totalCount(): number {
|
|
134
|
+
let count = this.globalEntries.length;
|
|
135
|
+
for (const section of this.sections.values()) {
|
|
136
|
+
count += section.count;
|
|
137
|
+
}
|
|
138
|
+
return count;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|