@neonwatty/limner 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 (82) hide show
  1. package/README.md +161 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +26 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/capture.d.ts +12 -0
  6. package/dist/commands/capture.js +66 -0
  7. package/dist/commands/capture.js.map +1 -0
  8. package/dist/commands/compare-image-app.d.ts +12 -0
  9. package/dist/commands/compare-image-app.js +45 -0
  10. package/dist/commands/compare-image-app.js.map +1 -0
  11. package/dist/commands/compare-image-reference.d.ts +28 -0
  12. package/dist/commands/compare-image-reference.js +82 -0
  13. package/dist/commands/compare-image-reference.js.map +1 -0
  14. package/dist/commands/compare.d.ts +15 -0
  15. package/dist/commands/compare.js +168 -0
  16. package/dist/commands/compare.js.map +1 -0
  17. package/dist/commands/init.d.ts +12 -0
  18. package/dist/commands/init.js +65 -0
  19. package/dist/commands/init.js.map +1 -0
  20. package/dist/commands/preview.d.ts +2 -0
  21. package/dist/commands/preview.js +27 -0
  22. package/dist/commands/preview.js.map +1 -0
  23. package/dist/commands/report.d.ts +2 -0
  24. package/dist/commands/report.js +14 -0
  25. package/dist/commands/report.js.map +1 -0
  26. package/dist/commands/runs.d.ts +2 -0
  27. package/dist/commands/runs.js +63 -0
  28. package/dist/commands/runs.js.map +1 -0
  29. package/dist/core/dom-metrics.d.ts +18 -0
  30. package/dist/core/dom-metrics.js +54 -0
  31. package/dist/core/dom-metrics.js.map +1 -0
  32. package/dist/core/playwright-capture.d.ts +27 -0
  33. package/dist/core/playwright-capture.js +46 -0
  34. package/dist/core/playwright-capture.js.map +1 -0
  35. package/dist/core/playwright-dom.d.ts +16 -0
  36. package/dist/core/playwright-dom.js +64 -0
  37. package/dist/core/playwright-dom.js.map +1 -0
  38. package/dist/core/reference-dom-facts.d.ts +65 -0
  39. package/dist/core/reference-dom-facts.js +71 -0
  40. package/dist/core/reference-dom-facts.js.map +1 -0
  41. package/dist/core/report-writer.d.ts +43 -0
  42. package/dist/core/report-writer.js +181 -0
  43. package/dist/core/report-writer.js.map +1 -0
  44. package/dist/core/run-logger.d.ts +22 -0
  45. package/dist/core/run-logger.js +67 -0
  46. package/dist/core/run-logger.js.map +1 -0
  47. package/dist/core/side-by-side.d.ts +8 -0
  48. package/dist/core/side-by-side.js +33 -0
  49. package/dist/core/side-by-side.js.map +1 -0
  50. package/dist/core/static-server.d.ts +6 -0
  51. package/dist/core/static-server.js +73 -0
  52. package/dist/core/static-server.js.map +1 -0
  53. package/dist/core/visual-spec-agent-pack.d.ts +27 -0
  54. package/dist/core/visual-spec-agent-pack.js +143 -0
  55. package/dist/core/visual-spec-agent-pack.js.map +1 -0
  56. package/dist/core/visual-spec-prompts.d.ts +32 -0
  57. package/dist/core/visual-spec-prompts.js +129 -0
  58. package/dist/core/visual-spec-prompts.js.map +1 -0
  59. package/dist/core/workspace.d.ts +31 -0
  60. package/dist/core/workspace.js +97 -0
  61. package/dist/core/workspace.js.map +1 -0
  62. package/dist/index.d.ts +14 -0
  63. package/dist/index.js +12 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/schemas/contract.d.ts +43 -0
  66. package/dist/schemas/contract.js +19 -0
  67. package/dist/schemas/contract.js.map +1 -0
  68. package/dist/schemas/events.d.ts +34 -0
  69. package/dist/schemas/events.js +29 -0
  70. package/dist/schemas/events.js.map +1 -0
  71. package/dist/schemas/visual-spec.d.ts +410 -0
  72. package/dist/schemas/visual-spec.js +201 -0
  73. package/dist/schemas/visual-spec.js.map +1 -0
  74. package/docs/agent-workflow.md +45 -0
  75. package/package.json +64 -0
  76. package/skills/limner/SKILL.md +51 -0
  77. package/templates/target/AGENT_GUIDE.md +24 -0
  78. package/templates/target/contract/acceptance.md +23 -0
  79. package/templates/target/contract/regions.json +11 -0
  80. package/templates/target/contract/tokens.json +21 -0
  81. package/templates/target/reference/index.html +22 -0
  82. package/templates/target/reference/styles.css +53 -0
@@ -0,0 +1,43 @@
1
+ import type { RegionMetrics } from './dom-metrics.js';
2
+ import type { TargetPaths } from './workspace.js';
3
+ export declare function writeImageReferenceReport(input: {
4
+ target: TargetPaths;
5
+ idealPath: string;
6
+ referencePath: string;
7
+ sideBySidePath: string;
8
+ specPaths?: {
9
+ idealSpecPath: string;
10
+ referenceSpecPath: string;
11
+ diffPath: string;
12
+ };
13
+ specHighlights?: string[];
14
+ agentSpec?: {
15
+ promptPath: string;
16
+ codexPromptPath: string;
17
+ claudePromptPath: string;
18
+ factsPath: string;
19
+ examplePath: string;
20
+ schemaPath: string;
21
+ instructionsPath: string;
22
+ responsePath: string;
23
+ status: 'awaiting-agent' | 'validated' | 'invalid';
24
+ validationErrors?: string[];
25
+ };
26
+ }): Promise<string>;
27
+ export declare function writeReferenceAppReport(input: {
28
+ target: TargetPaths;
29
+ referencePath: string;
30
+ appPath: string;
31
+ sideBySidePath: string;
32
+ referenceMetrics: RegionMetrics[];
33
+ appMetrics: RegionMetrics[];
34
+ appUrl: string;
35
+ }): Promise<string>;
36
+ export declare function writeImageAppReport(input: {
37
+ target: TargetPaths;
38
+ idealPath: string;
39
+ appPath: string;
40
+ sideBySidePath: string;
41
+ appUrl: string;
42
+ }): Promise<string>;
43
+ export declare function writeTargetSummary(target: TargetPaths): Promise<string>;
@@ -0,0 +1,181 @@
1
+ import { mkdir, readdir, stat, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function writeImageReferenceReport(input) {
4
+ const reportPath = path.join(input.target.reportsDir, 'image-reference.md');
5
+ await mkdir(path.dirname(reportPath), { recursive: true });
6
+ await writeFile(reportPath, `# Image To Reference Report
7
+
8
+ Target: \`${input.target.name}\`
9
+
10
+ ## Artifacts
11
+
12
+ - Ideal image: \`${relative(input.target.workspace.root, input.idealPath)}\`
13
+ - Reference screenshot: \`${relative(input.target.workspace.root, input.referencePath)}\`
14
+ - Side-by-side: \`${relative(input.target.workspace.root, input.sideBySidePath)}\`
15
+ ${formatSpecArtifacts(input.target.workspace.root, input.specPaths, input.agentSpec)}
16
+
17
+ ## Agent Critique
18
+
19
+ - Hierarchy:
20
+ - Density:
21
+ - Typography:
22
+ - Color and tokens:
23
+ - Component anatomy:
24
+ - Intentional deviations:
25
+
26
+ ## Next Fix
27
+
28
+ - Record the highest-value scoped fix here.
29
+ ${formatAgentSpecStatus(input.agentSpec)}
30
+ ${formatSpecHighlights(input.specHighlights)}
31
+ `);
32
+ return reportPath;
33
+ }
34
+ export async function writeReferenceAppReport(input) {
35
+ const reportPath = path.join(input.target.reportsDir, 'reference-app.md');
36
+ await mkdir(path.dirname(reportPath), { recursive: true });
37
+ await writeFile(reportPath, `# Reference To App Report
38
+
39
+ Target: \`${input.target.name}\`
40
+
41
+ App URL: ${input.appUrl}
42
+
43
+ ## Artifacts
44
+
45
+ - Reference screenshot: \`${relative(input.target.workspace.root, input.referencePath)}\`
46
+ - App screenshot: \`${relative(input.target.workspace.root, input.appPath)}\`
47
+ - Side-by-side: \`${relative(input.target.workspace.root, input.sideBySidePath)}\`
48
+
49
+ ## Region Metrics
50
+
51
+ ${formatMetrics(input.referenceMetrics, input.appMetrics)}
52
+
53
+ ## Agent Findings
54
+
55
+ - Highest mismatch:
56
+ - Evidence:
57
+ - Suggested app fix:
58
+ - Intentional deviations:
59
+ `);
60
+ return reportPath;
61
+ }
62
+ export async function writeImageAppReport(input) {
63
+ const reportPath = path.join(input.target.reportsDir, 'image-app.md');
64
+ await mkdir(path.dirname(reportPath), { recursive: true });
65
+ await writeFile(reportPath, `# Image To App Report
66
+
67
+ Target: \`${input.target.name}\`
68
+
69
+ App URL: ${input.appUrl}
70
+
71
+ ## Artifacts
72
+
73
+ - Ideal image: \`${relative(input.target.workspace.root, input.idealPath)}\`
74
+ - App screenshot: \`${relative(input.target.workspace.root, input.appPath)}\`
75
+ - Side-by-side: \`${relative(input.target.workspace.root, input.sideBySidePath)}\`
76
+
77
+ ## Agent Findings
78
+
79
+ - Highest visual mismatch:
80
+ - Evidence:
81
+ - Suggested app fix:
82
+ - Intentional deviations:
83
+ `);
84
+ return reportPath;
85
+ }
86
+ export async function writeTargetSummary(target) {
87
+ const lines = [`# Limner Target Summary`, '', `Target: \`${target.name}\``, ''];
88
+ for (const dir of ['captures/image-reference', 'captures/reference-app', 'captures/image-app', 'reports']) {
89
+ const absoluteDir = path.join(target.root, dir);
90
+ lines.push(`## ${dir}`, '');
91
+ try {
92
+ const entries = await readdir(absoluteDir);
93
+ for (const entry of entries) {
94
+ const entryPath = path.join(absoluteDir, entry);
95
+ const entryStat = await stat(entryPath);
96
+ lines.push(`- \`${dir}/${entry}\` (${entryStat.size} bytes)`);
97
+ }
98
+ }
99
+ catch {
100
+ lines.push('- No artifacts yet.');
101
+ }
102
+ lines.push('');
103
+ }
104
+ return lines.join('\n');
105
+ }
106
+ function formatMetrics(referenceMetrics, appMetrics) {
107
+ const appById = new Map(appMetrics.map((metric) => [metric.id, metric]));
108
+ const rows = referenceMetrics.map((reference) => {
109
+ const app = appById.get(reference.id);
110
+ const referenceBounds = reference.bounds ? `${reference.bounds.width}x${reference.bounds.height}` : 'missing';
111
+ const appBounds = app?.bounds ? `${app.bounds.width}x${app.bounds.height}` : 'missing';
112
+ return `| ${reference.id} | ${reference.found ? 'yes' : 'no'} | ${app?.found ? 'yes' : 'no'} | ${referenceBounds} | ${appBounds} |`;
113
+ });
114
+ return [
115
+ '| Region | Reference Found | App Found | Reference Bounds | App Bounds |',
116
+ '| --- | --- | --- | --- | --- |',
117
+ ...rows,
118
+ ].join('\n');
119
+ }
120
+ function relative(root, filePath) {
121
+ return path.relative(root, filePath);
122
+ }
123
+ function formatSpecArtifacts(root, specPaths, agentSpec) {
124
+ const lines = [];
125
+ if (agentSpec) {
126
+ lines.push(`- Shared agent prompt: \`${relative(root, agentSpec.promptPath)}\``);
127
+ lines.push(`- Codex prompt: \`${relative(root, agentSpec.codexPromptPath)}\``);
128
+ lines.push(`- Claude prompt: \`${relative(root, agentSpec.claudePromptPath)}\``);
129
+ lines.push(`- Reference DOM facts: \`${relative(root, agentSpec.factsPath)}\``);
130
+ lines.push(`- Agent response example: \`${relative(root, agentSpec.examplePath)}\``);
131
+ lines.push(`- Agent response schema: \`${relative(root, agentSpec.schemaPath)}\``);
132
+ lines.push(`- Custom instructions: \`${relative(root, agentSpec.instructionsPath)}\``);
133
+ lines.push(`- Agent response target: \`${relative(root, agentSpec.responsePath)}\``);
134
+ }
135
+ if (specPaths) {
136
+ lines.push(`- Ideal visual spec: \`${relative(root, specPaths.idealSpecPath)}\``);
137
+ lines.push(`- Reference visual spec: \`${relative(root, specPaths.referenceSpecPath)}\``);
138
+ lines.push(`- Visual spec diff: \`${relative(root, specPaths.diffPath)}\``);
139
+ }
140
+ return lines.length > 0 ? `\n${lines.join('\n')}` : '';
141
+ }
142
+ function formatAgentSpecStatus(agentSpec) {
143
+ if (!agentSpec) {
144
+ return '';
145
+ }
146
+ if (agentSpec.status === 'awaiting-agent') {
147
+ return `
148
+
149
+ ## Visual Spec Status
150
+
151
+ - Agent-required spec pack generated.
152
+ - Next step: have an agent inspect the ideal image and reference artifacts, then write \`captures/image-reference/spec/agent-response.json\`.
153
+ - Rerun \`limn compare image-reference --target <target> --spec\` after the response file exists.`;
154
+ }
155
+ if (agentSpec.status === 'invalid') {
156
+ const errors = agentSpec.validationErrors?.map((error) => `- ${error}`).join('\n') ?? '- Unknown validation error.';
157
+ return `
158
+
159
+ ## Visual Spec Status
160
+
161
+ - Agent response was found but did not match the required structure.
162
+
163
+ ${errors}`;
164
+ }
165
+ return `
166
+
167
+ ## Visual Spec Status
168
+
169
+ - Agent response validated and split into standalone spec artifacts.`;
170
+ }
171
+ function formatSpecHighlights(highlights) {
172
+ if (!highlights || highlights.length === 0) {
173
+ return '';
174
+ }
175
+ return `
176
+
177
+ ## Visual Spec Highlights
178
+
179
+ ${highlights.map((highlight) => `- ${highlight}`).join('\n')}`;
180
+ }
181
+ //# sourceMappingURL=report-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-writer.js","sourceRoot":"","sources":["../../src/core/report-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { LimnerRunEvent } from '../schemas/events.js';
2
+ import type { WorkspacePaths } from './workspace.js';
3
+ export type RunLogger = {
4
+ runId: string;
5
+ runDir: string;
6
+ manifestPath: string;
7
+ eventsPath: string;
8
+ notesPath: string;
9
+ event(event: Omit<LimnerRunEvent, 'ts'>): Promise<void>;
10
+ artifact(filePath: string, kind: string): Promise<void>;
11
+ complete(status?: 'ok' | 'failed', message?: string): Promise<void>;
12
+ };
13
+ export declare function createRunLogger(workspace: WorkspacePaths, input: {
14
+ command: string;
15
+ target?: string;
16
+ mode?: string;
17
+ viewport?: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ }): Promise<RunLogger>;
22
+ export declare function createRunId(date?: Date): string;
@@ -0,0 +1,67 @@
1
+ import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function createRunLogger(workspace, input) {
4
+ const runId = createRunId();
5
+ const runDir = path.join(workspace.runsDir, runId);
6
+ const manifestPath = path.join(runDir, 'manifest.json');
7
+ const eventsPath = path.join(runDir, 'events.jsonl');
8
+ const notesPath = path.join(runDir, 'agent-notes.md');
9
+ const manifest = {
10
+ runId,
11
+ command: input.command,
12
+ target: input.target,
13
+ mode: input.mode,
14
+ startedAt: new Date().toISOString(),
15
+ status: 'running',
16
+ artifacts: [],
17
+ viewport: input.viewport,
18
+ };
19
+ await mkdir(runDir, { recursive: true });
20
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
21
+ await writeFile(eventsPath, '');
22
+ await writeFile(notesPath, `# Limner Run Notes\n\nRun: ${runId}\n\n`);
23
+ const writeManifest = async (next) => {
24
+ await writeFile(manifestPath, JSON.stringify(next, null, 2) + '\n');
25
+ };
26
+ const readManifest = async () => JSON.parse(await readFile(manifestPath, 'utf8'));
27
+ const logEvent = async (event) => {
28
+ const completeEvent = {
29
+ ...event,
30
+ command: event.command ?? input.command,
31
+ target: event.target ?? input.target,
32
+ mode: event.mode ?? input.mode,
33
+ ts: new Date().toISOString(),
34
+ };
35
+ await appendFile(eventsPath, JSON.stringify(completeEvent) + '\n');
36
+ };
37
+ return {
38
+ runId,
39
+ runDir,
40
+ manifestPath,
41
+ eventsPath,
42
+ notesPath,
43
+ event: logEvent,
44
+ async artifact(filePath, kind) {
45
+ const manifest = await readManifest();
46
+ const relativePath = path.relative(workspace.root, filePath);
47
+ if (!manifest.artifacts.includes(relativePath)) {
48
+ manifest.artifacts.push(relativePath);
49
+ await writeManifest(manifest);
50
+ }
51
+ await logEvent({ type: 'artifact.written', path: relativePath, kind });
52
+ },
53
+ async complete(status = 'ok', message) {
54
+ const manifest = await readManifest();
55
+ manifest.status = status;
56
+ manifest.completedAt = new Date().toISOString();
57
+ await writeManifest(manifest);
58
+ await logEvent({ type: 'command.completed', status, message });
59
+ },
60
+ };
61
+ }
62
+ export function createRunId(date = new Date()) {
63
+ const stamp = date.toISOString().replace(/[:.]/g, '').replace('Z', 'Z');
64
+ const random = Math.random().toString(36).slice(2, 8);
65
+ return `${stamp}-${random}`;
66
+ }
67
+ //# sourceMappingURL=run-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-logger.js","sourceRoot":"","sources":["../../src/core/run-logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function createSideBySideImage(input: {
2
+ leftPath: string;
3
+ rightPath: string;
4
+ outputPath: string;
5
+ height?: number;
6
+ gap?: number;
7
+ background?: string;
8
+ }): Promise<string>;
@@ -0,0 +1,33 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import sharp from 'sharp';
4
+ export async function createSideBySideImage(input) {
5
+ const height = input.height ?? 900;
6
+ const gap = input.gap ?? 16;
7
+ const background = input.background ?? '#f8fafc';
8
+ const left = await resizeToHeight(input.leftPath, height);
9
+ const right = await resizeToHeight(input.rightPath, height);
10
+ const width = left.info.width + right.info.width + gap;
11
+ await mkdir(path.dirname(input.outputPath), { recursive: true });
12
+ await sharp({
13
+ create: {
14
+ width,
15
+ height,
16
+ channels: 4,
17
+ background,
18
+ },
19
+ })
20
+ .composite([
21
+ { input: left.buffer, left: 0, top: 0 },
22
+ { input: right.buffer, left: left.info.width + gap, top: 0 },
23
+ ])
24
+ .png()
25
+ .toFile(input.outputPath);
26
+ return input.outputPath;
27
+ }
28
+ async function resizeToHeight(filePath, height) {
29
+ const image = sharp(filePath).resize({ height, fit: 'inside', withoutEnlargement: false }).png();
30
+ const { data, info } = await image.toBuffer({ resolveWithObject: true });
31
+ return { buffer: data, info: { width: info.width, height: info.height } };
32
+ }
33
+ //# sourceMappingURL=side-by-side.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-by-side.js","sourceRoot":"","sources":["../../src/core/side-by-side.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAO3C;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC;IACnC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,SAAS,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;IAEvD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,MAAM,KAAK,CAAC;QACV,MAAM,EAAE;YACN,KAAK;YACL,MAAM;YACN,QAAQ,EAAE,CAAC;YACX,UAAU;SACX;KACF,CAAC;SACC,SAAS,CAAC;QACT,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;QACvC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;KAC7D,CAAC;SACD,GAAG,EAAE;SACL,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE5B,OAAO,KAAK,CAAC,UAAU,CAAC;AAC1B,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,MAAc;IAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACjG,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type StaticServer = {
2
+ url: string;
3
+ port: number;
4
+ close(): Promise<void>;
5
+ };
6
+ export declare function startStaticServer(rootDir: string, preferredPort?: number): Promise<StaticServer>;
@@ -0,0 +1,73 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
+ import { createServer } from 'node:http';
4
+ import path from 'node:path';
5
+ export async function startStaticServer(rootDir, preferredPort = 0) {
6
+ const root = path.resolve(rootDir);
7
+ const server = createServer(async (request, response) => {
8
+ try {
9
+ const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
10
+ const pathname = decodeURIComponent(requestUrl.pathname === '/' ? '/index.html' : requestUrl.pathname);
11
+ const filePath = path.resolve(root, `.${pathname}`);
12
+ if (!filePath.startsWith(root)) {
13
+ response.writeHead(403);
14
+ response.end('Forbidden');
15
+ return;
16
+ }
17
+ const fileStat = await stat(filePath);
18
+ if (!fileStat.isFile()) {
19
+ response.writeHead(404);
20
+ response.end('Not found');
21
+ return;
22
+ }
23
+ response.writeHead(200, { 'content-type': contentType(filePath) });
24
+ createReadStream(filePath).pipe(response);
25
+ }
26
+ catch {
27
+ response.writeHead(404);
28
+ response.end('Not found');
29
+ }
30
+ });
31
+ await new Promise((resolve, reject) => {
32
+ server.once('error', reject);
33
+ server.listen(preferredPort, '127.0.0.1', () => resolve());
34
+ });
35
+ const address = server.address();
36
+ if (typeof address !== 'object' || !address) {
37
+ throw new Error('Unable to determine static server port.');
38
+ }
39
+ return {
40
+ url: `http://127.0.0.1:${address.port}`,
41
+ port: address.port,
42
+ close: () => closeServer(server),
43
+ };
44
+ }
45
+ function closeServer(server) {
46
+ return new Promise((resolve, reject) => {
47
+ server.close((error) => {
48
+ if (error)
49
+ reject(error);
50
+ else
51
+ resolve();
52
+ });
53
+ });
54
+ }
55
+ function contentType(filePath) {
56
+ const extension = path.extname(filePath).toLowerCase();
57
+ if (extension === '.html')
58
+ return 'text/html; charset=utf-8';
59
+ if (extension === '.css')
60
+ return 'text/css; charset=utf-8';
61
+ if (extension === '.js')
62
+ return 'text/javascript; charset=utf-8';
63
+ if (extension === '.json')
64
+ return 'application/json; charset=utf-8';
65
+ if (extension === '.png')
66
+ return 'image/png';
67
+ if (extension === '.jpg' || extension === '.jpeg')
68
+ return 'image/jpeg';
69
+ if (extension === '.svg')
70
+ return 'image/svg+xml';
71
+ return 'application/octet-stream';
72
+ }
73
+ //# sourceMappingURL=static-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-server.js","sourceRoot":"","sources":["../../src/core/static-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { TargetPaths } from './workspace.js';
2
+ export declare function writeImageReferenceSpecPack(input: {
3
+ target: TargetPaths;
4
+ captureDir: string;
5
+ referenceUrl: string;
6
+ referencePath: string;
7
+ sideBySidePath: string;
8
+ headed?: boolean;
9
+ instructionsPath?: string;
10
+ }): Promise<{
11
+ promptPath: string;
12
+ codexPromptPath: string;
13
+ claudePromptPath: string;
14
+ factsPath: string;
15
+ examplePath: string;
16
+ schemaPath: string;
17
+ instructionsPath: string;
18
+ responsePath: string;
19
+ status: 'awaiting-agent' | 'validated' | 'invalid';
20
+ validationErrors?: string[];
21
+ specPaths?: {
22
+ idealSpecPath: string;
23
+ referenceSpecPath: string;
24
+ diffPath: string;
25
+ };
26
+ diffHighlights?: string[];
27
+ }>;
@@ -0,0 +1,143 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { visualSpecBundleSchema, createVisualSpecBundleExample, createVisualSpecJsonSchema, } from '../schemas/visual-spec.js';
4
+ import { buildClaudePrompt, buildCodexPrompt, buildSharedPrompt } from './visual-spec-prompts.js';
5
+ import { writeIfMissing } from './workspace.js';
6
+ import { collectReferenceDomFacts } from './reference-dom-facts.js';
7
+ export async function writeImageReferenceSpecPack(input) {
8
+ const specDir = path.join(input.captureDir, 'spec');
9
+ const promptPath = path.join(specDir, 'agent-prompt.md');
10
+ const codexPromptPath = path.join(specDir, 'agent-prompt.codex.md');
11
+ const claudePromptPath = path.join(specDir, 'agent-prompt.claude.md');
12
+ const factsPath = path.join(specDir, 'reference-dom-facts.json');
13
+ const examplePath = path.join(specDir, 'agent-response.example.json');
14
+ const schemaPath = path.join(specDir, 'agent-response.schema.json');
15
+ const responsePath = path.join(specDir, 'agent-response.json');
16
+ const instructionsPath = input.instructionsPath ?? path.join(input.target.contractDir, 'visual-spec-instructions.md');
17
+ const facts = await collectReferenceDomFacts(input.referenceUrl, input.headed);
18
+ await writeIfMissing(instructionsPath, defaultInstructions());
19
+ const customInstructions = await readFile(instructionsPath, 'utf8');
20
+ await mkdir(specDir, { recursive: true });
21
+ await Promise.all([
22
+ writeJson(factsPath, facts),
23
+ writeJson(schemaPath, createVisualSpecJsonSchema()),
24
+ writeIfMissing(examplePath, JSON.stringify(createVisualSpecBundleExample(), null, 2) + '\n'),
25
+ writeFile(promptPath, buildSharedPrompt({
26
+ target: input.target,
27
+ referencePath: input.referencePath,
28
+ sideBySidePath: input.sideBySidePath,
29
+ factsPath,
30
+ examplePath,
31
+ schemaPath,
32
+ responsePath,
33
+ instructionsPath,
34
+ customInstructions,
35
+ })),
36
+ writeFile(codexPromptPath, buildCodexPrompt({
37
+ target: input.target,
38
+ promptPath,
39
+ referencePath: input.referencePath,
40
+ sideBySidePath: input.sideBySidePath,
41
+ factsPath,
42
+ schemaPath,
43
+ responsePath,
44
+ instructionsPath,
45
+ })),
46
+ writeFile(claudePromptPath, buildClaudePrompt({
47
+ target: input.target,
48
+ promptPath,
49
+ referencePath: input.referencePath,
50
+ sideBySidePath: input.sideBySidePath,
51
+ factsPath,
52
+ schemaPath,
53
+ responsePath,
54
+ instructionsPath,
55
+ })),
56
+ ]);
57
+ const response = await readBundle(responsePath);
58
+ if (response.status === 'missing') {
59
+ return { promptPath, codexPromptPath, claudePromptPath, factsPath, examplePath, schemaPath, instructionsPath, responsePath, status: 'awaiting-agent' };
60
+ }
61
+ if (response.status === 'invalid-json') {
62
+ return {
63
+ promptPath,
64
+ codexPromptPath,
65
+ claudePromptPath,
66
+ factsPath,
67
+ examplePath,
68
+ schemaPath,
69
+ instructionsPath,
70
+ responsePath,
71
+ status: 'invalid',
72
+ validationErrors: ['agent-response.json: Invalid JSON.'],
73
+ };
74
+ }
75
+ const parsed = visualSpecBundleSchema.safeParse(response.value);
76
+ if (!parsed.success) {
77
+ return {
78
+ promptPath,
79
+ codexPromptPath,
80
+ claudePromptPath,
81
+ factsPath,
82
+ examplePath,
83
+ schemaPath,
84
+ instructionsPath,
85
+ responsePath,
86
+ status: 'invalid',
87
+ validationErrors: parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`),
88
+ };
89
+ }
90
+ const specPaths = {
91
+ idealSpecPath: path.join(input.captureDir, 'ideal-visual-spec.json'),
92
+ referenceSpecPath: path.join(input.captureDir, 'reference-visual-spec.json'),
93
+ diffPath: path.join(input.captureDir, 'visual-spec-diff.json'),
94
+ };
95
+ await Promise.all([
96
+ writeJson(specPaths.idealSpecPath, parsed.data.idealSpec),
97
+ writeJson(specPaths.referenceSpecPath, parsed.data.referenceSpec),
98
+ writeJson(specPaths.diffPath, parsed.data.diff),
99
+ ]);
100
+ return {
101
+ promptPath,
102
+ codexPromptPath,
103
+ claudePromptPath,
104
+ factsPath,
105
+ examplePath,
106
+ schemaPath,
107
+ instructionsPath,
108
+ responsePath,
109
+ status: 'validated',
110
+ specPaths,
111
+ diffHighlights: parsed.data.diff.mismatches.slice(0, 4).map((finding) => `${finding.area}: ${finding.impact}`),
112
+ };
113
+ }
114
+ async function readBundle(filePath) {
115
+ try {
116
+ return { status: 'ok', value: JSON.parse(await readFile(filePath, 'utf8')) };
117
+ }
118
+ catch {
119
+ try {
120
+ await readFile(filePath, 'utf8');
121
+ return { status: 'invalid-json' };
122
+ }
123
+ catch {
124
+ return { status: 'missing' };
125
+ }
126
+ }
127
+ }
128
+ async function writeJson(filePath, value) {
129
+ await writeFile(filePath, JSON.stringify(value, null, 2) + '\n');
130
+ }
131
+ function defaultInstructions() {
132
+ return `# Visual Spec Instructions
133
+
134
+ Add target-specific guidance for Codex or Claude here.
135
+
136
+ Examples:
137
+
138
+ - Preserve the product stance around side-by-side screenshots as visual ground truth.
139
+ - Treat copy, disabled state semantics, and mobile behavior as first-class evidence.
140
+ - Call out uncertainty instead of inventing exact measurements from the images.
141
+ `;
142
+ }
143
+ //# sourceMappingURL=visual-spec-agent-pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-spec-agent-pack.js","sourceRoot":"","sources":["../../src/core/visual-spec-agent-pack.ts"],"names":[],"mappings":"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"}