@kintsugi-ai/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/auth.d.ts +57 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +151 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/baseline.d.ts +8 -0
  6. package/dist/baseline.d.ts.map +1 -0
  7. package/dist/baseline.js +114 -0
  8. package/dist/baseline.js.map +1 -0
  9. package/dist/benchmark.d.ts +65 -0
  10. package/dist/benchmark.d.ts.map +1 -0
  11. package/dist/benchmark.js +232 -0
  12. package/dist/benchmark.js.map +1 -0
  13. package/dist/capturer.d.ts +14 -0
  14. package/dist/capturer.d.ts.map +1 -0
  15. package/dist/capturer.js +92 -0
  16. package/dist/capturer.js.map +1 -0
  17. package/dist/classifier.d.ts +62 -0
  18. package/dist/classifier.d.ts.map +1 -0
  19. package/dist/classifier.js +204 -0
  20. package/dist/classifier.js.map +1 -0
  21. package/dist/comparator.d.ts +28 -0
  22. package/dist/comparator.d.ts.map +1 -0
  23. package/dist/comparator.js +184 -0
  24. package/dist/comparator.js.map +1 -0
  25. package/dist/comparator.test.d.ts +2 -0
  26. package/dist/comparator.test.d.ts.map +1 -0
  27. package/dist/comparator.test.js +73 -0
  28. package/dist/comparator.test.js.map +1 -0
  29. package/dist/config.d.ts +6 -0
  30. package/dist/config.d.ts.map +1 -0
  31. package/dist/config.js +79 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/config.test.d.ts +2 -0
  34. package/dist/config.test.d.ts.map +1 -0
  35. package/dist/config.test.js +67 -0
  36. package/dist/config.test.js.map +1 -0
  37. package/dist/domdiff.d.ts +6 -0
  38. package/dist/domdiff.d.ts.map +1 -0
  39. package/dist/domdiff.js +71 -0
  40. package/dist/domdiff.js.map +1 -0
  41. package/dist/feedback.d.ts +9 -0
  42. package/dist/feedback.d.ts.map +1 -0
  43. package/dist/feedback.js +57 -0
  44. package/dist/feedback.js.map +1 -0
  45. package/dist/flowid.d.ts +3 -0
  46. package/dist/flowid.d.ts.map +1 -0
  47. package/dist/flowid.js +11 -0
  48. package/dist/flowid.js.map +1 -0
  49. package/dist/index.d.ts +14 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +14 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/recorder.d.ts +12 -0
  54. package/dist/recorder.d.ts.map +1 -0
  55. package/dist/recorder.js +86 -0
  56. package/dist/recorder.js.map +1 -0
  57. package/dist/setupPrompt.d.ts +10 -0
  58. package/dist/setupPrompt.d.ts.map +1 -0
  59. package/dist/setupPrompt.js +27 -0
  60. package/dist/setupPrompt.js.map +1 -0
  61. package/dist/types.d.ts +144 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +12 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/vision.d.ts +34 -0
  66. package/dist/vision.d.ts.map +1 -0
  67. package/dist/vision.js +139 -0
  68. package/dist/vision.js.map +1 -0
  69. package/package.json +37 -0
  70. package/src/auth.ts +195 -0
  71. package/src/baseline.ts +125 -0
  72. package/src/benchmark.ts +307 -0
  73. package/src/capturer.ts +105 -0
  74. package/src/classifier.ts +258 -0
  75. package/src/comparator.test.ts +97 -0
  76. package/src/comparator.ts +217 -0
  77. package/src/config.test.ts +80 -0
  78. package/src/config.ts +83 -0
  79. package/src/domdiff.ts +62 -0
  80. package/src/feedback.ts +64 -0
  81. package/src/flowid.ts +14 -0
  82. package/src/index.ts +13 -0
  83. package/src/recorder.ts +96 -0
  84. package/src/setupPrompt.ts +26 -0
  85. package/src/types.ts +144 -0
  86. package/tsconfig.json +9 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type Browser } from 'playwright';
2
+ import type { CaptureResult, FlowRecording } from './types.js';
3
+ export declare function getBrowser(): Promise<Browser>;
4
+ export declare function closeBrowser(): Promise<void>;
5
+ export declare function captureCurrentState(options: {
6
+ url: string;
7
+ flow: FlowRecording;
8
+ outputDir: string;
9
+ viewport?: {
10
+ width: number;
11
+ height: number;
12
+ };
13
+ }): Promise<CaptureResult>;
14
+ //# sourceMappingURL=capturer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capturer.d.ts","sourceRoot":"","sources":["../src/capturer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI/D,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAKnD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAKlD;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,GAAG,OAAO,CAAC,aAAa,CAAC,CA8EzB"}
@@ -0,0 +1,92 @@
1
+ import { chromium } from 'playwright';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ let browserInstance = null;
5
+ export async function getBrowser() {
6
+ if (!browserInstance) {
7
+ browserInstance = await chromium.launch({ headless: true });
8
+ }
9
+ return browserInstance;
10
+ }
11
+ export async function closeBrowser() {
12
+ if (browserInstance) {
13
+ await browserInstance.close();
14
+ browserInstance = null;
15
+ }
16
+ }
17
+ export async function captureCurrentState(options) {
18
+ const { url, flow, outputDir, viewport = { width: 1280, height: 720 } } = options;
19
+ const browser = await getBrowser();
20
+ const videoDir = path.join(outputDir, 'video');
21
+ await fs.mkdir(videoDir, { recursive: true });
22
+ const context = await browser.newContext({
23
+ viewport,
24
+ recordVideo: { dir: videoDir }
25
+ });
26
+ const page = await context.newPage();
27
+ const result = {
28
+ screenshots: [],
29
+ screenshotPaths: [],
30
+ ariaSnapshots: [],
31
+ completedSteps: 0,
32
+ totalSteps: flow.steps.length,
33
+ errors: []
34
+ };
35
+ await fs.mkdir(outputDir, { recursive: true });
36
+ try {
37
+ let navigated = false;
38
+ for (let i = 0; i < flow.steps.length; i++) {
39
+ const step = flow.steps[i];
40
+ try {
41
+ if (!navigated && page.url() === 'about:blank' && step.action !== 'navigate') {
42
+ await page.goto(url, { waitUntil: 'networkidle' });
43
+ navigated = true;
44
+ }
45
+ if (step.action === 'navigate') {
46
+ await page.goto(step.url || url, { waitUntil: 'networkidle' });
47
+ navigated = true;
48
+ }
49
+ else if (step.action === 'click' && step.selector) {
50
+ await page.click(step.selector, { timeout: step.timeout || 5000 });
51
+ }
52
+ else if (step.action === 'type' && step.selector && step.value !== undefined) {
53
+ await page.fill(step.selector, step.value, { timeout: step.timeout || 5000 });
54
+ }
55
+ else if (step.action === 'scroll' && step.position) {
56
+ await page.mouse.wheel(step.position.x, step.position.y);
57
+ }
58
+ else if (step.action === 'wait') {
59
+ await page.waitForTimeout(step.timeout || 1000);
60
+ }
61
+ await page.waitForTimeout(500);
62
+ const screenshotPath = path.join(outputDir, `step_${i}.png`);
63
+ const buffer = await page.screenshot({ path: screenshotPath });
64
+ // Semantic DOM state for the classifier tier (diffable as text)
65
+ let aria = '';
66
+ try {
67
+ aria = await page.locator('body').ariaSnapshot();
68
+ }
69
+ catch {
70
+ aria = '';
71
+ }
72
+ await fs.writeFile(path.join(outputDir, `aria_step_${i}.yml`), aria, 'utf-8');
73
+ result.screenshots.push(buffer);
74
+ result.screenshotPaths.push(screenshotPath);
75
+ result.ariaSnapshots.push(aria);
76
+ result.completedSteps++;
77
+ }
78
+ catch (err) {
79
+ result.errors.push(`Step ${i} (${step.action}) failed: ${err.message}`);
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ finally {
85
+ const videoObj = await page.video();
86
+ const videoPath = videoObj ? await videoObj.path() : undefined;
87
+ result.videoPath = videoPath;
88
+ await context.close();
89
+ }
90
+ return result;
91
+ }
92
+ //# sourceMappingURL=capturer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capturer.js","sourceRoot":"","sources":["../src/capturer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { ClassifierConfig } from './types.js';
2
+ export interface ClassifierVerdict {
3
+ intentional: boolean;
4
+ /** 0..1 certainty reported by the classifier */
5
+ confidence: number;
6
+ /** One sentence: what changed and — if not intentional — how to fix it */
7
+ reasoning: string;
8
+ }
9
+ /** Metadata about the flow being classified (the hosted API uses it for the flow gate + usage log) */
10
+ export interface ClassifyMeta {
11
+ flowName?: string;
12
+ flowHash?: string;
13
+ step?: number;
14
+ }
15
+ /**
16
+ * Thrown when the hosted API returns 429 — either the free-plan flow limit
17
+ * (distinct flows) or the monthly abuse cap. Carries the payload surfaced
18
+ * in agent feedback and UI ("10 of 10 flows guarded — upgrade for unlimited").
19
+ */
20
+ export declare class FlowLimitError extends Error {
21
+ readonly kind: 'flow_limit_reached' | 'monthly_cap_reached';
22
+ readonly flowsUsed?: number;
23
+ readonly flowLimit?: number;
24
+ readonly upgradeUrl?: string;
25
+ constructor(body: {
26
+ error?: string;
27
+ flowsUsed?: number;
28
+ flowLimit?: number;
29
+ upgradeUrl?: string;
30
+ });
31
+ }
32
+ /**
33
+ * Classifies a visual change as intentional or a regression. Provider
34
+ * 'kintsugi' POSTs to the hosted classifier API (which owns the model and
35
+ * the flow gate); 'openai-compatible' sends the chat-completions prompt
36
+ * below to any OpenAI-compatible endpoint.
37
+ */
38
+ export declare function classifyChange(options: {
39
+ baselineAria: string;
40
+ currentAria: string;
41
+ context?: string;
42
+ config: ClassifierConfig;
43
+ apiToken: string;
44
+ meta?: ClassifyMeta;
45
+ logger?: {
46
+ info: (msg: string, data?: unknown) => void;
47
+ warn: (msg: string, data?: unknown) => void;
48
+ };
49
+ }): Promise<ClassifierVerdict>;
50
+ /** Robustly extracts {intentional, confidence, reasoning} from the model output. Fails safe to not-intentional. */
51
+ export declare function parseVerdict(content: string): ClassifierVerdict;
52
+ /**
53
+ * Resolves the API token: process env first, then the project's
54
+ * `<projectDir>/.kintsugi/.env`, then the machine-global `~/.kintsugi/.env`
55
+ * (so one key works across all projects).
56
+ */
57
+ export declare function resolveApiToken(tokenEnvVar: string, projectDir?: string): string | undefined;
58
+ /** Path of the machine-global kintsugi env file — where the hosted API key lives. */
59
+ export declare function globalKintsugiEnvPath(): string;
60
+ /** Loads KEY=VALUE pairs from <projectDir>/.kintsugi/.env into process.env (never overrides). */
61
+ export declare function loadKintsugiEnv(projectDir: string): void;
62
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,sGAAsG;AACtG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,GAAG,qBAAqB,CAAC;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CAQlG;AAaD;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;CACvG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAK7B;AAgHD,mHAAmH;AACnH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAkC/D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK5F;AAED,qFAAqF;AACrF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAExD"}
@@ -0,0 +1,204 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { diffAriaSnapshots } from './domdiff.js';
5
+ /**
6
+ * Thrown when the hosted API returns 429 — either the free-plan flow limit
7
+ * (distinct flows) or the monthly abuse cap. Carries the payload surfaced
8
+ * in agent feedback and UI ("10 of 10 flows guarded — upgrade for unlimited").
9
+ */
10
+ export class FlowLimitError extends Error {
11
+ kind;
12
+ flowsUsed;
13
+ flowLimit;
14
+ upgradeUrl;
15
+ constructor(body) {
16
+ super(body.error ?? 'limit_reached');
17
+ this.name = 'FlowLimitError';
18
+ this.kind = body.error === 'monthly_cap_reached' ? 'monthly_cap_reached' : 'flow_limit_reached';
19
+ this.flowsUsed = body.flowsUsed;
20
+ this.flowLimit = body.flowLimit;
21
+ this.upgradeUrl = body.upgradeUrl;
22
+ }
23
+ }
24
+ const SYSTEM_PROMPT = [
25
+ 'You are a strict UI regression classifier for an automated QA tool that guards AI coding agents.',
26
+ 'You will be given: (1) a structural diff of the page accessibility tree between the accepted baseline and the current state after a code change, and (2) optional context about what the developer/agent was asked to do in the latest turn.',
27
+ 'The baseline is the ACCEPTED, WORKING state. Removed or broken elements (buttons, forms, prices, text, navigation) are regressions even if the remaining page looks coherent.',
28
+ 'If no context is provided, be conservative: significant structural changes should be classified as not intentional.',
29
+ 'If context IS provided and the diff implements exactly what the context asked for, classify it as intentional with high confidence — a requested change is not a regression.',
30
+ 'Benign changes (reordered identical items, minor text edits, added requested features) are intentional.',
31
+ 'Answer with ONLY a JSON object, no other text:',
32
+ '{"intentional": <true|false>, "confidence": <number between 0 and 1>, "reasoning": "<exactly one sentence: what changed, and if not intentional, the specific fix>"}',
33
+ ].join('\n');
34
+ /**
35
+ * Classifies a visual change as intentional or a regression. Provider
36
+ * 'kintsugi' POSTs to the hosted classifier API (which owns the model and
37
+ * the flow gate); 'openai-compatible' sends the chat-completions prompt
38
+ * below to any OpenAI-compatible endpoint.
39
+ */
40
+ export async function classifyChange(options) {
41
+ if (options.config.provider === 'kintsugi') {
42
+ return classifyViaKintsugi(options);
43
+ }
44
+ return classifyViaOpenAiCompatible(options);
45
+ }
46
+ /** Hosted kintsugi API path — the server computes the diff, picks the model, and enforces the flow gate. */
47
+ async function classifyViaKintsugi(options) {
48
+ const { baselineAria, currentAria, context, config, apiToken, meta } = options;
49
+ const url = `${config.endpoint.replace(/\/$/, '')}/classify`;
50
+ const controller = new AbortController();
51
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
52
+ let response;
53
+ try {
54
+ response = await fetch(url, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ Authorization: `Bearer ${apiToken}`,
59
+ },
60
+ body: JSON.stringify({ baselineAria, currentAria, context: context ?? null, meta: meta ?? null }),
61
+ signal: controller.signal,
62
+ });
63
+ }
64
+ finally {
65
+ clearTimeout(timer);
66
+ }
67
+ if (response.status === 429) {
68
+ const body = await response.json().catch(() => ({}));
69
+ throw new FlowLimitError(body);
70
+ }
71
+ if (!response.ok) {
72
+ const detail = await response.text().catch(() => '');
73
+ throw new Error(`kintsugi api ${response.status}: ${detail.slice(0, 300)}`);
74
+ }
75
+ const payload = (await response.json());
76
+ if (typeof payload.intentional !== 'boolean' || typeof payload.confidence !== 'number') {
77
+ throw new Error(`unexpected kintsugi api response: ${JSON.stringify(payload).slice(0, 200)}`);
78
+ }
79
+ return {
80
+ intentional: payload.intentional,
81
+ confidence: payload.confidence,
82
+ reasoning: payload.reasoning ?? '',
83
+ };
84
+ }
85
+ async function classifyViaOpenAiCompatible(options) {
86
+ const { baselineAria, currentAria, context, config, apiToken, logger } = options;
87
+ const diff = diffAriaSnapshots(baselineAria, currentAria);
88
+ const userText = [
89
+ 'Accessibility-tree diff (baseline → current). Lines prefixed "REMOVED:" were removed from the page, "ADDED:" were added:',
90
+ diff,
91
+ '',
92
+ context
93
+ ? `Context from the latest turn (what the agent was asked to do):\n"${context}"`
94
+ : 'No context available about the intended change.',
95
+ '',
96
+ 'Classify this change as intentional or not. Reply with ONLY the JSON object.',
97
+ ].join('\n');
98
+ const body = {
99
+ model: config.model,
100
+ temperature: 0,
101
+ max_tokens: 120,
102
+ messages: [
103
+ { role: 'system', content: SYSTEM_PROMPT },
104
+ { role: 'user', content: userText },
105
+ ],
106
+ };
107
+ const url = `${config.endpoint.replace(/\/$/, '')}/chat/completions`;
108
+ const controller = new AbortController();
109
+ const timer = setTimeout(() => controller.abort(), config.timeoutMs);
110
+ let response;
111
+ try {
112
+ response = await fetch(url, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ Authorization: `Bearer ${apiToken}`,
117
+ },
118
+ body: JSON.stringify(body),
119
+ signal: controller.signal,
120
+ });
121
+ }
122
+ finally {
123
+ clearTimeout(timer);
124
+ }
125
+ if (!response.ok) {
126
+ const detail = await response.text().catch(() => '');
127
+ throw new Error(`classifier endpoint ${response.status}: ${detail.slice(0, 300)}`);
128
+ }
129
+ const payload = JSON.parse(await response.text());
130
+ const content = payload.choices?.[0]?.message?.content ?? '';
131
+ logger?.info('classifier responded', { content: content.slice(0, 300) });
132
+ return parseVerdict(content);
133
+ }
134
+ /** Robustly extracts {intentional, confidence, reasoning} from the model output. Fails safe to not-intentional. */
135
+ export function parseVerdict(content) {
136
+ const text = content.trim();
137
+ // Preferred: the JSON object we asked for
138
+ const jsonMatch = text.match(/\{[\s\S]*?\}/);
139
+ if (jsonMatch) {
140
+ try {
141
+ const parsed = JSON.parse(jsonMatch[0]);
142
+ if (typeof parsed.intentional === 'boolean') {
143
+ return {
144
+ intentional: parsed.intentional,
145
+ confidence: typeof parsed.confidence === 'number'
146
+ ? Math.min(1, Math.max(0, parsed.confidence))
147
+ : 0.5,
148
+ reasoning: typeof parsed.reasoning === 'string' && parsed.reasoning.trim()
149
+ ? parsed.reasoning.trim()
150
+ : 'No reasoning provided by classifier.',
151
+ };
152
+ }
153
+ }
154
+ catch {
155
+ // fall through to fail-safe
156
+ }
157
+ }
158
+ // Unparseable — fail safe: treat as an unresolved regression
159
+ return {
160
+ intentional: false,
161
+ confidence: 0.5,
162
+ reasoning: 'Classifier output was unparseable; treat this change as a regression and inspect the aria diff manually.',
163
+ };
164
+ }
165
+ /**
166
+ * Resolves the API token: process env first, then the project's
167
+ * `<projectDir>/.kintsugi/.env`, then the machine-global `~/.kintsugi/.env`
168
+ * (so one key works across all projects).
169
+ */
170
+ export function resolveApiToken(tokenEnvVar, projectDir) {
171
+ if (process.env[tokenEnvVar])
172
+ return process.env[tokenEnvVar];
173
+ if (projectDir)
174
+ loadKintsugiEnv(projectDir);
175
+ loadEnvFile(globalKintsugiEnvPath());
176
+ return process.env[tokenEnvVar];
177
+ }
178
+ /** Path of the machine-global kintsugi env file — where the hosted API key lives. */
179
+ export function globalKintsugiEnvPath() {
180
+ return path.join(os.homedir(), '.kintsugi', '.env');
181
+ }
182
+ /** Loads KEY=VALUE pairs from <projectDir>/.kintsugi/.env into process.env (never overrides). */
183
+ export function loadKintsugiEnv(projectDir) {
184
+ loadEnvFile(path.join(projectDir, '.kintsugi', '.env'));
185
+ }
186
+ function loadEnvFile(envPath) {
187
+ let content;
188
+ try {
189
+ content = fs.readFileSync(envPath, 'utf-8');
190
+ }
191
+ catch {
192
+ return;
193
+ }
194
+ for (const line of content.split('\n')) {
195
+ const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
196
+ if (!match)
197
+ continue;
198
+ const key = match[1];
199
+ const value = match[2].replace(/^["']|["']$/g, '');
200
+ if (!(key in process.env))
201
+ process.env[key] = value;
202
+ }
203
+ }
204
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type StepComparisonResult, type FlowRecording, type CaptureResult, type ComparisonResult, type ClassifierConfig } from './types.js';
2
+ /** Everything the comparator needs to consult the classifier for large diffs */
3
+ export interface ClassifierContext {
4
+ config: ClassifierConfig;
5
+ apiToken: string;
6
+ /** What the agent was asked to do in the latest turn (sharpens intentional-vs-regression) */
7
+ context?: string;
8
+ logger?: {
9
+ info: (msg: string, data?: unknown) => void;
10
+ warn: (msg: string, data?: unknown) => void;
11
+ };
12
+ }
13
+ export interface CompareOptions {
14
+ outputDir?: string;
15
+ classifier?: ClassifierContext;
16
+ }
17
+ export declare function computePixelDiff(img1: Buffer, img2: Buffer): Promise<{
18
+ diffPercent: number;
19
+ diffImage: Buffer;
20
+ }>;
21
+ /**
22
+ * Pixel-only verdict for one screenshot pair. Diff percentages above the
23
+ * classifier threshold are returned as a tentative CHANGED — compareFlow
24
+ * refines those with the classifier when one is configured.
25
+ */
26
+ export declare function compareScreenshots(baseline: Buffer, current: Buffer, stepIndex?: number, options?: CompareOptions): Promise<StepComparisonResult>;
27
+ export declare function compareFlow(baseline: FlowRecording, current: CaptureResult, options?: CompareOptions): Promise<ComparisonResult>;
28
+ //# sourceMappingURL=comparator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comparator.d.ts","sourceRoot":"","sources":["../src/comparator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,oBAAoB,EAA2B,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKlL,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC;CACvG;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgBtH;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAU,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2B1J;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+DtI"}
@@ -0,0 +1,184 @@
1
+ import pixelmatch from 'pixelmatch';
2
+ import { PNG } from 'pngjs';
3
+ import { promises as fs } from 'fs';
4
+ import path from 'path';
5
+ import { DiffResult } from './types.js';
6
+ import { classifyChange, FlowLimitError } from './classifier.js';
7
+ import { flowFingerprint } from './flowid.js';
8
+ import { diffAriaSnapshots } from './domdiff.js';
9
+ export async function computePixelDiff(img1, img2) {
10
+ const png1 = PNG.sync.read(img1);
11
+ const png2 = PNG.sync.read(img2);
12
+ if (png1.width !== png2.width || png1.height !== png2.height) {
13
+ return { diffPercent: 100, diffImage: Buffer.alloc(0) };
14
+ }
15
+ const { width, height } = png1;
16
+ const diff = new PNG({ width, height });
17
+ const numDiffPixels = pixelmatch(png1.data, png2.data, diff.data, width, height, { threshold: 0.1 });
18
+ const diffPercent = (numDiffPixels / (width * height)) * 100;
19
+ const diffImage = PNG.sync.write(diff);
20
+ return { diffPercent, diffImage };
21
+ }
22
+ /**
23
+ * Pixel-only verdict for one screenshot pair. Diff percentages above the
24
+ * classifier threshold are returned as a tentative CHANGED — compareFlow
25
+ * refines those with the classifier when one is configured.
26
+ */
27
+ export async function compareScreenshots(baseline, current, stepIndex = 0, options) {
28
+ const outputDir = options?.outputDir;
29
+ const { diffPercent, diffImage } = await computePixelDiff(baseline, current);
30
+ let result;
31
+ if (diffPercent <= 0.05) {
32
+ result = DiffResult.IDENTICAL;
33
+ }
34
+ else if (diffPercent > (options?.classifier?.config.pixelDiffThreshold ?? 0.5)) {
35
+ result = DiffResult.CHANGED;
36
+ }
37
+ else {
38
+ // Sub-threshold change: benign (anti-aliasing, subpixel shifts, dynamic content)
39
+ result = DiffResult.MINOR;
40
+ }
41
+ let diffImagePath;
42
+ if (outputDir && result !== DiffResult.IDENTICAL) {
43
+ diffImagePath = path.join(outputDir, `diff_${stepIndex}.png`);
44
+ await fs.writeFile(diffImagePath, diffImage);
45
+ }
46
+ return {
47
+ step: stepIndex,
48
+ pixelDiffPercent: diffPercent,
49
+ ssimScore: 1, // deprecated field, kept for output compatibility
50
+ result,
51
+ diffImagePath,
52
+ };
53
+ }
54
+ export async function compareFlow(baseline, current, options) {
55
+ const stepResults = [];
56
+ const len = Math.min(baseline.screenshotPaths.length, current.screenshots.length);
57
+ // Pass 1: pixel diff every step (cheap, deterministic)
58
+ for (let i = 0; i < len; i++) {
59
+ const baselineBuffer = await fs.readFile(baseline.screenshotPaths[i]);
60
+ const currentBuffer = current.screenshots[i];
61
+ stepResults.push(await compareScreenshots(baselineBuffer, currentBuffer, i, options));
62
+ }
63
+ // Pass 2: classify every oversized step — all classifier calls in parallel
64
+ const threshold = options?.classifier?.config.pixelDiffThreshold ?? 0.5;
65
+ const candidates = stepResults.filter(s => s.pixelDiffPercent > threshold);
66
+ const classifications = options?.classifier
67
+ ? await classifySteps(candidates, baseline, current, options.classifier)
68
+ : [];
69
+ for (const c of classifications) {
70
+ const step = stepResults.find(s => s.step === c.step);
71
+ if (step) {
72
+ step.result = c.intentional && c.confidence > (options.classifier.config.confidenceThreshold)
73
+ ? DiffResult.INTENTIONAL
74
+ : DiffResult.CHANGED;
75
+ }
76
+ }
77
+ // Aggregate the worst verdict across steps
78
+ let worstResult = DiffResult.IDENTICAL;
79
+ let failedStep;
80
+ for (const comp of stepResults) {
81
+ if (comp.result === DiffResult.CHANGED || comp.result === DiffResult.BROKEN || comp.result === DiffResult.ERROR) {
82
+ if (worstResult !== DiffResult.CHANGED && worstResult !== DiffResult.BROKEN && worstResult !== DiffResult.ERROR) {
83
+ worstResult = comp.result;
84
+ failedStep = comp.step;
85
+ }
86
+ }
87
+ else if (comp.result === DiffResult.MINOR && worstResult === DiffResult.IDENTICAL) {
88
+ worstResult = DiffResult.MINOR;
89
+ }
90
+ else if (comp.result === DiffResult.INTENTIONAL && worstResult === DiffResult.IDENTICAL) {
91
+ worstResult = DiffResult.INTENTIONAL;
92
+ }
93
+ }
94
+ if (current.errors.length > 0) {
95
+ worstResult = DiffResult.BROKEN;
96
+ failedStep = current.completedSteps;
97
+ }
98
+ const worstStep = stepResults.find(s => s.step === failedStep) || stepResults[0];
99
+ // Surface the classifier's explanation in the escalation panel (semanticAnalysis
100
+ // is what the VS Code split-view displays).
101
+ const worstRegression = classifications.find(c => !c.intentional);
102
+ return {
103
+ result: worstResult,
104
+ pixelDiffPercent: worstStep?.pixelDiffPercent || 0,
105
+ ssimScore: worstStep?.ssimScore || 1,
106
+ diffImagePath: worstStep?.diffImagePath,
107
+ semanticAnalysis: worstRegression?.reasoning,
108
+ failedStep,
109
+ stepResults,
110
+ classifications: classifications.length > 0 ? classifications : undefined,
111
+ };
112
+ }
113
+ /**
114
+ * Classifies all oversized steps in parallel. Fail-safe: a missing aria
115
+ * snapshot or any classifier error yields a not-intentional classification
116
+ * so the step still escalates.
117
+ */
118
+ async function classifySteps(candidates, baseline, current, classifier) {
119
+ return Promise.all(candidates.map(async (candidate) => {
120
+ const i = candidate.step;
121
+ const currentAria = current.ariaSnapshots?.[i];
122
+ let baselineAria;
123
+ if (baseline.ariaSnapshotPaths?.[i]) {
124
+ try {
125
+ baselineAria = await fs.readFile(baseline.ariaSnapshotPaths[i], 'utf-8');
126
+ }
127
+ catch {
128
+ baselineAria = undefined;
129
+ }
130
+ }
131
+ const base = {
132
+ step: i,
133
+ intentional: false,
134
+ confidence: 0.5,
135
+ reasoning: 'aria snapshots unavailable for this pair — inspect the diff image manually',
136
+ ariaDiff: '(aria snapshots unavailable — re-record the baseline to enable classification)',
137
+ oldImagePath: baseline.screenshotPaths[i],
138
+ newImagePath: current.screenshotPaths[i],
139
+ };
140
+ if (baselineAria === undefined || currentAria === undefined) {
141
+ classifier.logger?.warn('aria snapshots unavailable for step — escalating without classification', { step: i });
142
+ return base;
143
+ }
144
+ const ariaDiff = diffAriaSnapshots(baselineAria, currentAria);
145
+ try {
146
+ const verdict = await classifyChange({
147
+ baselineAria,
148
+ currentAria,
149
+ context: classifier.context,
150
+ config: classifier.config,
151
+ apiToken: classifier.apiToken,
152
+ meta: {
153
+ flowName: baseline.metadata.name,
154
+ flowHash: flowFingerprint(baseline.metadata.name, baseline.steps),
155
+ step: i,
156
+ },
157
+ logger: classifier.logger,
158
+ });
159
+ classifier.logger?.info('classification', { step: i, ...verdict });
160
+ return { ...base, ...verdict, ariaDiff };
161
+ }
162
+ catch (err) {
163
+ if (err instanceof FlowLimitError) {
164
+ classifier.logger?.warn('flow limit reached — step escalates pixel-diff only', {
165
+ step: i,
166
+ kind: err.kind,
167
+ flowsUsed: err.flowsUsed,
168
+ flowLimit: err.flowLimit,
169
+ });
170
+ const where = err.kind === 'monthly_cap_reached'
171
+ ? 'monthly classification cap reached on the free plan'
172
+ : `free plan flow limit reached (${err.flowsUsed ?? '?'}/${err.flowLimit ?? '?'} flows guarded)`;
173
+ return {
174
+ ...base,
175
+ ariaDiff,
176
+ reasoning: `${where} — this flow degrades to pixel-diff only and is treated as a change. Upgrade at ${err.upgradeUrl ?? 'https://kintsugi.dev/upgrade'} to guard it with the classifier.`,
177
+ };
178
+ }
179
+ classifier.logger?.warn('classifier failed — escalating step', { step: i, error: err.message });
180
+ return { ...base, ariaDiff, reasoning: `classifier failed (${err.message}) — inspect the aria diff manually` };
181
+ }
182
+ }));
183
+ }
184
+ //# sourceMappingURL=comparator.js.map