dialekt 0.1.0 → 0.1.1

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 (71) hide show
  1. package/README.md +8 -10
  2. package/TESTING.md +29 -29
  3. package/dist/cli/main.d.mts +1 -1
  4. package/dist/cli/main.mjs +549 -362
  5. package/dist/formatters-De4Q-X1d.mjs +516 -435
  6. package/dist/index.d.mts +162 -25
  7. package/dist/index.mjs +119 -34
  8. package/package.json +3 -3
  9. package/pnpm-workspace.yaml +3 -3
  10. package/src/adapter/types.test.ts +57 -57
  11. package/src/adapter/types.ts +7 -4
  12. package/src/benchmark/metrics.test.ts +141 -69
  13. package/src/benchmark/metrics.ts +6 -6
  14. package/src/benchmark/report.test.ts +38 -38
  15. package/src/benchmark/report.ts +6 -6
  16. package/src/benchmark/runner.test.ts +70 -72
  17. package/src/benchmark/runner.ts +4 -4
  18. package/src/cli/commands/add.test.ts +90 -109
  19. package/src/cli/commands/add.ts +40 -28
  20. package/src/cli/commands/benchmark.test.ts +77 -64
  21. package/src/cli/commands/benchmark.ts +64 -41
  22. package/src/cli/commands/languages.test.ts +45 -42
  23. package/src/cli/commands/languages.ts +16 -12
  24. package/src/cli/commands/missing.test.ts +143 -92
  25. package/src/cli/commands/missing.ts +24 -17
  26. package/src/cli/commands/translate.test.ts +79 -79
  27. package/src/cli/commands/translate.ts +41 -31
  28. package/src/cli/commands/unused.test.ts +62 -51
  29. package/src/cli/commands/unused.ts +18 -14
  30. package/src/cli/commands/validate.test.ts +130 -72
  31. package/src/cli/commands/validate.ts +25 -20
  32. package/src/cli/config-resolution.test.ts +169 -49
  33. package/src/cli/config-resolution.ts +5 -7
  34. package/src/cli/format.test.ts +50 -50
  35. package/src/cli/format.ts +57 -60
  36. package/src/cli/formatters.test.ts +128 -106
  37. package/src/cli/formatters.ts +72 -95
  38. package/src/cli/main.ts +13 -13
  39. package/src/config/define-config.test.ts +44 -29
  40. package/src/config/define-config.ts +1 -1
  41. package/src/config/load-config.test.ts +21 -18
  42. package/src/config/load-config.ts +5 -5
  43. package/src/config/types.test.ts +50 -44
  44. package/src/config/types.ts +2 -2
  45. package/src/index.ts +22 -26
  46. package/src/keys/flatten.test.ts +52 -52
  47. package/src/keys/flatten.ts +7 -9
  48. package/src/sdk/file-io.test.ts +47 -59
  49. package/src/sdk/file-io.ts +2 -2
  50. package/src/sdk/node-layer.test.ts +18 -18
  51. package/src/sdk/node-layer.ts +2 -2
  52. package/src/sdk/php-array-reader.test.ts +49 -40
  53. package/src/sdk/php-array-reader.ts +5 -5
  54. package/src/translation/chunking.test.ts +52 -44
  55. package/src/translation/chunking.ts +1 -1
  56. package/src/translation/missing-keys.test.ts +86 -93
  57. package/src/translation/missing-keys.ts +4 -6
  58. package/src/translation/model-registry.test.ts +41 -32
  59. package/src/translation/model-registry.ts +9 -9
  60. package/src/translation/one-shot-strategy.test.ts +105 -86
  61. package/src/translation/one-shot-strategy.ts +10 -12
  62. package/src/translation/orchestrator.test.ts +90 -101
  63. package/src/translation/orchestrator.ts +26 -26
  64. package/src/translation/prompt.test.ts +76 -76
  65. package/src/translation/prompt.ts +2 -2
  66. package/src/translation/tool-loop-strategy.test.ts +134 -107
  67. package/src/translation/tool-loop-strategy.ts +14 -18
  68. package/src/translation/types.test.ts +22 -22
  69. package/src/translation/types.ts +3 -3
  70. package/tsdown.config.ts +3 -3
  71. package/vitest.config.ts +3 -3
@@ -1,12 +1,12 @@
1
- import { Command, Options } from '@effect/cli';
2
- import { Effect, Console, Option } from 'effect';
3
- import { loadConfig } from '../../config/load-config.js';
4
- import { resolveEffectiveConfig } from '../config-resolution.js';
5
- import { computeMissingKeys } from '../../translation/missing-keys.js';
6
- import { detectFormat, type OutputFormat } from '../format.js';
7
- import { formatValidate, formatError } from '../formatters.js';
8
- import type { DialektConfig } from '../../config/types.js';
9
- import type { TranslationAdapter, ResourceRef } from '../../adapter/types.js';
1
+ import { Command, Options } from "@effect/cli";
2
+ import { Effect, Console, Option } from "effect";
3
+ import { loadConfig } from "../../config/load-config.js";
4
+ import { resolveEffectiveConfig } from "../config-resolution.js";
5
+ import { computeMissingKeys } from "../../translation/missing-keys.js";
6
+ import { detectFormat, type OutputFormat } from "../format.js";
7
+ import { formatValidate, formatError } from "../formatters.js";
8
+ import type { DialektConfig } from "../../config/types.js";
9
+ import type { TranslationAdapter, ResourceRef } from "../../adapter/types.js";
10
10
 
11
11
  export interface ValidateFlags {
12
12
  readonly config: string;
@@ -30,7 +30,10 @@ export function runValidate(
30
30
  adapter: TranslationAdapter,
31
31
  sourceLocale: string,
32
32
  targetLocales: readonly string[],
33
- ) => Effect.Effect<readonly MissingEntry[], unknown> = computeMissingKeys as unknown as typeof missingKeysComputer,
33
+ ) => Effect.Effect<
34
+ readonly MissingEntry[],
35
+ unknown
36
+ > = computeMissingKeys as unknown as typeof missingKeysComputer,
34
37
  logger: (msg: string) => Effect.Effect<void> = (msg: string) => Console.log(msg),
35
38
  ): Effect.Effect<void, Error> {
36
39
  return Effect.gen(function* () {
@@ -75,9 +78,7 @@ export function runValidate(
75
78
 
76
79
  const passing = entries.length === 0;
77
80
 
78
- yield* logger(
79
- formatValidate({ passing, entries }, format),
80
- );
81
+ yield* logger(formatValidate({ passing, entries }, format));
81
82
 
82
83
  if (!passing) {
83
84
  yield* Effect.sync(() => {
@@ -87,10 +88,14 @@ export function runValidate(
87
88
  }).pipe(Effect.mapError((e) => e as Error)) as Effect.Effect<void, Error, never>;
88
89
  }
89
90
 
90
- export const validateCommand = Command.make('validate', {
91
- config: Options.text('config').pipe(Options.withDefault('./dialekt.config.ts')),
92
- adapter: Options.optional(Options.text('adapter')),
93
- baseLanguage: Options.optional(Options.text('base-language')),
94
- language: Options.optional(Options.text('language')),
95
- format: Options.optional(Options.text('format')),
96
- }, (flags) => runValidate(flags));
91
+ export const validateCommand = Command.make(
92
+ "validate",
93
+ {
94
+ config: Options.text("config").pipe(Options.withDefault("./dialekt.config.ts")),
95
+ adapter: Options.optional(Options.text("adapter")),
96
+ baseLanguage: Options.optional(Options.text("base-language")),
97
+ language: Options.optional(Options.text("language")),
98
+ format: Options.optional(Options.text("format")),
99
+ },
100
+ (flags) => runValidate(flags),
101
+ );
@@ -1,77 +1,122 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { resolveEffectiveConfig } from './config-resolution.js';
3
- import type { DialektConfig } from '../config/types.js';
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveEffectiveConfig } from "./config-resolution.js";
3
+ import type { DialektConfig } from "../config/types.js";
4
4
 
5
5
  const baseConfig: DialektConfig = {
6
- sourceLocale: 'en',
7
- targetLocales: ['de', 'fr'],
8
- strategy: 'one-shot',
9
- model: { provider: 'openai', modelId: 'gpt-4o' },
10
- fastModel: { provider: 'openai', modelId: 'gpt-4o-mini' },
6
+ sourceLocale: "en",
7
+ targetLocales: ["de", "fr"],
8
+ strategy: "one-shot",
9
+ model: { provider: "openai", modelId: "gpt-4o" },
10
+ fastModel: { provider: "openai", modelId: "gpt-4o-mini" },
11
11
  chunking: { maxTokens: 3000, charsPerToken: 3.0, concurrency: 3 },
12
12
  retry: { maxAttempts: 3, baseDelayMs: 1000 },
13
13
  adapters: [],
14
14
  };
15
15
 
16
- describe('resolveEffectiveConfig', () => {
17
- it('uses loaded config when no flags given', () => {
16
+ describe("resolveEffectiveConfig", () => {
17
+ it("uses loaded config when no flags given", () => {
18
18
  const result = resolveEffectiveConfig({}, baseConfig);
19
- expect(result.sourceLocale).toBe('en');
20
- expect(result.targetLocales).toEqual(['de', 'fr']);
19
+ expect(result.sourceLocale).toBe("en");
20
+ expect(result.targetLocales).toEqual(["de", "fr"]);
21
21
  });
22
22
 
23
- it('overrides sourceLocale with flag', () => {
24
- const result = resolveEffectiveConfig({ baseLanguage: 'fr' }, baseConfig);
25
- expect(result.sourceLocale).toBe('fr');
23
+ it("overrides sourceLocale with flag", () => {
24
+ const result = resolveEffectiveConfig({ baseLanguage: "fr" }, baseConfig);
25
+ expect(result.sourceLocale).toBe("fr");
26
26
  });
27
27
 
28
- it('overrides strategy with flag', () => {
29
- const result = resolveEffectiveConfig({ strategy: 'tool-loop-agent' }, baseConfig);
30
- expect(result.strategy).toBe('tool-loop-agent');
28
+ it("overrides strategy with flag", () => {
29
+ const result = resolveEffectiveConfig({ strategy: "tool-loop-agent" }, baseConfig);
30
+ expect(result.strategy).toBe("tool-loop-agent");
31
31
  });
32
32
 
33
- it('filters adapters by name flag', () => {
34
- const adapterA = { name: 'a', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
35
- const adapterB = { name: 'b', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
33
+ it("filters adapters by name flag", () => {
34
+ const adapterA = {
35
+ name: "a",
36
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
37
+ listLocales: () => {
38
+ throw new Error();
39
+ },
40
+ listResources: () => {
41
+ throw new Error();
42
+ },
43
+ readResource: () => {
44
+ throw new Error();
45
+ },
46
+ writeResource: () => {
47
+ throw new Error();
48
+ },
49
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
50
+ const adapterB = {
51
+ name: "b",
52
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
53
+ listLocales: () => {
54
+ throw new Error();
55
+ },
56
+ listResources: () => {
57
+ throw new Error();
58
+ },
59
+ readResource: () => {
60
+ throw new Error();
61
+ },
62
+ writeResource: () => {
63
+ throw new Error();
64
+ },
65
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
36
66
  const config = { ...baseConfig, adapters: [adapterA, adapterB] };
37
- const result = resolveEffectiveConfig({ adapter: 'b' }, config);
67
+ const result = resolveEffectiveConfig({ adapter: "b" }, config);
38
68
  expect(result.adapters).toHaveLength(1);
39
- expect(result.adapters[0]!.name).toBe('b');
69
+ expect(result.adapters[0]!.name).toBe("b");
40
70
  });
41
71
 
42
- it('returns all adapters when adapter flag does not match any', () => {
43
- const adapterA = { name: 'a', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
72
+ it("returns all adapters when adapter flag does not match any", () => {
73
+ const adapterA = {
74
+ name: "a",
75
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
76
+ listLocales: () => {
77
+ throw new Error();
78
+ },
79
+ listResources: () => {
80
+ throw new Error();
81
+ },
82
+ readResource: () => {
83
+ throw new Error();
84
+ },
85
+ writeResource: () => {
86
+ throw new Error();
87
+ },
88
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
44
89
  const config = { ...baseConfig, adapters: [adapterA] };
45
- const result = resolveEffectiveConfig({ adapter: 'nonexistent' }, config);
90
+ const result = resolveEffectiveConfig({ adapter: "nonexistent" }, config);
46
91
  expect(result.adapters).toHaveLength(0);
47
92
  });
48
93
 
49
- it('overrides targetLocales with language flag', () => {
50
- const result = resolveEffectiveConfig({ language: ['es'] }, baseConfig);
51
- expect(result.targetLocales).toEqual(['es']);
94
+ it("overrides targetLocales with language flag", () => {
95
+ const result = resolveEffectiveConfig({ language: ["es"] }, baseConfig);
96
+ expect(result.targetLocales).toEqual(["es"]);
52
97
  });
53
98
 
54
- it('preserves targetLocales when language flag is empty', () => {
99
+ it("preserves targetLocales when language flag is empty", () => {
55
100
  const result = resolveEffectiveConfig({ language: [] }, baseConfig);
56
- expect(result.targetLocales).toEqual(['de', 'fr']);
101
+ expect(result.targetLocales).toEqual(["de", "fr"]);
57
102
  });
58
103
 
59
- it('preserves targetLocales when language flag is undefined', () => {
104
+ it("preserves targetLocales when language flag is undefined", () => {
60
105
  const result = resolveEffectiveConfig({}, baseConfig);
61
- expect(result.targetLocales).toEqual(['de', 'fr']);
106
+ expect(result.targetLocales).toEqual(["de", "fr"]);
62
107
  });
63
108
 
64
- it('does not override strategy when flag is undefined', () => {
109
+ it("does not override strategy when flag is undefined", () => {
65
110
  const result = resolveEffectiveConfig({}, baseConfig);
66
- expect(result.strategy).toBe('one-shot');
111
+ expect(result.strategy).toBe("one-shot");
67
112
  });
68
113
 
69
- it('does not override sourceLocale when baseLanguage is undefined', () => {
114
+ it("does not override sourceLocale when baseLanguage is undefined", () => {
70
115
  const result = resolveEffectiveConfig({}, baseConfig);
71
- expect(result.sourceLocale).toBe('en');
116
+ expect(result.sourceLocale).toBe("en");
72
117
  });
73
118
 
74
- it('preserves other config fields unchanged', () => {
119
+ it("preserves other config fields unchanged", () => {
75
120
  const result = resolveEffectiveConfig({}, baseConfig);
76
121
  expect(result.model).toEqual(baseConfig.model);
77
122
  expect(result.fastModel).toEqual(baseConfig.fastModel);
@@ -79,21 +124,96 @@ describe('resolveEffectiveConfig', () => {
79
124
  expect(result.retry).toEqual(baseConfig.retry);
80
125
  });
81
126
 
82
- it('handles multiple adapter filters matching one', () => {
83
- const a1 = { name: 'laravel', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
84
- const a2 = { name: 'paraglide', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
127
+ it("handles multiple adapter filters matching one", () => {
128
+ const a1 = {
129
+ name: "laravel",
130
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
131
+ listLocales: () => {
132
+ throw new Error();
133
+ },
134
+ listResources: () => {
135
+ throw new Error();
136
+ },
137
+ readResource: () => {
138
+ throw new Error();
139
+ },
140
+ writeResource: () => {
141
+ throw new Error();
142
+ },
143
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
144
+ const a2 = {
145
+ name: "paraglide",
146
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
147
+ listLocales: () => {
148
+ throw new Error();
149
+ },
150
+ listResources: () => {
151
+ throw new Error();
152
+ },
153
+ readResource: () => {
154
+ throw new Error();
155
+ },
156
+ writeResource: () => {
157
+ throw new Error();
158
+ },
159
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
85
160
  const config = { ...baseConfig, adapters: [a1, a2] };
86
- const result = resolveEffectiveConfig({ adapter: 'laravel' }, config);
161
+ const result = resolveEffectiveConfig({ adapter: "laravel" }, config);
87
162
  expect(result.adapters).toHaveLength(1);
88
- expect(result.adapters[0]!.name).toBe('laravel');
163
+ expect(result.adapters[0]!.name).toBe("laravel");
89
164
  });
90
165
 
91
- it('preserves adapter order after filtering', () => {
92
- const a1 = { name: 'first', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
93
- const a2 = { name: 'second', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
94
- const a3 = { name: 'third', capabilities: { canCreateResource: true, unusedKeyDetection: false }, listLocales: () => { throw new Error(); }, listResources: () => { throw new Error(); }, readResource: () => { throw new Error(); }, writeResource: () => { throw new Error(); } } as unknown as import('../config/types.js').DialektConfig['adapters'][number];
166
+ it("preserves adapter order after filtering", () => {
167
+ const a1 = {
168
+ name: "first",
169
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
170
+ listLocales: () => {
171
+ throw new Error();
172
+ },
173
+ listResources: () => {
174
+ throw new Error();
175
+ },
176
+ readResource: () => {
177
+ throw new Error();
178
+ },
179
+ writeResource: () => {
180
+ throw new Error();
181
+ },
182
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
183
+ const a2 = {
184
+ name: "second",
185
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
186
+ listLocales: () => {
187
+ throw new Error();
188
+ },
189
+ listResources: () => {
190
+ throw new Error();
191
+ },
192
+ readResource: () => {
193
+ throw new Error();
194
+ },
195
+ writeResource: () => {
196
+ throw new Error();
197
+ },
198
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
199
+ const a3 = {
200
+ name: "third",
201
+ capabilities: { canCreateResource: true, unusedKeyDetection: false },
202
+ listLocales: () => {
203
+ throw new Error();
204
+ },
205
+ listResources: () => {
206
+ throw new Error();
207
+ },
208
+ readResource: () => {
209
+ throw new Error();
210
+ },
211
+ writeResource: () => {
212
+ throw new Error();
213
+ },
214
+ } as unknown as import("../config/types.js").DialektConfig["adapters"][number];
95
215
  const config = { ...baseConfig, adapters: [a1, a2, a3] };
96
- const result = resolveEffectiveConfig({ adapter: 'second' }, config);
97
- expect(result.adapters[0]!.name).toBe('second');
216
+ const result = resolveEffectiveConfig({ adapter: "second" }, config);
217
+ expect(result.adapters[0]!.name).toBe("second");
98
218
  });
99
219
  });
@@ -1,9 +1,9 @@
1
- import type { DialektConfig } from '../config/types.js';
1
+ import type { DialektConfig } from "../config/types.js";
2
2
 
3
3
  export interface CliFlags {
4
4
  readonly config?: string | undefined;
5
5
  readonly adapter?: string | undefined;
6
- readonly strategy?: 'one-shot' | 'tool-loop-agent' | undefined;
6
+ readonly strategy?: "one-shot" | "tool-loop-agent" | undefined;
7
7
  readonly baseLanguage?: string | undefined;
8
8
  readonly language?: readonly string[] | undefined;
9
9
  readonly name?: readonly string[] | undefined;
@@ -13,14 +13,12 @@ export interface CliFlags {
13
13
  readonly langDir?: string | undefined;
14
14
  }
15
15
 
16
- export function resolveEffectiveConfig(
17
- flags: CliFlags,
18
- loaded: DialektConfig,
19
- ): DialektConfig {
16
+ export function resolveEffectiveConfig(flags: CliFlags, loaded: DialektConfig): DialektConfig {
20
17
  return {
21
18
  ...loaded,
22
19
  sourceLocale: flags.baseLanguage ?? loaded.sourceLocale,
23
- targetLocales: flags.language && flags.language.length > 0 ? flags.language : loaded.targetLocales,
20
+ targetLocales:
21
+ flags.language && flags.language.length > 0 ? flags.language : loaded.targetLocales,
24
22
  strategy: flags.strategy ?? loaded.strategy,
25
23
  adapters: flags.adapter
26
24
  ? loaded.adapters.filter((a: { name: string }) => a.name === flags.adapter)
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, afterEach } from 'vitest';
1
+ import { describe, expect, it, afterEach } from "vitest";
2
2
  import {
3
3
  detectFormat,
4
4
  color,
@@ -10,9 +10,9 @@ import {
10
10
  warning,
11
11
  info,
12
12
  keyValue,
13
- } from './format.js';
13
+ } from "./format.js";
14
14
 
15
- describe('detectFormat', () => {
15
+ describe("detectFormat", () => {
16
16
  const originalTty = process.stdout.isTTY;
17
17
  const originalEnv = { ...process.env };
18
18
 
@@ -24,94 +24,94 @@ describe('detectFormat', () => {
24
24
  Object.assign(process.env, originalEnv);
25
25
  });
26
26
 
27
- it('returns explicit format when provided', () => {
28
- expect(detectFormat('json')).toBe('json');
29
- expect(detectFormat('pretty')).toBe('pretty');
27
+ it("returns explicit format when provided", () => {
28
+ expect(detectFormat("json")).toBe("json");
29
+ expect(detectFormat("pretty")).toBe("pretty");
30
30
  });
31
31
 
32
- it('defaults to json when not a TTY', () => {
32
+ it("defaults to json when not a TTY", () => {
33
33
  Object.assign(process.stdout, { isTTY: false });
34
- expect(detectFormat()).toBe('json');
34
+ expect(detectFormat()).toBe("json");
35
35
  });
36
36
 
37
- it('defaults to pretty when TTY and no agent env', () => {
37
+ it("defaults to pretty when TTY and no agent env", () => {
38
38
  Object.assign(process.stdout, { isTTY: true });
39
- expect(detectFormat()).toBe('pretty');
39
+ expect(detectFormat()).toBe("pretty");
40
40
  });
41
41
 
42
- it('defaults to json when agent env var is set', () => {
42
+ it("defaults to json when agent env var is set", () => {
43
43
  Object.assign(process.stdout, { isTTY: true });
44
- process.env.CLAUDE_CODE = '1';
45
- expect(detectFormat()).toBe('json');
44
+ process.env.CLAUDE_CODE = "1";
45
+ expect(detectFormat()).toBe("json");
46
46
  });
47
47
  });
48
48
 
49
- describe('color', () => {
50
- it('returns bare text when not a TTY', () => {
51
- expect(color('hello', '\x1b[31m')).toBe('hello');
49
+ describe("color", () => {
50
+ it("returns bare text when not a TTY", () => {
51
+ expect(color("hello", "\x1b[31m")).toBe("hello");
52
52
  });
53
53
  });
54
54
 
55
- describe('drawTable', () => {
56
- it('renders a table with headers and rows', () => {
55
+ describe("drawTable", () => {
56
+ it("renders a table with headers and rows", () => {
57
57
  const table = drawTable(
58
- ['Name', 'Score'],
58
+ ["Name", "Score"],
59
59
  [
60
- ['Alice', '10'],
61
- ['Bob', '8'],
60
+ ["Alice", "10"],
61
+ ["Bob", "8"],
62
62
  ],
63
63
  );
64
- expect(table).toContain('Name');
65
- expect(table).toContain('Score');
66
- expect(table).toContain('Alice');
67
- expect(table).toContain('Bob');
64
+ expect(table).toContain("Name");
65
+ expect(table).toContain("Score");
66
+ expect(table).toContain("Alice");
67
+ expect(table).toContain("Bob");
68
68
  });
69
69
 
70
- it('handles empty rows', () => {
71
- const table = drawTable(['A'], []);
72
- expect(table).toContain('A');
70
+ it("handles empty rows", () => {
71
+ const table = drawTable(["A"], []);
72
+ expect(table).toContain("A");
73
73
  });
74
74
  });
75
75
 
76
- describe('banner', () => {
77
- it('includes the title', () => {
78
- expect(banner('Results')).toContain('Results');
76
+ describe("banner", () => {
77
+ it("includes the title", () => {
78
+ expect(banner("Results")).toContain("Results");
79
79
  });
80
80
  });
81
81
 
82
- describe('sectionHeader', () => {
83
- it('includes the label', () => {
84
- expect(sectionHeader('Missing keys')).toContain('Missing keys');
82
+ describe("sectionHeader", () => {
83
+ it("includes the label", () => {
84
+ expect(sectionHeader("Missing keys")).toContain("Missing keys");
85
85
  });
86
86
  });
87
87
 
88
- describe('success', () => {
89
- it('includes the text', () => {
90
- expect(success('Done')).toContain('Done');
88
+ describe("success", () => {
89
+ it("includes the text", () => {
90
+ expect(success("Done")).toContain("Done");
91
91
  });
92
92
  });
93
93
 
94
- describe('failure', () => {
95
- it('includes the text', () => {
96
- expect(failure('Failed')).toContain('Failed');
94
+ describe("failure", () => {
95
+ it("includes the text", () => {
96
+ expect(failure("Failed")).toContain("Failed");
97
97
  });
98
98
  });
99
99
 
100
- describe('warning', () => {
101
- it('includes the text', () => {
102
- expect(warning('Warn')).toContain('Warn');
100
+ describe("warning", () => {
101
+ it("includes the text", () => {
102
+ expect(warning("Warn")).toContain("Warn");
103
103
  });
104
104
  });
105
105
 
106
- describe('info', () => {
107
- it('returns dimmed text', () => {
108
- expect(info('note')).toBe('note');
106
+ describe("info", () => {
107
+ it("returns dimmed text", () => {
108
+ expect(info("note")).toBe("note");
109
109
  });
110
110
  });
111
111
 
112
- describe('keyValue', () => {
113
- it('formats key and value', () => {
114
- expect(keyValue('Name:', 'test')).toContain('Name:');
115
- expect(keyValue('Name:', 'test')).toContain('test');
112
+ describe("keyValue", () => {
113
+ it("formats key and value", () => {
114
+ expect(keyValue("Name:", "test")).toContain("Name:");
115
+ expect(keyValue("Name:", "test")).toContain("test");
116
116
  });
117
117
  });