foundry-design 0.2.0-beta.21 → 0.2.0-beta.23

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 (41) hide show
  1. package/README.md +6 -4
  2. package/dist/delivery-capture.d.ts +8 -0
  3. package/dist/delivery-capture.d.ts.map +1 -0
  4. package/dist/delivery-capture.js +234 -0
  5. package/dist/delivery-capture.js.map +1 -0
  6. package/dist/delivery-export.d.ts +3 -1
  7. package/dist/delivery-export.d.ts.map +1 -1
  8. package/dist/delivery-export.js +172 -8
  9. package/dist/delivery-export.js.map +1 -1
  10. package/dist/doctor.d.ts +8 -1
  11. package/dist/doctor.d.ts.map +1 -1
  12. package/dist/doctor.js +110 -13
  13. package/dist/doctor.js.map +1 -1
  14. package/dist/index.js +51 -27
  15. package/dist/index.js.map +1 -1
  16. package/dist/indexer.d.ts.map +1 -1
  17. package/dist/indexer.js +9 -0
  18. package/dist/indexer.js.map +1 -1
  19. package/dist/installer.d.ts +2 -1
  20. package/dist/installer.d.ts.map +1 -1
  21. package/dist/installer.js +2 -1
  22. package/dist/installer.js.map +1 -1
  23. package/dist/release.d.ts +3 -3
  24. package/dist/release.js +1 -1
  25. package/dist/session-resume.d.ts +16 -0
  26. package/dist/session-resume.d.ts.map +1 -0
  27. package/dist/session-resume.js +30 -0
  28. package/dist/session-resume.js.map +1 -0
  29. package/dist/source-anchor.d.ts +8 -0
  30. package/dist/source-anchor.d.ts.map +1 -0
  31. package/dist/source-anchor.js +40 -0
  32. package/dist/source-anchor.js.map +1 -0
  33. package/dist/visual-check-capture.d.ts +8 -0
  34. package/dist/visual-check-capture.d.ts.map +1 -0
  35. package/dist/visual-check-capture.js +331 -0
  36. package/dist/visual-check-capture.js.map +1 -0
  37. package/dist/visual-check.d.ts +10 -0
  38. package/dist/visual-check.d.ts.map +1 -0
  39. package/dist/visual-check.js +411 -0
  40. package/dist/visual-check.js.map +1 -0
  41. package/package.json +6 -4
package/README.md CHANGED
@@ -10,7 +10,9 @@ Design Branches keeps parallel visual directions isolated inside one local sessi
10
10
 
11
11
  Foundry is distributed through npm, so testers do not need GitHub access.
12
12
 
13
- > **Current beta release:** `0.2.0-beta.21`. After publication, both npm `latest` and `beta` resolve to this release.
13
+ > **Current beta release:** `0.2.0-beta.23`. For an exact-version install, use `foundry-design@0.2.0-beta.23`.
14
+
15
+ The redesigned workspace now opens by default. Existing `ui=next` links continue to work; append `ui=legacy` to the workspace URL only when you need the previous interface for compatibility.
14
16
 
15
17
  Full documentation is available at [withfoundry.ai](https://withfoundry.ai).
16
18
 
@@ -141,10 +143,10 @@ If an npm mirror or existing `npx` cache reports an old tag, bypass it with a te
141
143
 
142
144
  ```bash
143
145
  FOUNDRY_NPX_CACHE="$(mktemp -d)"
144
- npx --yes --prefer-online --registry=https://registry.npmjs.org --cache "$FOUNDRY_NPX_CACHE" --package=foundry-design@0.2.0-beta.21 foundry-design
146
+ npx --yes --prefer-online --registry=https://registry.npmjs.org --cache "$FOUNDRY_NPX_CACHE" --package=foundry-design@0.2.0-beta.23 foundry-design
145
147
  ```
146
148
 
147
- The beta supports Node.js 20 or newer. Read the [local-first safety model](https://withfoundry.ai/#safety) before using it with sensitive work.
149
+ The CLI supports Node.js 20.9 or newer. Repository development and the framework compatibility fixtures require Node.js 20.19 or newer. Read the [local-first safety model](https://withfoundry.ai/#safety) before using it with sensitive work.
148
150
 
149
151
  ## Agent plugin
150
152
 
@@ -158,7 +160,7 @@ Foundry remains local-first. Installing the plugin does not create an account, e
158
160
 
159
161
  ### Cursor one-click connection
160
162
 
161
- [Add the Foundry MCP bridge to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=foundry-design-control&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcHJlZmVyLW9ubGluZSIsImZvdW5kcnktZGVzaWduLW1jcC1zZXJ2ZXJAMC4yLjAtYmV0YS4yMSJdLCJlbnYiOnsiRk9VTkRSWV9ERVNJR05fUlVOVElNRV9VUkwiOiJodHRwOi8vMTI3LjAuMC4xOjQzODcifX0%3D), then run `npx foundry-design` inside the project. The repository's Cursor plugin additionally bundles the Foundry skill and session-start hook for marketplace distribution.
163
+ [Add the Foundry MCP bridge to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=foundry-design-control&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcHJlZmVyLW9ubGluZSIsImZvdW5kcnktZGVzaWduLW1jcC1zZXJ2ZXJAMC4yLjAtYmV0YS4yMyJdLCJlbnYiOnsiRk9VTkRSWV9ERVNJR05fUlVOVElNRV9VUkwiOiJodHRwOi8vMTI3LjAuMC4xOjQzODcifX0%3D), then run `npx foundry-design` inside the project. The repository's Cursor plugin additionally bundles the Foundry skill and session-start hook for marketplace distribution.
162
164
 
163
165
  ## Current capabilities
164
166
 
@@ -0,0 +1,8 @@
1
+ import { type ChangeContext, type DesignChange, type VisualScreen } from 'foundry-design-protocol';
2
+ import type { DeliveryCaptureRequest, DeliveryCaptureResult, DeliveryEvidenceRequest } from 'foundry-design-runtime';
3
+ /** Reads one runtime-indexed capture, never a client-supplied file or arbitrary screenshot. */
4
+ export declare function readDeliveryEvidence({ projectRoot, evidence, }: DeliveryEvidenceRequest): Promise<Uint8Array>;
5
+ export declare function deliveryCaptureScreen(input: DeliveryCaptureRequest, change: DesignChange, context: ChangeContext): VisualScreen;
6
+ /** A reviewed Apply is the only caller. Fresh contexts never install the overlay or replay drafts. */
7
+ export declare function captureDeliveryEvidence(input: DeliveryCaptureRequest): Promise<DeliveryCaptureResult>;
8
+ //# sourceMappingURL=delivery-capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-capture.d.ts","sourceRoot":"","sources":["../src/delivery-capture.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,EAEjB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAOhC,+FAA+F;AAC/F,wBAAsB,oBAAoB,CAAC,EACzC,WAAW,EACX,QAAQ,GACT,EAAE,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0C/C;AAsBD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,sBAAsB,EAC7B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,GACrB,YAAY,CAmFd;AA4BD,sGAAsG;AACtG,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CA6EhC"}
@@ -0,0 +1,234 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { constants } from 'node:fs';
4
+ import { lstat, mkdir, open, realpath } from 'node:fs/promises';
5
+ import { join, relative, sep } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ import { chromium } from 'playwright';
8
+ import sharp from 'sharp';
9
+ import { visualScreenSchema, } from 'foundry-design-protocol';
10
+ import { captureVisualScreen, UnsupportedVisualContextError, validateVisualUrl, } from './visual-check-capture.js';
11
+ /** Reads one runtime-indexed capture, never a client-supplied file or arbitrary screenshot. */
12
+ export async function readDeliveryEvidence({ projectRoot, evidence, }) {
13
+ const capture = evidence.capture;
14
+ if (!capture?.sha256 ||
15
+ !capture.conditions ||
16
+ !['before', 'rebuilt'].includes(capture.phase) ||
17
+ !/^\.foundry\/sessions\/delivery-evidence\/(before|rebuilt)-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.png$/.test(evidence.path) ||
18
+ !evidence.path.split('/').at(-1)?.startsWith(`${capture.phase}-`))
19
+ throw new Error('Evidence is not a trusted source capture.');
20
+ const root = await realpath(projectRoot);
21
+ let file = root;
22
+ const parts = evidence.path.split('/');
23
+ for (const [index, part] of parts.entries()) {
24
+ file = join(file, part);
25
+ const stat = await lstat(file);
26
+ if (stat.isSymbolicLink() || (index < parts.length - 1 ? !stat.isDirectory() : !stat.isFile()))
27
+ throw new Error('Evidence paths must remain inside the project capture directory.');
28
+ }
29
+ if ((await realpath(file)) !== file)
30
+ throw new Error('Evidence path changed during validation.');
31
+ const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
32
+ try {
33
+ const stat = await handle.stat();
34
+ if (!stat.isFile() || stat.size > 20 * 1024 * 1024)
35
+ throw new Error('Evidence image exceeds the safe size limit.');
36
+ const png = await handle.readFile();
37
+ if (createHash('sha256').update(png).digest('hex') !== capture.sha256)
38
+ throw new Error('Evidence image does not match its recorded digest.');
39
+ const metadata = await sharp(png, { limitInputPixels: 40_000_000 }).metadata();
40
+ const scale = capture.conditions.deviceScaleFactor;
41
+ if (metadata.format !== 'png' ||
42
+ metadata.width !== capture.viewport.width * scale ||
43
+ metadata.height !== capture.viewport.height * scale)
44
+ throw new Error('Evidence image does not match its recorded viewport.');
45
+ return png;
46
+ }
47
+ finally {
48
+ await handle.close();
49
+ }
50
+ }
51
+ function themeHook(id, graph) {
52
+ if (id === 'current' || id === 'system')
53
+ return undefined;
54
+ const theme = graph?.themes?.find((candidate) => candidate.id === id);
55
+ if (!theme?.selector)
56
+ throw new UnsupportedVisualContextError(`Theme ${id} has no indexed root selector.`);
57
+ const attribute = /^(html|body|:root)?\[([a-z-]+)=["']?([^"'\]]+)["']?\]$/.exec(theme.selector);
58
+ if (attribute && (!theme.attribute || theme.attribute === attribute[2]))
59
+ return {
60
+ selector: attribute[1] || 'html',
61
+ attribute: attribute[2],
62
+ value: theme.value ?? attribute[3],
63
+ };
64
+ const className = /^(html|body|:root)?\.([a-zA-Z_-][\w-]*)$/.exec(theme.selector);
65
+ if (className)
66
+ return { selector: className[1] || 'html', attribute: 'class', value: className[2] };
67
+ throw new UnsupportedVisualContextError(`Theme ${id} does not expose a supported root attribute or class hook.`);
68
+ }
69
+ export function deliveryCaptureScreen(input, change, context) {
70
+ if (!input.changeSet.context.targetUrl)
71
+ throw new UnsupportedVisualContextError('No product preview URL is configured.');
72
+ const url = validateVisualUrl(input.changeSet.context.targetUrl);
73
+ const selector = typeof change.target.locator.selector === 'string'
74
+ ? change.target.locator.selector
75
+ : typeof change.target.locator.foundryId === 'string'
76
+ ? `[data-foundry-id=${JSON.stringify(change.target.locator.foundryId)}]`
77
+ : undefined;
78
+ if (!selector)
79
+ throw new UnsupportedVisualContextError('Reviewed target has no browser selector.');
80
+ const breakpoint = input.designGraph?.breakpoints?.find((candidate) => candidate.id === context.breakpoint);
81
+ const viewport = context.breakpoint === 'current'
82
+ ? input.changeSet.context.viewport
83
+ : breakpoint && {
84
+ width: breakpoint.width,
85
+ height: breakpoint.height ?? input.changeSet.context.viewport?.height,
86
+ };
87
+ if (!viewport?.width || !viewport.height)
88
+ throw new UnsupportedVisualContextError(`Breakpoint ${context.breakpoint} has no recorded dimensions.`);
89
+ let selectedTheme = context.theme;
90
+ let state = 'current';
91
+ let stateHook;
92
+ if (context.state !== 'current') {
93
+ const authored = input.designGraph?.states?.find((candidate) => candidate.id === context.state);
94
+ if (!authored)
95
+ throw new UnsupportedVisualContextError(`State ${context.state} is not indexed.`);
96
+ if (authored.pseudoStates?.length)
97
+ throw new UnsupportedVisualContextError(`State ${context.state} needs pseudo-state replay; no visual capture was fabricated.`);
98
+ if (authored.reducedMotion === false)
99
+ throw new UnsupportedVisualContextError(`State ${context.state} requires motion; the still-image capture uses reduced motion.`);
100
+ if (authored.viewport &&
101
+ (authored.viewport.width !== viewport.width || authored.viewport.height !== viewport.height))
102
+ throw new UnsupportedVisualContextError(`State ${context.state} has different authored dimensions.`);
103
+ if (authored.theme) {
104
+ if (!['current', 'system', authored.theme].includes(selectedTheme))
105
+ throw new UnsupportedVisualContextError('Authored state and requested theme conflict.');
106
+ selectedTheme = authored.theme;
107
+ }
108
+ for (const [key, value] of Object.entries(authored.query ?? {}))
109
+ url.searchParams.set(key, value);
110
+ const variants = Object.entries(authored.variant ?? {});
111
+ if (variants.length > 1)
112
+ throw new UnsupportedVisualContextError(`State ${context.state} has multiple variant axes; no partial capture was retained.`);
113
+ if (variants[0]) {
114
+ const [property, value] = variants[0];
115
+ state = context.state;
116
+ stateHook = {
117
+ selector,
118
+ attribute: `data-${property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`,
119
+ value: String(value),
120
+ };
121
+ }
122
+ }
123
+ validateVisualUrl(url.toString());
124
+ return visualScreenSchema.parse({
125
+ version: 1,
126
+ name: 'delivery-capture',
127
+ url: url.toString(),
128
+ viewport,
129
+ theme: ['current', 'system'].includes(selectedTheme) ? 'current' : selectedTheme,
130
+ state,
131
+ themeHook: themeHook(selectedTheme, input.designGraph),
132
+ stateHook,
133
+ targets: [selector],
134
+ masks: [],
135
+ });
136
+ }
137
+ async function ignoredCaptureDirectory(projectRoot) {
138
+ const root = await realpath(projectRoot);
139
+ const local = '.foundry/sessions/delivery-evidence';
140
+ const { stdout } = await promisify(execFile)('git', ['check-ignore', '--', `${local}/capture.png`], { cwd: root, timeout: 2500 });
141
+ if (!stdout.trim())
142
+ throw new Error('Delivery evidence needs the ignored .foundry/sessions folder created by setup.');
143
+ let directory = root;
144
+ for (const part of local.split('/')) {
145
+ directory = join(directory, part);
146
+ const stat = await lstat(directory).catch((error) => {
147
+ if (error.code === 'ENOENT')
148
+ return undefined;
149
+ throw error;
150
+ });
151
+ if (stat?.isSymbolicLink() || (stat && !stat.isDirectory()))
152
+ throw new Error('Delivery evidence directory must be a local project directory.');
153
+ if (!stat)
154
+ await mkdir(directory);
155
+ }
156
+ return directory;
157
+ }
158
+ /** A reviewed Apply is the only caller. Fresh contexts never install the overlay or replay drafts. */
159
+ export async function captureDeliveryEvidence(input) {
160
+ const result = { screenshots: [], unavailable: [] };
161
+ if (input.signal.aborted)
162
+ return { screenshots: [], unavailable: ['Visual capture was cancelled.'] };
163
+ const root = await realpath(input.changeSet.context.projectRoot);
164
+ const directory = await ignoredCaptureDirectory(root);
165
+ const browser = await chromium.launch({ headless: true, timeout: 8000 });
166
+ const abort = () => {
167
+ void browser.close().catch(() => { });
168
+ };
169
+ input.signal.addEventListener('abort', abort, { once: true });
170
+ try {
171
+ const seen = new Set();
172
+ for (const change of input.changeSet.changes) {
173
+ for (const breakpoint of change.contextSet.breakpoints)
174
+ for (const theme of change.contextSet.themes)
175
+ for (const state of change.contextSet.states) {
176
+ const context = { breakpoint, theme, state };
177
+ const key = JSON.stringify([change.target.id, context]);
178
+ if (seen.has(key))
179
+ continue;
180
+ seen.add(key);
181
+ const label = `${change.target.label}: ${breakpoint} / ${theme} / ${state}`;
182
+ if (input.signal.aborted) {
183
+ result.unavailable.push(`${label}: capture time limit reached.`);
184
+ continue;
185
+ }
186
+ try {
187
+ const screen = deliveryCaptureScreen(input, change, context);
188
+ const path = join(directory, `${input.phase}-${randomUUID()}.png`);
189
+ const captured = await captureVisualScreen(browser, screen, root, path);
190
+ const geometry = captured.geometry[0];
191
+ if (captured.geometry.length !== 1 ||
192
+ !geometry ||
193
+ geometry.width <= 0 ||
194
+ geometry.height <= 0 ||
195
+ geometry.x >= screen.viewport.width ||
196
+ geometry.y >= screen.viewport.height ||
197
+ geometry.x + geometry.width <= 0 ||
198
+ geometry.y + geometry.height <= 0)
199
+ throw new UnsupportedVisualContextError('Reviewed target does not resolve to one visible rendered element.');
200
+ result.screenshots.push({
201
+ label: `${input.phase === 'before' ? 'Source before Apply' : 'Verified rebuilt source'} · ${label}`,
202
+ path: relative(root, path).split(sep).join('/'),
203
+ createdAt: captured.createdAt,
204
+ capture: {
205
+ version: 1,
206
+ phase: input.phase,
207
+ context,
208
+ viewport: screen.viewport,
209
+ sourceRevision: input.revision,
210
+ applyRunId: input.applyRunId,
211
+ targetId: change.target.id,
212
+ sha256: captured.screenshotHash,
213
+ conditions: {
214
+ motion: captured.motion,
215
+ browser: captured.browser,
216
+ platform: captured.platform,
217
+ deviceScaleFactor: captured.deviceScaleFactor,
218
+ },
219
+ },
220
+ });
221
+ }
222
+ catch (error) {
223
+ result.unavailable.push(`${input.phase} ${label}: ${error instanceof Error ? error.message : 'capture unavailable'}`);
224
+ }
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+ finally {
230
+ input.signal.removeEventListener('abort', abort);
231
+ await browser.close().catch(() => { });
232
+ }
233
+ }
234
+ //# sourceMappingURL=delivery-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-capture.js","sourceRoot":"","sources":["../src/delivery-capture.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { DeliveryRecord } from 'foundry-design-protocol';
2
+ export { matchingDeliveryCaptures } from 'foundry-design-protocol';
2
3
  import { type StoredSession } from 'foundry-design-runtime';
3
4
  export interface DeliveryExportManifest {
4
5
  format: 'foundry.delivery-export';
@@ -20,7 +21,8 @@ export declare class DeliveryExportConflictError extends Error {
20
21
  readonly documentationPageIds: string[];
21
22
  constructor(conflictingPaths: string[], documentationPageIds: string[]);
22
23
  }
23
- export declare function renderPortableDeliveryJson(record: DeliveryRecord, projectRoot: string): string;
24
+ export declare function renderPortableDeliveryJson(record: DeliveryRecord, projectRoot: string, session?: Pick<StoredSession, 'applyRuns'>): string;
25
+ export declare function renderEngineeringBrief(session: StoredSession, record: DeliveryRecord): string;
24
26
  export declare function deliveryExportFiles(session: StoredSession, record: DeliveryRecord): Map<string, string>;
25
27
  export declare function resolveManagedDeliveryPath(outputRoot: string, relativePath: string): string;
26
28
  export declare function writeRepositoryDeliveryExport(outputRoot: string, session: StoredSession, record: DeliveryRecord, now?: string): Promise<DeliveryExportManifest>;
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-export.d.ts","sourceRoot":"","sources":["../src/delivery-export.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,yBAAyB,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,kBAAkB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClF;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,EAAE,CAAC;gBAE5B,gBAAgB,EAAE,MAAM,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE;CAQvE;AAqBD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAM9F;AA+DD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,cAAc,GACrB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwDrB;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAc3F;AAwBD,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,cAAc,EACtB,GAAG,SAA2B,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAsDjC"}
1
+ {"version":3,"file":"delivery-export.d.ts","sourceRoot":"","sources":["../src/delivery-export.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,yBAAyB,CAAC;AAEjF,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,yBAAyB,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,kBAAkB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClF;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,EAAE,CAAC;gBAE5B,gBAAgB,EAAE,MAAM,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE;CAQvE;AAqBD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,GACzC,MAAM,CAeR;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CA+G7F;AA+ED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,cAAc,GACrB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyErB;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAc3F;AAuFD,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,cAAc,EACtB,GAAG,SAA2B,GAC7B,OAAO,CAAC,sBAAsB,CAAC,CAoEjC"}
@@ -1,6 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { access, lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
3
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ import sharp from 'sharp';
5
+ import { engineeringVerification, matchingDeliveryCaptures } from 'foundry-design-protocol';
6
+ export { matchingDeliveryCaptures } from 'foundry-design-protocol';
4
7
  import { renderDeliveryMarkdown, sanitizeDeliveryText, } from 'foundry-design-runtime';
5
8
  export class DeliveryExportConflictError extends Error {
6
9
  conflictingPaths;
@@ -28,12 +31,98 @@ function sanitizeJsonValue(value, projectRoot) {
28
31
  function safeJson(value, projectRoot) {
29
32
  return `${JSON.stringify(sanitizeJsonValue(value, projectRoot), null, 2)}\n`;
30
33
  }
31
- export function renderPortableDeliveryJson(record, projectRoot) {
32
- return JSON.stringify(sanitizeJsonValue({ format: 'foundry.delivery-record', version: 1, record }, projectRoot), null, 2);
34
+ export function renderPortableDeliveryJson(record, projectRoot, session) {
35
+ const run = session?.applyRuns.find((item) => item.id === record.applyRunId);
36
+ return JSON.stringify(sanitizeJsonValue({
37
+ format: 'foundry.delivery-record',
38
+ version: 1,
39
+ record,
40
+ reviewedChangeSet: run?.reviewedChangeSet,
41
+ }, projectRoot), null, 2);
42
+ }
43
+ export function renderEngineeringBrief(session, record) {
44
+ const root = session.changeSet.context.projectRoot;
45
+ const safe = (value) => sanitizeDeliveryText(value, root)
46
+ .replace(/\|/g, '\\|')
47
+ .replace(/[\r\n]+/g, ' ');
48
+ const value = (input) => safe(typeof input === 'string' ? input : (JSON.stringify(input) ?? 'unknown'));
49
+ const run = session.applyRuns.find((item) => item.id === record.applyRunId);
50
+ const reviewed = run?.reviewedChangeSet;
51
+ const changes = reviewed?.changes.filter((change) => record.changeIds.includes(change.id)) ?? [];
52
+ const lines = [
53
+ renderDeliveryMarkdown({ ...record, verificationResults: [] }, root),
54
+ 'The coverage below is checked against the frozen request and current Apply attempt.',
55
+ '',
56
+ '## Reviewed implementation contract',
57
+ '',
58
+ ];
59
+ if (!reviewed)
60
+ lines.push('The frozen reviewed contract is unavailable in this older record. Exact edits and complete context verification cannot be reconstructed.', '');
61
+ else {
62
+ lines.push('| Target / property | Before | Requested | Source | Context | Verification |', '| --- | --- | --- | --- | --- | --- |');
63
+ for (const change of changes) {
64
+ const mapping = change.mappingCandidates.find((item) => item.id === change.selectedMappingId) ??
65
+ (change.mappingCandidates.length === 1 ? change.mappingCandidates[0] : undefined);
66
+ const source = mapping?.source ?? change.target.source;
67
+ for (const breakpoint of change.contextSet.breakpoints)
68
+ for (const theme of change.contextSet.themes)
69
+ for (const state of change.contextSet.states) {
70
+ const verification = engineeringVerification(record, run, change, {
71
+ breakpoint,
72
+ theme,
73
+ state,
74
+ });
75
+ const result = verification.result;
76
+ const outcome = verification.status === 'passed'
77
+ ? 'Passed'
78
+ : verification.status === 'failed'
79
+ ? 'Failed'
80
+ : 'Untested';
81
+ lines.push(`| ${safe(change.target.label)} / ${safe(change.property)} | ${value(change.before)} | ${value(change.after)} | ${source ? safe(`${source.file}${source.line ? `:${source.line}` : ''}`) : 'Mapping unavailable'} | ${safe(`${breakpoint} / ${theme} / ${state}`)} | ${outcome}${result ? `: rendered ${value(result.rendered)}` : ''}${verification.reason ? ` (${safe(verification.reason)})` : ''} |`);
82
+ }
83
+ }
84
+ if (!changes.length)
85
+ lines.push('| No matching changes in the frozen contract | | | | | Untested |');
86
+ lines.push('');
87
+ }
88
+ lines.push('## Execution provenance', '', `- Reviewed source revision: ${safe(record.baselineRevision ?? 'Unavailable')}`, `- Applied source revision: ${safe(record.appliedRevision ?? 'Unavailable')}`, `- Apply run: ${safe(record.applyRunId)}`, `- Design graph revision: ${safe(record.designGraphRevision ?? 'Unavailable')}`, `- Narrative origin: ${record.narrativeSource}`, '');
89
+ if (record.blockers.length)
90
+ lines.push('## Unresolved blockers', '', ...record.blockers.map((item) => `- ${safe(item)}`), '');
91
+ const pairs = matchingDeliveryCaptures(record);
92
+ lines.push('## Visual evidence', '');
93
+ if (record.captureIssues?.length)
94
+ lines.push(...record.captureIssues.map((issue) => `- Capture unavailable: ${safe(issue)}`), '');
95
+ if (!pairs.length)
96
+ lines.push('No matching before/rebuilt image pair is recorded. Existing references are not proof of a visual comparison.', '');
97
+ for (const pair of pairs) {
98
+ const context = pair.after.capture;
99
+ lines.push(`### ${safe(`${context.context.breakpoint} / ${context.context.theme} / ${context.context.state}`)} · ${context.viewport.width} × ${context.viewport.height}`, '', `Before: ${safe(pair.before.path)}`, '', `Rebuilt: ${safe(pair.after.path)}`, '');
100
+ }
101
+ for (const evidence of record.evidence) {
102
+ const phase = evidence.capture?.phase === 'preview'
103
+ ? 'Temporary preview'
104
+ : evidence.capture?.phase === 'rebuilt'
105
+ ? 'Rebuilt capture (see provenance)'
106
+ : evidence.capture?.phase === 'before'
107
+ ? 'Before source change'
108
+ : 'Unclassified reference';
109
+ lines.push(`- ${phase}: ${safe(evidence.label)} — ${safe(evidence.path)} (${safe(evidence.createdAt)})`);
110
+ }
111
+ if (!record.validationResults.length)
112
+ lines.push('', 'No validation results are recorded.');
113
+ if (!record.verificationResults.length)
114
+ lines.push('', 'No rendered verification results are recorded.');
115
+ return `${lines.join('\n')}\n`;
33
116
  }
34
117
  function historyMarkdown(session, projectRoot) {
35
118
  const safe = (value) => sanitizeDeliveryText(value, projectRoot);
36
119
  const lines = ['# Foundry design history', ''];
120
+ for (const milestone of session.deliveryMilestones) {
121
+ const entries = session.designHistory.filter((entry) => milestone.entryIds.includes(entry.id));
122
+ lines.push(`## Milestone: ${safe(milestone.name)} (${milestone.status})`, '', safe(milestone.summary), '', ...entries.map((entry) => `- ${safe(entry.title)}: ${safe(entry.summary)} (${safe(entry.deliveryRecordId)})`), '');
123
+ if (milestone.entryIds.length > entries.length)
124
+ lines.push('Some milestone entries are unavailable in this session.', '');
125
+ }
37
126
  if (!session.designHistory.length)
38
127
  lines.push('No verified delivery records yet.', '');
39
128
  for (const entry of session.designHistory) {
@@ -84,20 +173,35 @@ export function deliveryExportFiles(session, record) {
84
173
  const files = new Map();
85
174
  const recordId = deliveryIdentifier(record.id);
86
175
  const safe = (value) => sanitizeDeliveryText(value, root);
87
- addManagedFile(files, `docs/foundry/handoffs/${recordId}.md`, `${renderDeliveryMarkdown(record, root)}\n`);
88
- addManagedFile(files, `docs/foundry/handoffs/${recordId}.json`, safeJson({ format: 'foundry.delivery-record', version: 1, record }, root));
176
+ addManagedFile(files, `docs/foundry/handoffs/${recordId}.md`, renderEngineeringBrief(session, record));
177
+ addManagedFile(files, `docs/foundry/handoffs/${recordId}.json`, `${renderPortableDeliveryJson(record, root, session)}\n`);
89
178
  addManagedFile(files, `docs/foundry/handoffs/${recordId}.pr.md`, [
90
179
  `# ${safe(record.title)}`,
91
180
  '',
92
181
  safe(record.summary),
93
182
  '',
183
+ '## Intent',
184
+ '',
185
+ safe(record.intent),
186
+ '',
187
+ '## Affected source and contexts',
188
+ '',
189
+ ...record.affectedFiles.map((file) => `- ${safe(file)}`),
190
+ ...record.contexts.map((context) => `- ${safe(context.breakpoint)} / ${safe(context.theme)} / ${safe(context.state)}`),
191
+ '',
192
+ '## Risks and unresolved questions',
193
+ '',
194
+ ...[...record.risks, ...record.questions, ...record.blockers].map((item) => `- ${safe(item)}`),
195
+ '',
94
196
  '## Validation',
95
197
  '',
96
198
  ...record.validationResults.map((item) => `- [${item.passed ? 'x' : ' '}] ${safe(item.name)}${item.summary ? `: ${safe(item.summary)}` : ''}`),
97
199
  '',
98
200
  '## Rendered verification',
99
201
  '',
100
- ...record.verificationResults.map((item) => `- [${item.passed ? 'x' : ' '}] ${safe(item.property)}${item.context ? ` (${safe(item.context.breakpoint)} / ${safe(item.context.theme)} / ${safe(item.context.state)})` : ''}${item.reason ? `: ${safe(item.reason)}` : ''}`),
202
+ 'See the engineering brief for exact request, claim and context coverage. Raw pass flags alone are not treated as proof.',
203
+ '',
204
+ `Complete engineering brief: ${recordId}.md`,
101
205
  '',
102
206
  ].join('\n'));
103
207
  for (const page of session.documentationPages)
@@ -146,6 +250,60 @@ async function assertNoSymlinkTraversal(root, target) {
146
250
  }
147
251
  }
148
252
  }
253
+ async function bundledDeliveryEvidence(projectRoot, record) {
254
+ const files = new Map();
255
+ const attachments = [];
256
+ const candidates = [
257
+ ...new Set([
258
+ ...record.evidence.map((item) => item.path),
259
+ ...record.verificationResults
260
+ .map((item) => item.screenshotPath)
261
+ .filter((item) => Boolean(item)),
262
+ ]),
263
+ ];
264
+ for (const [index, source] of candidates.entries()) {
265
+ try {
266
+ const target = resolve(projectRoot, source);
267
+ const inside = relative(projectRoot, target);
268
+ if (!inside || inside === '..' || inside.startsWith(`..${sep}`) || isAbsolute(inside))
269
+ throw new Error('Evidence is outside the project and was not copied.');
270
+ await assertNoSymlinkTraversal(projectRoot, target);
271
+ const extension = extname(target).toLowerCase();
272
+ if (!['.png', '.jpg', '.jpeg', '.webp'].includes(extension))
273
+ throw new Error('Only PNG, JPEG and WebP evidence can be bundled.');
274
+ const stat = await lstat(target);
275
+ if (!stat.isFile() || stat.size > 20 * 1024 * 1024)
276
+ throw new Error('Evidence is not a supported image file below 20 MB.');
277
+ const content = await readFile(target);
278
+ const metadata = await sharp(content).metadata();
279
+ if (!['png', 'jpeg', 'webp'].includes(metadata.format ?? ''))
280
+ throw new Error('Evidence file content is not a supported image.');
281
+ const provenance = record.evidence.find((item) => item.path === source)?.capture;
282
+ const scale = provenance?.conditions?.deviceScaleFactor ?? 1;
283
+ if (provenance &&
284
+ (metadata.width !== Math.round(provenance.viewport.width * scale) ||
285
+ metadata.height !== Math.round(provenance.viewport.height * scale)))
286
+ throw new Error('Image dimensions do not match the recorded capture viewport.');
287
+ const expected = record.evidence.find((item) => item.path === source)?.capture?.sha256;
288
+ const contentHash = sha256(content);
289
+ if (expected && expected !== contentHash)
290
+ throw new Error('Evidence hash no longer matches the capture provenance.');
291
+ const path = `docs/foundry/assets/${deliveryIdentifier(record.id)}-${index}${extension}`;
292
+ files.set(path, content);
293
+ attachments.push({ source, status: 'bundled', path, sha256: contentHash });
294
+ }
295
+ catch (error) {
296
+ attachments.push({
297
+ source,
298
+ status: 'unavailable',
299
+ reason: error.code === 'ENOENT'
300
+ ? 'Evidence file is not present in the project.'
301
+ : String(error.message),
302
+ });
303
+ }
304
+ }
305
+ return { files, attachments };
306
+ }
149
307
  export async function writeRepositoryDeliveryExport(outputRoot, session, record, now = new Date().toISOString()) {
150
308
  const root = resolve(outputRoot);
151
309
  const manifestPath = resolveManagedDeliveryPath(root, 'docs/foundry/manifest.json');
@@ -154,7 +312,13 @@ export async function writeRepositoryDeliveryExport(outputRoot, session, record,
154
312
  ? JSON.parse(await readFile(manifestPath, 'utf8'))
155
313
  : undefined;
156
314
  const priorHashes = new Map(previous?.files.map((file) => [file.path, file.sha256]) ?? []);
157
- const files = deliveryExportFiles(session, record);
315
+ const files = new Map(deliveryExportFiles(session, record));
316
+ const bundled = await bundledDeliveryEvidence(session.changeSet.context.projectRoot, record);
317
+ for (const [path, content] of bundled.files)
318
+ files.set(path, content);
319
+ files.set(`docs/foundry/assets/${deliveryIdentifier(record.id)}.json`, safeJson({ screenshots: record.evidence, attachments: bundled.attachments }, session.changeSet.context.projectRoot));
320
+ const handoffPath = `docs/foundry/handoffs/${deliveryIdentifier(record.id)}.md`;
321
+ files.set(handoffPath, `${files.get(handoffPath)}\n## Bundled image files\n\n${bundled.attachments.length ? bundled.attachments.map((item) => (item.status === 'bundled' ? `- [${sanitizeDeliveryText(item.source, session.changeSet.context.projectRoot)}](../assets/${item.path.split('/').pop()}) · SHA-256 ${item.sha256}` : `- Unavailable: ${sanitizeDeliveryText(item.source, session.changeSet.context.projectRoot)}. ${sanitizeDeliveryText(item.reason ?? '', session.changeSet.context.projectRoot)}`)).join('\n') : 'No image files are referenced.'}\n`);
158
322
  const documentationPages = documentationExports(session);
159
323
  const targets = new Map();
160
324
  for (const relativePath of files.keys()) {
@@ -167,7 +331,7 @@ export async function writeRepositoryDeliveryExport(outputRoot, session, record,
167
331
  const target = targets.get(relativePath);
168
332
  if (!(await exists(target)))
169
333
  continue;
170
- const actualHash = sha256(await readFile(target, 'utf8'));
334
+ const actualHash = sha256(await readFile(target));
171
335
  const managedHash = priorHashes.get(relativePath);
172
336
  if ((managedHash && actualHash !== managedHash) ||
173
337
  (!managedHash && actualHash !== sha256(content))) {