assertloom 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +393 -2
  3. package/dist/ai/anthropic.d.ts +11 -0
  4. package/dist/ai/anthropic.js +68 -0
  5. package/dist/ai/anthropic.js.map +1 -0
  6. package/dist/ai/index.d.ts +12 -0
  7. package/dist/ai/index.js +23 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/ai/openai.d.ts +9 -0
  10. package/dist/ai/openai.js +57 -0
  11. package/dist/ai/openai.js.map +1 -0
  12. package/dist/ai/openaiCompatible.d.ts +13 -0
  13. package/dist/ai/openaiCompatible.js +62 -0
  14. package/dist/ai/openaiCompatible.js.map +1 -0
  15. package/dist/ai/pricing.d.ts +38 -0
  16. package/dist/ai/pricing.js +105 -0
  17. package/dist/ai/pricing.js.map +1 -0
  18. package/dist/ai/prompts.d.ts +27 -0
  19. package/dist/ai/prompts.js +89 -0
  20. package/dist/ai/prompts.js.map +1 -0
  21. package/dist/ai/provider.d.ts +26 -0
  22. package/dist/ai/provider.js +4 -0
  23. package/dist/ai/provider.js.map +1 -0
  24. package/dist/ai/redact.d.ts +5 -0
  25. package/dist/ai/redact.js +31 -0
  26. package/dist/ai/redact.js.map +1 -0
  27. package/dist/analyzer/dependencies.d.ts +16 -0
  28. package/dist/analyzer/dependencies.js +168 -0
  29. package/dist/analyzer/dependencies.js.map +1 -0
  30. package/dist/analyzer/format.d.ts +3 -0
  31. package/dist/analyzer/format.js +14 -0
  32. package/dist/analyzer/format.js.map +1 -0
  33. package/dist/analyzer/functions.d.ts +4 -0
  34. package/dist/analyzer/functions.js +324 -0
  35. package/dist/analyzer/functions.js.map +1 -0
  36. package/dist/analyzer/index.d.ts +5 -0
  37. package/dist/analyzer/index.js +29 -0
  38. package/dist/analyzer/index.js.map +1 -0
  39. package/dist/analyzer/parser.d.ts +11 -0
  40. package/dist/analyzer/parser.js +38 -0
  41. package/dist/analyzer/parser.js.map +1 -0
  42. package/dist/analyzer/usage.d.ts +16 -0
  43. package/dist/analyzer/usage.js +159 -0
  44. package/dist/analyzer/usage.js.map +1 -0
  45. package/dist/ci/report.d.ts +88 -0
  46. package/dist/ci/report.js +139 -0
  47. package/dist/ci/report.js.map +1 -0
  48. package/dist/ci/run.d.ts +70 -0
  49. package/dist/ci/run.js +198 -0
  50. package/dist/ci/run.js.map +1 -0
  51. package/dist/ci/select.d.ts +58 -0
  52. package/dist/ci/select.js +238 -0
  53. package/dist/ci/select.js.map +1 -0
  54. package/dist/cli/commands/analyze.d.ts +2 -0
  55. package/dist/cli/commands/analyze.js +49 -0
  56. package/dist/cli/commands/analyze.js.map +1 -0
  57. package/dist/cli/commands/ci.d.ts +37 -0
  58. package/dist/cli/commands/ci.js +258 -0
  59. package/dist/cli/commands/ci.js.map +1 -0
  60. package/dist/cli/commands/generate.d.ts +3 -0
  61. package/dist/cli/commands/generate.js +201 -0
  62. package/dist/cli/commands/generate.js.map +1 -0
  63. package/dist/cli/commands/run.d.ts +4 -0
  64. package/dist/cli/commands/run.js +53 -0
  65. package/dist/cli/commands/run.js.map +1 -0
  66. package/dist/cli/index.d.ts +2 -0
  67. package/dist/cli/index.js +16 -0
  68. package/dist/cli/index.js.map +1 -0
  69. package/dist/cli/program.d.ts +2 -0
  70. package/dist/cli/program.js +20 -0
  71. package/dist/cli/program.js.map +1 -0
  72. package/dist/cli/report.d.ts +3 -0
  73. package/dist/cli/report.js +36 -0
  74. package/dist/cli/report.js.map +1 -0
  75. package/dist/cli/validateFile.d.ts +5 -0
  76. package/dist/cli/validateFile.js +43 -0
  77. package/dist/cli/validateFile.js.map +1 -0
  78. package/dist/errors.d.ts +4 -0
  79. package/dist/errors.js +8 -0
  80. package/dist/errors.js.map +1 -0
  81. package/dist/generator/diff.d.ts +5 -0
  82. package/dist/generator/diff.js +56 -0
  83. package/dist/generator/diff.js.map +1 -0
  84. package/dist/generator/importPlan.d.ts +15 -0
  85. package/dist/generator/importPlan.js +62 -0
  86. package/dist/generator/importPlan.js.map +1 -0
  87. package/dist/generator/index.d.ts +5 -0
  88. package/dist/generator/index.js +6 -0
  89. package/dist/generator/index.js.map +1 -0
  90. package/dist/generator/mocks.d.ts +48 -0
  91. package/dist/generator/mocks.js +356 -0
  92. package/dist/generator/mocks.js.map +1 -0
  93. package/dist/generator/schema.d.ts +40 -0
  94. package/dist/generator/schema.js +15 -0
  95. package/dist/generator/schema.js.map +1 -0
  96. package/dist/generator/testGenerator.d.ts +36 -0
  97. package/dist/generator/testGenerator.js +85 -0
  98. package/dist/generator/testGenerator.js.map +1 -0
  99. package/dist/generator/testWriter.d.ts +22 -0
  100. package/dist/generator/testWriter.js +93 -0
  101. package/dist/generator/testWriter.js.map +1 -0
  102. package/dist/generator/validate.d.ts +31 -0
  103. package/dist/generator/validate.js +180 -0
  104. package/dist/generator/validate.js.map +1 -0
  105. package/dist/repair/classifyFailure.d.ts +11 -0
  106. package/dist/repair/classifyFailure.js +26 -0
  107. package/dist/repair/classifyFailure.js.map +1 -0
  108. package/dist/repair/index.d.ts +2 -0
  109. package/dist/repair/index.js +3 -0
  110. package/dist/repair/index.js.map +1 -0
  111. package/dist/repair/repairLoop.d.ts +74 -0
  112. package/dist/repair/repairLoop.js +219 -0
  113. package/dist/repair/repairLoop.js.map +1 -0
  114. package/dist/repair/schema.d.ts +17 -0
  115. package/dist/repair/schema.js +14 -0
  116. package/dist/repair/schema.js.map +1 -0
  117. package/dist/runner/coverage.d.ts +5 -0
  118. package/dist/runner/coverage.js +42 -0
  119. package/dist/runner/coverage.js.map +1 -0
  120. package/dist/runner/index.d.ts +5 -0
  121. package/dist/runner/index.js +5 -0
  122. package/dist/runner/index.js.map +1 -0
  123. package/dist/runner/jestConfig.d.ts +11 -0
  124. package/dist/runner/jestConfig.js +103 -0
  125. package/dist/runner/jestConfig.js.map +1 -0
  126. package/dist/runner/jestRunner.d.ts +17 -0
  127. package/dist/runner/jestRunner.js +154 -0
  128. package/dist/runner/jestRunner.js.map +1 -0
  129. package/dist/runner/networkGuard.d.ts +4 -0
  130. package/dist/runner/networkGuard.js +55 -0
  131. package/dist/runner/networkGuard.js.map +1 -0
  132. package/dist/runner/project.d.ts +30 -0
  133. package/dist/runner/project.js +86 -0
  134. package/dist/runner/project.js.map +1 -0
  135. package/dist/runner/results.d.ts +32 -0
  136. package/dist/runner/results.js +58 -0
  137. package/dist/runner/results.js.map +1 -0
  138. package/dist/runner/types.d.ts +39 -0
  139. package/dist/runner/types.js +2 -0
  140. package/dist/runner/types.js.map +1 -0
  141. package/dist/runner/vitest.d.ts +24 -0
  142. package/dist/runner/vitest.js +105 -0
  143. package/dist/runner/vitest.js.map +1 -0
  144. package/dist/types/index.d.ts +96 -0
  145. package/dist/types/index.js +2 -0
  146. package/dist/types/index.js.map +1 -0
  147. package/package.json +67 -4
@@ -0,0 +1,62 @@
1
+ import OpenAI from "openai";
2
+ import { zodResponseFormat } from "openai/helpers/zod";
3
+ import { ProviderError } from "./provider.js";
4
+ /**
5
+ * Any server that speaks the OpenAI Chat Completions API with JSON-schema response formats:
6
+ * Ollama, LM Studio, vLLM, llama.cpp, OpenRouter, and others.
7
+ */
8
+ export class OpenAICompatibleProvider {
9
+ model;
10
+ baseURL;
11
+ name = "openai-compatible";
12
+ client;
13
+ constructor(model, baseURL, apiKey = process.env.ASSERTLOOM_API_KEY) {
14
+ this.model = model;
15
+ this.baseURL = baseURL;
16
+ if (!model)
17
+ throw new ProviderError("--provider openai-compatible needs a model: pass --model or set ASSERTLOOM_MODEL.");
18
+ if (!baseURL) {
19
+ throw new ProviderError("--provider openai-compatible needs a server URL: pass --base-url or set ASSERTLOOM_BASE_URL (for example http://localhost:11434/v1).");
20
+ }
21
+ // Local servers usually ignore the key, but the SDK requires one.
22
+ this.client = new OpenAI({ apiKey: apiKey || "not-needed", baseURL, timeout: 300_000, maxRetries: 0 });
23
+ }
24
+ async generateStructured(request) {
25
+ let completion;
26
+ try {
27
+ completion = await this.client.chat.completions.create({
28
+ model: this.model,
29
+ messages: [
30
+ { role: "system", content: request.system },
31
+ { role: "user", content: request.prompt },
32
+ ],
33
+ response_format: zodResponseFormat(request.schema, request.schemaName),
34
+ max_tokens: request.maxOutputTokens,
35
+ });
36
+ }
37
+ catch (error) {
38
+ const status = error instanceof OpenAI.APIError ? ` (${error.status ?? "no status"})` : "";
39
+ throw new ProviderError(`Request to ${this.baseURL} failed${status}: ${error.message}`);
40
+ }
41
+ const choice = completion.choices[0];
42
+ if (choice?.finish_reason === "length")
43
+ throw new ProviderError("The model's response was cut off (max_tokens).");
44
+ let json;
45
+ try {
46
+ json = JSON.parse(choice?.message.content ?? "");
47
+ }
48
+ catch {
49
+ throw new ProviderError(`${this.model} returned no JSON output. Check that the server supports JSON-schema response formats.`);
50
+ }
51
+ const parsed = request.schema.safeParse(json);
52
+ if (!parsed.success) {
53
+ throw new ProviderError(`${this.model} returned output that does not match the ${request.schemaName} schema: ${parsed.error.message}`);
54
+ }
55
+ return {
56
+ data: parsed.data,
57
+ model: completion.model || this.model,
58
+ usage: { inputTokens: completion.usage?.prompt_tokens ?? 0, outputTokens: completion.usage?.completion_tokens ?? 0 },
59
+ };
60
+ }
61
+ }
62
+ //# sourceMappingURL=openaiCompatible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiCompatible.js","sourceRoot":"","sources":["../../src/ai/openaiCompatible.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { TokenUsage } from "./provider.js";
2
+ /**
3
+ * Standard prices in USD per 1M tokens, checked on 2026-10-31 at https://platform.openai.com/docs/pricing and
4
+ * https://platform.claude.com/docs/en/about-claude/pricing. Claude Haiku 5.5 is priced for prompts up to 100K tokens.
5
+ */
6
+ export declare const PRICES_CHECKED = "2026-10-31";
7
+ /** Prices for a model name, including dated snapshots such as `gpt-5.4-mini-2026-03-17` or `claude-haiku-4-5-20251001`. */
8
+ export declare function priceFor(model: string): {
9
+ input: number;
10
+ output: number;
11
+ } | undefined;
12
+ export declare function estimateCost(model: string, usage: TokenUsage): number | undefined;
13
+ export declare function formatUsd(amount: number): string;
14
+ /** Without known prices, spending is capped by tokens instead. */
15
+ export declare const UNPRICED_TOKEN_LIMIT = 500000;
16
+ /**
17
+ * A hard spending limit for one command. Each model call is checked against its worst case
18
+ * (full prompt plus the maximum output) before it is made. Share one instance to share the limit
19
+ * across several files.
20
+ */
21
+ export declare class Budget {
22
+ readonly model: string;
23
+ readonly maxUsd: number;
24
+ /** Token cap used when the model has no known price. */
25
+ readonly maxTokens: number;
26
+ readonly usage: TokenUsage;
27
+ constructor(model: string, maxUsd: number,
28
+ /** Token cap used when the model has no known price. */
29
+ maxTokens?: number);
30
+ get priced(): boolean;
31
+ get spentUsd(): number | undefined;
32
+ /** The limit that applies to this model, for messages. */
33
+ describeLimit(): string;
34
+ allows(worstCase: TokenUsage): boolean;
35
+ add(usage: TokenUsage): void;
36
+ }
37
+ /** Rough upper bound on tokens for a prompt: about 3 characters per token for code. */
38
+ export declare function promptTokens(...parts: string[]): number;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Standard prices in USD per 1M tokens, checked on 2026-10-31 at https://platform.openai.com/docs/pricing and
3
+ * https://platform.claude.com/docs/en/about-claude/pricing. Claude Haiku 5.5 is priced for prompts up to 100K tokens.
4
+ */
5
+ export const PRICES_CHECKED = "2026-10-31";
6
+ const PRICES = {
7
+ "claude-fable-5-1": { input: 10, output: 50 },
8
+ "claude-fable-5": { input: 10, output: 50 },
9
+ "claude-opus-5-5": { input: 4, output: 20 },
10
+ "claude-opus-5": { input: 5, output: 25 },
11
+ "claude-opus-4-8": { input: 5, output: 25 },
12
+ "claude-opus-4-7": { input: 5, output: 25 },
13
+ "claude-opus-4-6": { input: 5, output: 25 },
14
+ "claude-opus-4-5": { input: 5, output: 25 },
15
+ "claude-sonnet-5-5": { input: 2, output: 10 },
16
+ "claude-sonnet-5": { input: 2, output: 10 },
17
+ "claude-sonnet-4-6": { input: 3, output: 15 },
18
+ "claude-sonnet-4-5": { input: 3, output: 15 },
19
+ "claude-haiku-5-5": { input: 0.1, output: 0.5 },
20
+ "claude-haiku-4-5": { input: 1, output: 5 },
21
+ "gpt-6-astra": { input: 10, output: 50 },
22
+ "gpt-6.1-sol": { input: 2, output: 10 },
23
+ "gpt-6.1-terra": { input: 1, output: 5 },
24
+ "gpt-6-luna": { input: 0.1, output: 0.5 },
25
+ "gpt-6-sol": { input: 2, output: 10 },
26
+ "gpt-5.6-sol": { input: 4, output: 20 },
27
+ "gpt-5.6-terra": { input: 2, output: 12 },
28
+ "gpt-5.6-luna": { input: 0.2, output: 1.2 },
29
+ "gpt-5.5": { input: 5, output: 30 },
30
+ "gpt-5.4": { input: 2.5, output: 15 },
31
+ "gpt-5.4-mini": { input: 0.75, output: 4.5 },
32
+ "gpt-5.4-nano": { input: 0.2, output: 1.25 },
33
+ "gpt-5.2": { input: 1.75, output: 14 },
34
+ "gpt-5.1": { input: 1.25, output: 10 },
35
+ "gpt-5": { input: 1.25, output: 10 },
36
+ "gpt-5-mini": { input: 0.25, output: 2 },
37
+ "gpt-5-nano": { input: 0.05, output: 0.4 },
38
+ "gpt-4.1": { input: 2, output: 8 },
39
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
40
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
41
+ "gpt-4o": { input: 2.5, output: 10 },
42
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
43
+ };
44
+ /** Prices for a model name, including dated snapshots such as `gpt-5.4-mini-2026-03-17` or `claude-haiku-4-5-20251001`. */
45
+ export function priceFor(model) {
46
+ if (PRICES[model])
47
+ return PRICES[model];
48
+ const snapshot = /^(.*)-(?:\d{4}-\d{2}-\d{2}|\d{8})$/.exec(model);
49
+ return snapshot ? PRICES[snapshot[1]] : undefined;
50
+ }
51
+ export function estimateCost(model, usage) {
52
+ const price = priceFor(model);
53
+ if (!price)
54
+ return undefined;
55
+ return (usage.inputTokens * price.input + usage.outputTokens * price.output) / 1_000_000;
56
+ }
57
+ export function formatUsd(amount) {
58
+ return amount >= 0.01 ? `$${amount.toFixed(2)}` : `$${amount.toFixed(4)}`;
59
+ }
60
+ /** Without known prices, spending is capped by tokens instead. */
61
+ export const UNPRICED_TOKEN_LIMIT = 500_000;
62
+ /**
63
+ * A hard spending limit for one command. Each model call is checked against its worst case
64
+ * (full prompt plus the maximum output) before it is made. Share one instance to share the limit
65
+ * across several files.
66
+ */
67
+ export class Budget {
68
+ model;
69
+ maxUsd;
70
+ maxTokens;
71
+ usage = { inputTokens: 0, outputTokens: 0 };
72
+ constructor(model, maxUsd,
73
+ /** Token cap used when the model has no known price. */
74
+ maxTokens = UNPRICED_TOKEN_LIMIT) {
75
+ this.model = model;
76
+ this.maxUsd = maxUsd;
77
+ this.maxTokens = maxTokens;
78
+ }
79
+ get priced() {
80
+ return priceFor(this.model) !== undefined;
81
+ }
82
+ get spentUsd() {
83
+ return estimateCost(this.model, this.usage);
84
+ }
85
+ /** The limit that applies to this model, for messages. */
86
+ describeLimit() {
87
+ return this.priced ? formatUsd(this.maxUsd) : `${this.maxTokens.toLocaleString("en-US")} tokens (no known price for ${this.model})`;
88
+ }
89
+ allows(worstCase) {
90
+ const projected = { inputTokens: this.usage.inputTokens + worstCase.inputTokens, outputTokens: this.usage.outputTokens + worstCase.outputTokens };
91
+ const cost = estimateCost(this.model, projected);
92
+ if (cost === undefined)
93
+ return projected.inputTokens + projected.outputTokens <= this.maxTokens;
94
+ return cost <= this.maxUsd;
95
+ }
96
+ add(usage) {
97
+ this.usage.inputTokens += usage.inputTokens;
98
+ this.usage.outputTokens += usage.outputTokens;
99
+ }
100
+ }
101
+ /** Rough upper bound on tokens for a prompt: about 3 characters per token for code. */
102
+ export function promptTokens(...parts) {
103
+ return Math.ceil(parts.reduce((n, p) => n + p.length, 0) / 3);
104
+ }
105
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../../src/ai/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Binding } from "../generator/importPlan.js";
2
+ import type { FileAnalysis } from "../types/index.js";
3
+ export declare const GENERATION_SYSTEM_PROMPT = "You are Assertloom, a tool that writes Jest unit tests for JavaScript and TypeScript code.\n\nFor each target function you are given, return test cases. Each case has a short name (the `it` title), a category, and a body: the statements that go inside the test callback.\n\nRules:\n- Call target functions only through the binding names listed. They are already imported; Assertloom writes the imports, describe blocks, and it() wrappers.\n- Do not write import or require statements, describe/it/test wrappers, or jest.mock calls.\n- Assert the behavior of the source as written. Work out expected values by reading the code; never guess. If you cannot be sure of an exact value, assert something you can verify (type, length, ordering, a relationship between outputs).\n- Tests must be deterministic: no real network or file system access, real timers, Date.now(), or Math.random().\n- Dependencies listed under \"Mocks\" are already mocked by Assertloom and reset before every test, so every jest.fn() returns undefined until the test configures it. In each test, set up exactly what the code path needs with mockResolvedValue, mockRejectedValue, mockReturnValue, mockReturnValueOnce, or mockImplementation. In TypeScript, wrap mocks with jest.mocked(...) and cast fake values with `as any` when they are partial.\n- Shape fake values like the real API the source expects (for example, fetch responses need `ok`, `status`, and `json: async () => data`).\n- Where it matters, assert how dependencies were called with toHaveBeenCalledWith or toHaveBeenCalledTimes.\n- Functions in the source call each other directly, so spying on them has no effect, and `module.exports`/`exports` in a test refer to the test file itself. Control behavior through the mocked dependencies and real inputs instead.\n- Tests must pass on every operating system. Never hardcode path separators in expected values; build expected paths with the same functions the source uses (such as path.join, when it is imported in the test file) or use expect.stringContaining.\n- For async functions, use await, or await expect(...).rejects for rejections.\n- Use toBeCloseTo for floating-point results and toEqual for objects and arrays.\n- For generators, collect values with Array.from() or spread syntax.\n- In TypeScript, arguments must match the declared parameter types. If an error case needs an invalid argument, cast it with `as any`.\n- Cover each function with normal cases (typical input) and edge cases (empty values, zero, negative numbers, boundaries, unicode where relevant). Add error cases when the source throws or rejects, using toThrow / rejects.toThrow and matching the message when the source shows it.\n- For functions that use mocked dependencies, also cover failure paths: make the dependency reject or return an error result, then assert what the source does (rejects with its own message, rethrows, or falls back to a default).\n- Write 3 to 6 cases per function. Each case should check one behavior with a few focused expect() calls.";
4
+ export declare const REPAIR_SYSTEM_PROMPT = "You are Assertloom, a tool that repairs failing Jest tests it generated. You cannot change the source code; only the tests.\n\nFor each failing test you are given, return exactly one repair with one of these actions:\n- fix: the test is wrong (a miscalculated expected value, a wrong assumption about behavior, a missing await, a mock that was not configured or returns the wrong shape \u2014 \"Cannot read properties of undefined\" often means a mock still returns undefined). Return a corrected body that keeps testing the same behavior, with expected values derived from the source.\n- skip: the test's expectation matches what the function clearly intends (its name, documentation, or error messages), but the source does something else. This is likely a bug in the source; do not rewrite the test to match buggy behavior. Explain the suspected bug in the reason.\n- remove: the test cannot be made deterministic or tests something outside the function.\n\nPrefer fix. Use the failure message: \"Expected\"/\"Received\" lines show what the code actually returned.\n\nBody rules for fix:\n- Call target functions only through the binding names listed. They are already imported; Assertloom writes the imports, describe blocks, and it() wrappers.\n- Do not write import or require statements, describe/it/test wrappers, or jest.mock calls.\n- Assert the behavior of the source as written. Work out expected values by reading the code; never guess. If you cannot be sure of an exact value, assert something you can verify (type, length, ordering, a relationship between outputs).\n- Tests must be deterministic: no real network or file system access, real timers, Date.now(), or Math.random().\n- Dependencies listed under \"Mocks\" are already mocked by Assertloom and reset before every test, so every jest.fn() returns undefined until the test configures it. In each test, set up exactly what the code path needs with mockResolvedValue, mockRejectedValue, mockReturnValue, mockReturnValueOnce, or mockImplementation. In TypeScript, wrap mocks with jest.mocked(...) and cast fake values with `as any` when they are partial.\n- Shape fake values like the real API the source expects (for example, fetch responses need `ok`, `status`, and `json: async () => data`).\n- Where it matters, assert how dependencies were called with toHaveBeenCalledWith or toHaveBeenCalledTimes.\n- Functions in the source call each other directly, so spying on them has no effect, and `module.exports`/`exports` in a test refer to the test file itself. Control behavior through the mocked dependencies and real inputs instead.\n- Tests must pass on every operating system. Never hardcode path separators in expected values; build expected paths with the same functions the source uses (such as path.join, when it is imported in the test file) or use expect.stringContaining.\n- For async functions, use await, or await expect(...).rejects for rejections.\n- Use toBeCloseTo for floating-point results and toEqual for objects and arrays.\n- For generators, collect values with Array.from() or spread syntax.\n- In TypeScript, arguments must match the declared parameter types. If an error case needs an invalid argument, cast it with `as any`.";
5
+ export interface GenerationPromptInput {
6
+ analysis: FileAnalysis;
7
+ code: string;
8
+ bindings: Binding[];
9
+ testFileName: string;
10
+ mockSummary?: string;
11
+ }
12
+ export declare function buildGenerationPrompt({ analysis, code, bindings, testFileName, mockSummary }: GenerationPromptInput): string;
13
+ export declare const MAX_FAILURE_MESSAGE_CHARS = 1200;
14
+ export interface RepairTarget {
15
+ binding: Binding;
16
+ testName: string;
17
+ body: string;
18
+ failureCategory: string;
19
+ message: string;
20
+ }
21
+ export declare function buildRepairPrompt(input: {
22
+ analysis: FileAnalysis;
23
+ code: string;
24
+ targets: RepairTarget[];
25
+ attempt: number;
26
+ mockSummary?: string;
27
+ }): string;
@@ -0,0 +1,89 @@
1
+ import { formatSignature } from "../analyzer/format.js";
2
+ const BODY_RULES = `- Call target functions only through the binding names listed. They are already imported; Assertloom writes the imports, describe blocks, and it() wrappers.
3
+ - Do not write import or require statements, describe/it/test wrappers, or jest.mock calls.
4
+ - Assert the behavior of the source as written. Work out expected values by reading the code; never guess. If you cannot be sure of an exact value, assert something you can verify (type, length, ordering, a relationship between outputs).
5
+ - Tests must be deterministic: no real network or file system access, real timers, Date.now(), or Math.random().
6
+ - Dependencies listed under "Mocks" are already mocked by Assertloom and reset before every test, so every jest.fn() returns undefined until the test configures it. In each test, set up exactly what the code path needs with mockResolvedValue, mockRejectedValue, mockReturnValue, mockReturnValueOnce, or mockImplementation. In TypeScript, wrap mocks with jest.mocked(...) and cast fake values with \`as any\` when they are partial.
7
+ - Shape fake values like the real API the source expects (for example, fetch responses need \`ok\`, \`status\`, and \`json: async () => data\`).
8
+ - Where it matters, assert how dependencies were called with toHaveBeenCalledWith or toHaveBeenCalledTimes.
9
+ - Functions in the source call each other directly, so spying on them has no effect, and \`module.exports\`/\`exports\` in a test refer to the test file itself. Control behavior through the mocked dependencies and real inputs instead.
10
+ - Tests must pass on every operating system. Never hardcode path separators in expected values; build expected paths with the same functions the source uses (such as path.join, when it is imported in the test file) or use expect.stringContaining.
11
+ - For async functions, use await, or await expect(...).rejects for rejections.
12
+ - Use toBeCloseTo for floating-point results and toEqual for objects and arrays.
13
+ - For generators, collect values with Array.from() or spread syntax.
14
+ - In TypeScript, arguments must match the declared parameter types. If an error case needs an invalid argument, cast it with \`as any\`.`;
15
+ export const GENERATION_SYSTEM_PROMPT = `You are Assertloom, a tool that writes Jest unit tests for JavaScript and TypeScript code.
16
+
17
+ For each target function you are given, return test cases. Each case has a short name (the \`it\` title), a category, and a body: the statements that go inside the test callback.
18
+
19
+ Rules:
20
+ ${BODY_RULES}
21
+ - Cover each function with normal cases (typical input) and edge cases (empty values, zero, negative numbers, boundaries, unicode where relevant). Add error cases when the source throws or rejects, using toThrow / rejects.toThrow and matching the message when the source shows it.
22
+ - For functions that use mocked dependencies, also cover failure paths: make the dependency reject or return an error result, then assert what the source does (rejects with its own message, rethrows, or falls back to a default).
23
+ - Write 3 to 6 cases per function. Each case should check one behavior with a few focused expect() calls.`;
24
+ export const REPAIR_SYSTEM_PROMPT = `You are Assertloom, a tool that repairs failing Jest tests it generated. You cannot change the source code; only the tests.
25
+
26
+ For each failing test you are given, return exactly one repair with one of these actions:
27
+ - fix: the test is wrong (a miscalculated expected value, a wrong assumption about behavior, a missing await, a mock that was not configured or returns the wrong shape — "Cannot read properties of undefined" often means a mock still returns undefined). Return a corrected body that keeps testing the same behavior, with expected values derived from the source.
28
+ - skip: the test's expectation matches what the function clearly intends (its name, documentation, or error messages), but the source does something else. This is likely a bug in the source; do not rewrite the test to match buggy behavior. Explain the suspected bug in the reason.
29
+ - remove: the test cannot be made deterministic or tests something outside the function.
30
+
31
+ Prefer fix. Use the failure message: "Expected"/"Received" lines show what the code actually returned.
32
+
33
+ Body rules for fix:
34
+ ${BODY_RULES}`;
35
+ function mocksSection(mockSummary) {
36
+ return mockSummary ? `\nMocks (already set up in the test file):\n${mockSummary}\n` : "\nMocks: none. The source has no dependencies that need mocking.\n";
37
+ }
38
+ export function buildGenerationPrompt({ analysis, code, bindings, testFileName, mockSummary }) {
39
+ const language = analysis.language === "typescript" ? "typescript" : "javascript";
40
+ const targets = bindings
41
+ .map(({ binding, fn }) => {
42
+ const notes = [
43
+ binding !== fn.name ? `declared as \`${fn.name}\` in the source` : "",
44
+ fn.throws ? "throws in its own body" : "",
45
+ `defined at line ${fn.line}`,
46
+ ].filter(Boolean);
47
+ return `- \`${binding}\`: \`${formatSignature(fn, binding)}\` (${notes.join("; ")})`;
48
+ })
49
+ .join("\n");
50
+ return `Write Jest tests for the target functions in \`${analysis.file}\`.
51
+
52
+ Test file: \`${testFileName}\` (${language}, Jest globals available)
53
+
54
+ Target functions (use these binding names exactly):
55
+ ${targets}
56
+ ${mocksSection(mockSummary)}
57
+ Source of \`${analysis.file}\`:
58
+ \`\`\`${language}
59
+ ${code}
60
+ \`\`\``;
61
+ }
62
+ export const MAX_FAILURE_MESSAGE_CHARS = 1200;
63
+ export function buildRepairPrompt(input) {
64
+ const language = input.analysis.language === "typescript" ? "typescript" : "javascript";
65
+ const failures = input.targets
66
+ .map((t, i) => {
67
+ const message = t.message.length > MAX_FAILURE_MESSAGE_CHARS ? `${t.message.slice(0, MAX_FAILURE_MESSAGE_CHARS)}\n…(truncated)` : t.message;
68
+ return `### ${i + 1}. \`${t.binding.binding}\` › ${JSON.stringify(t.testName)}
69
+ Signature: \`${formatSignature(t.binding.fn, t.binding.binding)}\`
70
+ Failure (${t.failureCategory}):
71
+ \`\`\`
72
+ ${message}
73
+ \`\`\`
74
+ Current body:
75
+ \`\`\`${language}
76
+ ${t.body}
77
+ \`\`\``;
78
+ })
79
+ .join("\n\n");
80
+ return `Repair attempt ${input.attempt}: ${input.targets.length} generated test${input.targets.length === 1 ? "" : "s"} in the tests for \`${input.analysis.file}\` failed.
81
+
82
+ ${failures}
83
+ ${mocksSection(input.mockSummary)}
84
+ Source of \`${input.analysis.file}\` (read-only):
85
+ \`\`\`${language}
86
+ ${input.code}
87
+ \`\`\``;
88
+ }
89
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { z } from "zod";
2
+ import { UserError } from "../errors.js";
3
+ export declare class ProviderError extends UserError {
4
+ }
5
+ export interface StructuredRequest<T> {
6
+ system: string;
7
+ prompt: string;
8
+ schema: z.ZodType<T>;
9
+ schemaName: string;
10
+ maxOutputTokens: number;
11
+ }
12
+ export interface TokenUsage {
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ }
16
+ export interface StructuredResponse<T> {
17
+ data: T;
18
+ model: string;
19
+ usage: TokenUsage;
20
+ }
21
+ /** A model that returns JSON validated against a Zod schema. Implement this to add a provider. */
22
+ export interface LLMProvider {
23
+ readonly name: string;
24
+ readonly model: string;
25
+ generateStructured<T>(request: StructuredRequest<T>): Promise<StructuredResponse<T>>;
26
+ }
@@ -0,0 +1,4 @@
1
+ import { UserError } from "../errors.js";
2
+ export class ProviderError extends UserError {
3
+ }
4
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,OAAO,aAAc,SAAQ,SAAS;CAAG"}
@@ -0,0 +1,5 @@
1
+ /** Replaces likely credentials with a placeholder before code leaves the machine. */
2
+ export declare function redactSecrets(code: string): {
3
+ code: string;
4
+ redactions: number;
5
+ };
@@ -0,0 +1,31 @@
1
+ const SECRET_PATTERNS = [
2
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
3
+ /\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}/g,
4
+ /\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
5
+ /\bBearer\s+[A-Za-z0-9._~+/-]{16,}=*/g,
6
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
7
+ /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g,
8
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/g,
9
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
10
+ /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@'"`]+:[^\s@'"`]+@/gi,
11
+ ];
12
+ const ASSIGNED_SECRET = /\b([A-Za-z0-9_]*(?:secret|password|passwd|api_?key|token|credential|authorization)[A-Za-z0-9_]*["']?\s*(?:[:=]|===?|!==?)\s*)(["'`])((?:(?!\2)[^\n\\]|\\.){8,})\2/gi;
13
+ /** Replaces likely credentials with a placeholder before code leaves the machine. */
14
+ export function redactSecrets(code) {
15
+ let redactions = 0;
16
+ let result = code;
17
+ for (const pattern of SECRET_PATTERNS) {
18
+ result = result.replace(pattern, () => {
19
+ redactions++;
20
+ return "<redacted>";
21
+ });
22
+ }
23
+ result = result.replace(ASSIGNED_SECRET, (match, prefix, quote, value) => {
24
+ if (value.includes("<redacted>"))
25
+ return match;
26
+ redactions++;
27
+ return `${prefix}${quote}<redacted>${quote}`;
28
+ });
29
+ return { code: result, redactions };
30
+ }
31
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/ai/redact.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAa;IAChC,6EAA6E;IAC7E,yCAAyC;IACzC,gDAAgD;IAChD,sCAAsC;IACtC,gCAAgC;IAChC,iCAAiC;IACjC,iCAAiC;IACjC,4BAA4B;IAC5B,oDAAoD;CACrD,CAAC;AAEF,MAAM,eAAe,GACnB,qKAAqK,CAAC;AAExK,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,MAAM,GAAG,IAAI,CAAC;IAElB,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACtC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE;YACpC,UAAU,EAAE,CAAC;YACb,OAAO,YAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAc,EAAE,KAAa,EAAE,KAAa,EAAE,EAAE;QAC/F,IAAI,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/C,UAAU,EAAE,CAAC;QACb,OAAO,GAAG,MAAM,GAAG,KAAK,aAAa,KAAK,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { File, Node } from "@babel/types";
2
+ import type { DependencySummary, ImportCategory, ImportInfo } from "../types/index.js";
3
+ export interface ImportScan {
4
+ imports: ImportInfo[];
5
+ /** Top-level runtime bindings created by imports, keyed by local name. */
6
+ bindings: Map<string, string>;
7
+ /** `require()` and `import()` call nodes, mapped to the module they load. */
8
+ callSites: Map<Node, string>;
9
+ usesCommonJsExports: boolean;
10
+ }
11
+ export declare function classifySource(source: string): {
12
+ category: ImportCategory;
13
+ packageName?: string;
14
+ };
15
+ export declare function scanImports(ast: File): ImportScan;
16
+ export declare function summarizeDependencies(imports: ImportInfo[]): DependencySummary;
@@ -0,0 +1,168 @@
1
+ import { builtinModules } from "node:module";
2
+ import traverse from "@babel/traverse";
3
+ export function classifySource(source) {
4
+ if (source.startsWith(".") || source.startsWith("/") || source.startsWith("#")) {
5
+ return { category: "local" };
6
+ }
7
+ if (source.startsWith("node:") || builtinModules.includes(source)) {
8
+ return { category: "builtin" };
9
+ }
10
+ const segments = source.split("/");
11
+ const packageName = source.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
12
+ return { category: "external", packageName };
13
+ }
14
+ function stringValue(node) {
15
+ if (node?.type === "StringLiteral")
16
+ return node.value;
17
+ if (node?.type === "TemplateLiteral" && node.expressions.length === 0)
18
+ return node.quasis[0].value.cooked ?? undefined;
19
+ return undefined;
20
+ }
21
+ function moduleName(node) {
22
+ return node.type === "StringLiteral" ? node.value : node.type === "Identifier" ? node.name : "";
23
+ }
24
+ function isRequireCall(path) {
25
+ const callee = path.node.callee;
26
+ return callee.type === "Identifier" && callee.name === "require" && !path.scope.hasBinding("require");
27
+ }
28
+ function isCommonJsExportTarget(node) {
29
+ if (node.type !== "MemberExpression")
30
+ return false;
31
+ if (node.object.type === "Identifier") {
32
+ return (node.object.name === "exports" ||
33
+ (node.object.name === "module" && node.property.type === "Identifier" && node.property.name === "exports"));
34
+ }
35
+ return isCommonJsExportTarget(node.object);
36
+ }
37
+ export function scanImports(ast) {
38
+ const imports = [];
39
+ const bindings = new Map();
40
+ const callSites = new Map();
41
+ let usesCommonJsExports = false;
42
+ const add = (source, info) => {
43
+ imports.push({ source, ...classifySource(source), ...info });
44
+ };
45
+ traverse(ast, {
46
+ ImportDeclaration(path) {
47
+ const node = path.node;
48
+ const source = node.source.value;
49
+ const typeOnly = node.importKind === "type" ||
50
+ (node.specifiers.length > 0 && node.specifiers.every((s) => s.type === "ImportSpecifier" && s.importKind === "type"));
51
+ const specifiers = node.specifiers.map((s) => {
52
+ if (s.type === "ImportDefaultSpecifier")
53
+ return { kind: "default", imported: "default", local: s.local.name };
54
+ if (s.type === "ImportNamespaceSpecifier")
55
+ return { kind: "namespace", imported: "*", local: s.local.name };
56
+ return { kind: "named", imported: moduleName(s.imported), local: s.local.name };
57
+ });
58
+ if (!typeOnly) {
59
+ for (const s of node.specifiers) {
60
+ if (s.type === "ImportSpecifier" && s.importKind === "type")
61
+ continue;
62
+ bindings.set(s.local.name, source);
63
+ }
64
+ }
65
+ add(source, { kind: "import", typeOnly, specifiers, line: node.loc?.start.line ?? 0 });
66
+ },
67
+ ExportNamedDeclaration(path) {
68
+ const node = path.node;
69
+ if (!node.source)
70
+ return;
71
+ const specifiers = node.specifiers.map((s) => s.type === "ExportNamespaceSpecifier"
72
+ ? { kind: "namespace", imported: "*", local: moduleName(s.exported) }
73
+ : { kind: "named", imported: moduleName(s.type === "ExportSpecifier" ? s.local : s.exported), local: moduleName(s.exported) });
74
+ add(node.source.value, {
75
+ kind: "re-export",
76
+ typeOnly: node.exportKind === "type",
77
+ specifiers,
78
+ line: node.loc?.start.line ?? 0,
79
+ });
80
+ },
81
+ ExportAllDeclaration(path) {
82
+ const node = path.node;
83
+ add(node.source.value, {
84
+ kind: "re-export",
85
+ typeOnly: node.exportKind === "type",
86
+ specifiers: [{ kind: "namespace", imported: "*", local: "*" }],
87
+ line: node.loc?.start.line ?? 0,
88
+ });
89
+ },
90
+ ImportExpression(path) {
91
+ const source = stringValue(path.node.source);
92
+ if (!source)
93
+ return;
94
+ callSites.set(path.node, source);
95
+ add(source, { kind: "dynamic-import", typeOnly: false, specifiers: [], line: path.node.loc?.start.line ?? 0 });
96
+ },
97
+ CallExpression(path) {
98
+ if (!isRequireCall(path))
99
+ return;
100
+ const source = stringValue(path.node.arguments[0]);
101
+ if (!source)
102
+ return;
103
+ callSites.set(path.node, source);
104
+ const specifiers = [];
105
+ const declarator = path.parentPath;
106
+ if (declarator.isVariableDeclarator() && declarator.node.init === path.node) {
107
+ const id = declarator.node.id;
108
+ if (id.type === "Identifier") {
109
+ specifiers.push({ kind: "namespace", imported: "*", local: id.name });
110
+ }
111
+ else if (id.type === "ObjectPattern") {
112
+ for (const prop of id.properties) {
113
+ if (prop.type === "ObjectProperty" && prop.value.type === "Identifier") {
114
+ specifiers.push({ kind: "named", imported: moduleName(prop.key), local: prop.value.name });
115
+ }
116
+ }
117
+ }
118
+ if (path.scope.getProgramParent() === path.scope) {
119
+ for (const s of specifiers)
120
+ bindings.set(s.local, source);
121
+ }
122
+ }
123
+ add(source, { kind: "require", typeOnly: false, specifiers, line: path.node.loc?.start.line ?? 0 });
124
+ },
125
+ AssignmentExpression(path) {
126
+ if (isCommonJsExportTarget(path.node.left))
127
+ usesCommonJsExports = true;
128
+ },
129
+ // `const client = new PrismaClient()` makes `client` a stand-in for "@prisma/client".
130
+ VariableDeclarator(path) {
131
+ const { id, init } = path.node;
132
+ if (id.type !== "Identifier" || !init || path.scope !== path.scope.getProgramParent())
133
+ return;
134
+ if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")
135
+ return;
136
+ const programScope = path.scope;
137
+ path.traverse({
138
+ Identifier(idPath) {
139
+ if (bindings.has(id.name) || !idPath.isReferencedIdentifier())
140
+ return;
141
+ const source = bindings.get(idPath.node.name);
142
+ if (source && idPath.scope.getBinding(idPath.node.name) === programScope.getBinding(idPath.node.name)) {
143
+ bindings.set(id.name, source);
144
+ }
145
+ },
146
+ });
147
+ },
148
+ });
149
+ imports.sort((a, b) => a.line - b.line);
150
+ return { imports, bindings, callSites, usesCommonJsExports };
151
+ }
152
+ export function summarizeDependencies(imports) {
153
+ const local = new Set();
154
+ const external = new Set();
155
+ const builtin = new Set();
156
+ for (const entry of imports) {
157
+ if (entry.typeOnly)
158
+ continue;
159
+ if (entry.category === "local")
160
+ local.add(entry.source);
161
+ else if (entry.category === "builtin")
162
+ builtin.add(entry.source.replace(/^node:/, ""));
163
+ else if (entry.packageName)
164
+ external.add(entry.packageName);
165
+ }
166
+ return { local: [...local].sort(), external: [...external].sort(), builtin: [...builtin].sort() };
167
+ }
168
+ //# sourceMappingURL=dependencies.js.map