@kbediako/codex-orchestrator 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 (150) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +238 -0
  3. package/dist/bin/codex-orchestrator.js +507 -0
  4. package/dist/orchestrator/src/agents/builder.js +16 -0
  5. package/dist/orchestrator/src/agents/index.js +4 -0
  6. package/dist/orchestrator/src/agents/planner.js +17 -0
  7. package/dist/orchestrator/src/agents/reviewer.js +13 -0
  8. package/dist/orchestrator/src/agents/tester.js +13 -0
  9. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
  10. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
  11. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
  12. package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
  13. package/dist/orchestrator/src/cli/adapters/index.js +4 -0
  14. package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
  15. package/dist/orchestrator/src/cli/doctor.js +48 -0
  16. package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
  17. package/dist/orchestrator/src/cli/exec/command.js +56 -0
  18. package/dist/orchestrator/src/cli/exec/context.js +108 -0
  19. package/dist/orchestrator/src/cli/exec/experience.js +77 -0
  20. package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
  21. package/dist/orchestrator/src/cli/exec/learning.js +62 -0
  22. package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
  23. package/dist/orchestrator/src/cli/exec/summary.js +109 -0
  24. package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
  25. package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
  26. package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
  27. package/dist/orchestrator/src/cli/exec/types.js +1 -0
  28. package/dist/orchestrator/src/cli/init.js +64 -0
  29. package/dist/orchestrator/src/cli/mcp.js +124 -0
  30. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
  31. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
  32. package/dist/orchestrator/src/cli/orchestrator.js +554 -0
  33. package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
  34. package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
  35. package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
  36. package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
  37. package/dist/orchestrator/src/cli/run/environment.js +24 -0
  38. package/dist/orchestrator/src/cli/run/manifest.js +367 -0
  39. package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
  40. package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
  41. package/dist/orchestrator/src/cli/selfCheck.js +12 -0
  42. package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
  43. package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
  44. package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
  45. package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
  46. package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
  47. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
  48. package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
  49. package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
  50. package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
  51. package/dist/orchestrator/src/cli/types.js +1 -0
  52. package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
  53. package/dist/orchestrator/src/cli/ui/controller.js +26 -0
  54. package/dist/orchestrator/src/cli/ui/store.js +240 -0
  55. package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
  56. package/dist/orchestrator/src/cli/utils/fs.js +8 -0
  57. package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
  58. package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
  59. package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
  60. package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
  61. package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
  62. package/dist/orchestrator/src/cli/utils/runId.js +7 -0
  63. package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
  64. package/dist/orchestrator/src/cli/utils/strings.js +8 -0
  65. package/dist/orchestrator/src/cli/utils/time.js +6 -0
  66. package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
  67. package/dist/orchestrator/src/control-plane/index.js +3 -0
  68. package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
  69. package/dist/orchestrator/src/control-plane/types.js +1 -0
  70. package/dist/orchestrator/src/control-plane/validator.js +50 -0
  71. package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
  72. package/dist/orchestrator/src/events/EventBus.js +25 -0
  73. package/dist/orchestrator/src/learning/crystalizer.js +108 -0
  74. package/dist/orchestrator/src/learning/harvester.js +146 -0
  75. package/dist/orchestrator/src/learning/manifest.js +56 -0
  76. package/dist/orchestrator/src/learning/runner.js +177 -0
  77. package/dist/orchestrator/src/learning/validator.js +164 -0
  78. package/dist/orchestrator/src/logger.js +20 -0
  79. package/dist/orchestrator/src/manager.js +388 -0
  80. package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
  81. package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
  82. package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
  83. package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
  84. package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
  85. package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
  86. package/dist/orchestrator/src/persistence/lockFile.js +26 -0
  87. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
  88. package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
  89. package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
  90. package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
  91. package/dist/orchestrator/src/privacy/guard.js +111 -0
  92. package/dist/orchestrator/src/scheduler/index.js +1 -0
  93. package/dist/orchestrator/src/scheduler/plan.js +171 -0
  94. package/dist/orchestrator/src/scheduler/types.js +1 -0
  95. package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
  96. package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
  97. package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
  98. package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
  99. package/dist/orchestrator/src/types.js +1 -0
  100. package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
  101. package/dist/orchestrator/src/utils/errorMessage.js +14 -0
  102. package/dist/orchestrator/src/utils/executionMode.js +69 -0
  103. package/dist/packages/control-plane-schemas/src/index.js +1 -0
  104. package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
  105. package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
  106. package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
  107. package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
  108. package/dist/packages/orchestrator/src/index.js +3 -0
  109. package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
  110. package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
  111. package/dist/packages/orchestrator/src/notifications/index.js +74 -0
  112. package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
  113. package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
  114. package/dist/packages/sdk-node/src/orchestrator.js +195 -0
  115. package/dist/packages/shared/config/designConfig.js +495 -0
  116. package/dist/packages/shared/config/env.js +37 -0
  117. package/dist/packages/shared/config/index.js +2 -0
  118. package/dist/packages/shared/design-artifacts/writer.js +221 -0
  119. package/dist/packages/shared/events/serializer.js +84 -0
  120. package/dist/packages/shared/events/types.js +1 -0
  121. package/dist/packages/shared/manifest/artifactUtils.js +36 -0
  122. package/dist/packages/shared/manifest/designArtifacts.js +665 -0
  123. package/dist/packages/shared/manifest/fileIO.js +29 -0
  124. package/dist/packages/shared/manifest/toolRuns.js +78 -0
  125. package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
  126. package/dist/packages/shared/manifest/types.js +5 -0
  127. package/dist/packages/shared/manifest/validator.js +73 -0
  128. package/dist/packages/shared/manifest/writer.js +2 -0
  129. package/dist/packages/shared/streams/stdio.js +112 -0
  130. package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
  131. package/dist/scripts/design/pipeline/componentize.js +74 -0
  132. package/dist/scripts/design/pipeline/context.js +34 -0
  133. package/dist/scripts/design/pipeline/extract.js +249 -0
  134. package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
  135. package/dist/scripts/design/pipeline/prepare.js +46 -0
  136. package/dist/scripts/design/pipeline/reference.js +94 -0
  137. package/dist/scripts/design/pipeline/state.js +206 -0
  138. package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
  139. package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
  140. package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
  141. package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
  142. package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
  143. package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
  144. package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
  145. package/dist/scripts/design/pipeline/visual-regression.js +137 -0
  146. package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
  147. package/package.json +97 -0
  148. package/schemas/manifest.json +1064 -0
  149. package/templates/README.md +12 -0
  150. package/templates/codex/mcp-client.json +8 -0
@@ -0,0 +1,249 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { loadDesignContext } from './context.js';
5
+ import { appendArtifacts, loadDesignRunState, saveDesignRunState, upsertStage } from './state.js';
6
+ import { stageArtifacts } from '../../../orchestrator/src/persistence/ArtifactStager.js';
7
+ import { loadPlaywright } from './optionalDeps.js';
8
+ async function main() {
9
+ const context = await loadDesignContext();
10
+ const state = await loadDesignRunState(context.statePath);
11
+ const stageId = 'design-extract';
12
+ const metadata = context.config.config.metadata.design;
13
+ if (metadata.captureUrls.length === 0) {
14
+ upsertStage(state, {
15
+ id: stageId,
16
+ title: 'Playwright extractor',
17
+ status: 'skipped',
18
+ notes: ['No capture URLs configured.']
19
+ });
20
+ await saveDesignRunState(context.statePath, state);
21
+ console.log('[design-extract] skipped — no capture URLs configured');
22
+ return;
23
+ }
24
+ const breakpoints = metadata.breakpoints.length > 0 ? metadata.breakpoints : defaultBreakpoints();
25
+ const records = [];
26
+ const stageSummaries = [];
27
+ const notes = [];
28
+ let successCount = 0;
29
+ const playwright = await loadPlaywright();
30
+ const browser = await playwright.chromium.launch({ headless: true });
31
+ const tmpRoot = join(tmpdir(), `design-extract-${Date.now()}`);
32
+ await mkdir(tmpRoot, { recursive: true });
33
+ try {
34
+ for (const url of metadata.captureUrls) {
35
+ for (const breakpoint of breakpoints) {
36
+ try {
37
+ const result = await captureWithPlaywright({
38
+ browser,
39
+ url,
40
+ breakpoint,
41
+ tmpRoot,
42
+ taskId: context.taskId,
43
+ runId: context.runId,
44
+ allowThirdParty: metadata.privacy.allowThirdParty,
45
+ maskSelectors: metadata.maskSelectors ?? []
46
+ });
47
+ records.push(...result.records);
48
+ stageSummaries.push(...result.stageSummaries);
49
+ successCount += 1;
50
+ console.log(`[design-extract] staged assets for ${url} @ ${breakpoint.id}`);
51
+ }
52
+ catch (error) {
53
+ const message = error instanceof Error ? error.message : String(error);
54
+ notes.push(`${url} (${breakpoint.id}): ${message}`);
55
+ console.error(`[design-extract] capture failed for ${url} @ ${breakpoint.id}: ${message}`);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ finally {
61
+ await browser.close();
62
+ }
63
+ appendArtifacts(state, records);
64
+ const stageStatus = successCount === 0 ? 'failed' : notes.length > 0 ? 'failed' : 'succeeded';
65
+ upsertStage(state, {
66
+ id: stageId,
67
+ title: 'Playwright extractor',
68
+ status: stageStatus,
69
+ notes: notes.length > 0 ? notes : undefined,
70
+ metrics: {
71
+ capture_count: successCount,
72
+ failure_count: notes.length,
73
+ breakpoint_count: breakpoints.length
74
+ },
75
+ artifacts: stageSummaries
76
+ });
77
+ await saveDesignRunState(context.statePath, state);
78
+ if (stageStatus === 'failed') {
79
+ throw new Error('Playwright extraction encountered failures. See stage notes for details.');
80
+ }
81
+ }
82
+ async function captureWithPlaywright(options) {
83
+ const { browser, url, breakpoint, tmpRoot, taskId, runId, allowThirdParty, maskSelectors } = options;
84
+ const context = await browser.newContext({
85
+ viewport: { width: breakpoint.width, height: breakpoint.height },
86
+ deviceScaleFactor: breakpoint.deviceScaleFactor
87
+ });
88
+ const baseOrigin = new URL(url).origin;
89
+ if (!allowThirdParty) {
90
+ await context.route('**/*', async (route) => {
91
+ const requestUrl = route.request().url();
92
+ if (isSameOrigin(baseOrigin, requestUrl)) {
93
+ await route.continue();
94
+ }
95
+ else {
96
+ await route.abort();
97
+ }
98
+ });
99
+ }
100
+ const page = await context.newPage();
101
+ try {
102
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 60_000 });
103
+ if (maskSelectors.length > 0) {
104
+ await page.addStyleTag({ content: createMaskStyles(maskSelectors) });
105
+ }
106
+ const slug = slugify(`${url}-${breakpoint.id}`);
107
+ const captureDir = join(tmpRoot, slug);
108
+ await mkdir(captureDir, { recursive: true });
109
+ const domPath = join(captureDir, 'dom.html');
110
+ const cssPath = join(captureDir, 'styles.css');
111
+ const screenshotPath = join(captureDir, 'screenshot.png');
112
+ const metadataPath = join(captureDir, 'metadata.json');
113
+ const html = await page.content();
114
+ const css = await collectStyles(page);
115
+ const screenshot = await page.screenshot({ fullPage: true, type: 'png' });
116
+ const metadata = {
117
+ url,
118
+ breakpoint,
119
+ capturedAt: new Date().toISOString(),
120
+ assets: {
121
+ dom: 'dom.html',
122
+ css: 'styles.css',
123
+ screenshot: 'screenshot.png'
124
+ },
125
+ privacy: {
126
+ allowThirdParty,
127
+ maskSelectors
128
+ }
129
+ };
130
+ await Promise.all([
131
+ writeFile(domPath, html, 'utf8'),
132
+ writeFile(cssPath, css, 'utf8'),
133
+ writeFile(screenshotPath, screenshot),
134
+ writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf8')
135
+ ]);
136
+ const relativeDir = `design/reference/playwright/${sanitizeSegment(breakpoint.id)}/${slug}`;
137
+ const staged = await stageArtifacts({
138
+ taskId,
139
+ runId,
140
+ artifacts: [
141
+ { path: relative(process.cwd(), domPath), description: `DOM snapshot for ${url}` },
142
+ { path: relative(process.cwd(), cssPath), description: `CSS snapshot for ${url}` },
143
+ { path: relative(process.cwd(), screenshotPath), description: `Screenshot for ${url}` },
144
+ { path: relative(process.cwd(), metadataPath), description: `Capture metadata for ${url}` }
145
+ ],
146
+ options: {
147
+ relativeDir,
148
+ overwrite: true
149
+ }
150
+ });
151
+ const [domArtifact, cssArtifact, screenshotArtifact, metadataArtifact] = staged;
152
+ const records = [
153
+ buildDesignRecord('dom', domArtifact.path, url, breakpoint.id),
154
+ buildDesignRecord('css', cssArtifact.path, url, breakpoint.id),
155
+ buildDesignRecord('screenshot', screenshotArtifact.path, url, breakpoint.id),
156
+ {
157
+ stage: 'extract',
158
+ status: 'succeeded',
159
+ relative_path: metadataArtifact.path,
160
+ type: 'metadata',
161
+ description: `${url} (${breakpoint.id}) metadata`,
162
+ metadata
163
+ }
164
+ ];
165
+ const stageSummaries = [
166
+ summaryEntry(domArtifact.path, 'DOM snapshot'),
167
+ summaryEntry(cssArtifact.path, 'CSS snapshot'),
168
+ summaryEntry(screenshotArtifact.path, 'Screenshot'),
169
+ summaryEntry(metadataArtifact.path, 'Metadata')
170
+ ];
171
+ return { records, stageSummaries };
172
+ }
173
+ finally {
174
+ await context.close();
175
+ }
176
+ }
177
+ function summaryEntry(path, description) {
178
+ return {
179
+ relative_path: path,
180
+ stage: 'extract',
181
+ status: 'succeeded',
182
+ description
183
+ };
184
+ }
185
+ function buildDesignRecord(type, path, url, breakpointId) {
186
+ return {
187
+ stage: 'extract',
188
+ status: 'succeeded',
189
+ relative_path: path,
190
+ type,
191
+ description: `${url} (${breakpointId})`
192
+ };
193
+ }
194
+ function createMaskStyles(selectors) {
195
+ const rules = selectors
196
+ .map((selector) => `${selector} { filter: blur(10px) !important; }`)
197
+ .join('\n');
198
+ return `${rules}\n`;
199
+ }
200
+ function isSameOrigin(baseOrigin, requestUrl) {
201
+ try {
202
+ const requestOrigin = new URL(requestUrl).origin;
203
+ return requestOrigin === baseOrigin;
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ async function collectStyles(page) {
210
+ return (await page.evaluate(() => {
211
+ const outputs = [];
212
+ for (const sheet of Array.from(document.styleSheets)) {
213
+ try {
214
+ const rules = sheet.cssRules ? Array.from(sheet.cssRules).map((rule) => rule.cssText).join('\n') : '';
215
+ outputs.push(rules);
216
+ }
217
+ catch (error) {
218
+ const href = sheet.href ?? 'inline';
219
+ outputs.push(`/* Unable to read stylesheet: ${href} (${String(error)}) */`);
220
+ }
221
+ }
222
+ return outputs.join('\n\n');
223
+ }));
224
+ }
225
+ function defaultBreakpoints() {
226
+ return [
227
+ {
228
+ id: 'default',
229
+ width: 1280,
230
+ height: 720
231
+ }
232
+ ];
233
+ }
234
+ function slugify(value) {
235
+ return value
236
+ .toLowerCase()
237
+ .replace(/[^a-z0-9]+/g, '-')
238
+ .replace(/^-+|-+$/g, '')
239
+ .slice(0, 60) || 'capture';
240
+ }
241
+ function sanitizeSegment(value) {
242
+ const slug = slugify(value);
243
+ return slug.length > 0 ? slug : 'default';
244
+ }
245
+ main().catch((error) => {
246
+ console.error('[design-extract] failed to stage assets');
247
+ console.error(error instanceof Error ? error.stack ?? error.message : error);
248
+ process.exitCode = 1;
249
+ });
@@ -0,0 +1,107 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ const DESIGN_SETUP_HINT = 'Run "npm run setup:design-tools" and "npx playwright install" to enable design tooling.';
6
+ function isModuleNotFound(error) {
7
+ const candidate = error;
8
+ if (!candidate) {
9
+ return false;
10
+ }
11
+ const message = candidate.message ?? '';
12
+ return (candidate.code === 'ERR_MODULE_NOT_FOUND' ||
13
+ candidate.code === 'MODULE_NOT_FOUND' ||
14
+ message.includes('Cannot find package') ||
15
+ message.includes('Cannot find module'));
16
+ }
17
+ function missingDependency(specifier) {
18
+ return new Error(`[design-tools] Missing optional dependency "${specifier}". ${DESIGN_SETUP_HINT}`);
19
+ }
20
+ function resolveWithRequire(specifier, base) {
21
+ try {
22
+ const resolver = createRequire(base);
23
+ return resolver.resolve(specifier);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function resolveWithImportMeta(specifier, parentUrl) {
30
+ const resolver = import.meta.resolve;
31
+ if (typeof resolver !== 'function') {
32
+ return null;
33
+ }
34
+ try {
35
+ return resolver(specifier, parentUrl);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ function resolveOptionalDependency(specifier, cwd = process.cwd()) {
42
+ const cwdPackage = join(cwd, 'package.json');
43
+ if (existsSync(cwdPackage)) {
44
+ const cwdRequireResolved = resolveWithRequire(specifier, cwdPackage);
45
+ if (cwdRequireResolved) {
46
+ return cwdRequireResolved;
47
+ }
48
+ const cwdImportResolved = resolveWithImportMeta(specifier, pathToFileURL(cwdPackage).href);
49
+ if (cwdImportResolved) {
50
+ return cwdImportResolved;
51
+ }
52
+ }
53
+ const selfRequireResolved = resolveWithRequire(specifier, import.meta.url);
54
+ if (selfRequireResolved) {
55
+ return selfRequireResolved;
56
+ }
57
+ return resolveWithImportMeta(specifier, import.meta.url);
58
+ }
59
+ function toModuleUrl(resolved) {
60
+ if (resolved.startsWith('file://') || resolved.startsWith('node:')) {
61
+ return resolved;
62
+ }
63
+ return pathToFileURL(resolved).href;
64
+ }
65
+ async function loadOptionalDependency(specifier) {
66
+ const resolved = resolveOptionalDependency(specifier);
67
+ if (!resolved) {
68
+ throw missingDependency(specifier);
69
+ }
70
+ try {
71
+ return (await import(toModuleUrl(resolved)));
72
+ }
73
+ catch (error) {
74
+ if (isModuleNotFound(error)) {
75
+ throw missingDependency(specifier);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ let playwrightPromise = null;
81
+ let pngPromise = null;
82
+ let pixelmatchPromise = null;
83
+ let cheerioPromise = null;
84
+ export async function loadPlaywright() {
85
+ if (!playwrightPromise) {
86
+ playwrightPromise = loadOptionalDependency('playwright');
87
+ }
88
+ return playwrightPromise;
89
+ }
90
+ export async function loadPngjs() {
91
+ if (!pngPromise) {
92
+ pngPromise = loadOptionalDependency('pngjs');
93
+ }
94
+ return pngPromise;
95
+ }
96
+ export async function loadPixelmatch() {
97
+ if (!pixelmatchPromise) {
98
+ pixelmatchPromise = loadOptionalDependency('pixelmatch');
99
+ }
100
+ return pixelmatchPromise;
101
+ }
102
+ export async function loadCheerio() {
103
+ if (!cheerioPromise) {
104
+ cheerioPromise = loadOptionalDependency('cheerio');
105
+ }
106
+ return cheerioPromise;
107
+ }
@@ -0,0 +1,46 @@
1
+ import { loadDesignContext, resolveDesignPipelineId } from './context.js';
2
+ import { loadDesignRunState, mergeMetrics, saveDesignRunState, upsertStage } from './state.js';
3
+ async function main() {
4
+ const context = await loadDesignContext();
5
+ const state = await loadDesignRunState(context.statePath);
6
+ state.configSnapshot = JSON.parse(JSON.stringify(context.config.config));
7
+ const retention = context.config.config.metadata.design.retention;
8
+ state.retention = {
9
+ days: retention.days,
10
+ autoPurge: Boolean(retention.autoPurge),
11
+ policy: 'design.config.retention'
12
+ };
13
+ const designMetadata = context.config.config.metadata.design;
14
+ const privacy = designMetadata.privacy;
15
+ state.privacy = {
16
+ allowThirdParty: Boolean(privacy.allowThirdParty),
17
+ requireApproval: Boolean(privacy.requireApproval),
18
+ maskSelectors: [...designMetadata.maskSelectors],
19
+ approver: privacy.approver ?? null
20
+ };
21
+ mergeMetrics(state, {
22
+ design_pipeline_id: resolveDesignPipelineId(context.config),
23
+ design_pipeline_enabled: designMetadata.enabled,
24
+ capture_url_count: designMetadata.captureUrls.length,
25
+ breakpoint_count: designMetadata.breakpoints.length
26
+ });
27
+ const stageNotes = context.config.warnings.length > 0 ? context.config.warnings : undefined;
28
+ upsertStage(state, {
29
+ id: 'design-config',
30
+ title: 'Resolve design configuration',
31
+ status: 'succeeded',
32
+ notes: stageNotes,
33
+ metrics: {
34
+ config_path: context.designConfigPath
35
+ }
36
+ });
37
+ await saveDesignRunState(context.statePath, state);
38
+ console.log(`[design-config] task=${context.taskId} run=${context.runId} designEnabled=${context.config.config.metadata.design.enabled}`);
39
+ console.log(`[design-config] retention=${state.retention?.days ?? retention.days}d autoPurge=${state.retention?.autoPurge ?? retention.autoPurge}`);
40
+ console.log(`[design-config] privacy allowThirdParty=${state.privacy?.allowThirdParty} requireApproval=${state.privacy?.requireApproval}`);
41
+ }
42
+ main().catch((error) => {
43
+ console.error('[design-config] failed to prepare design context');
44
+ console.error(error instanceof Error ? error.stack ?? error.message : error);
45
+ process.exitCode = 1;
46
+ });
@@ -0,0 +1,94 @@
1
+ import { writeFile, mkdir } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { loadDesignContext } from './context.js';
5
+ import { appendArtifacts, loadDesignRunState, saveDesignRunState, upsertStage } from './state.js';
6
+ import { stageArtifacts } from '../../../orchestrator/src/persistence/ArtifactStager.js';
7
+ async function main() {
8
+ const context = await loadDesignContext();
9
+ const state = await loadDesignRunState(context.statePath);
10
+ const stageId = 'design-reference';
11
+ const html = buildReferencePage(context.config.config.metadata.design.captureUrls, state.artifacts.filter((artifact) => artifact.stage === 'extract'));
12
+ const tmpRoot = join(tmpdir(), `design-reference-${Date.now()}`);
13
+ await mkdir(tmpRoot, { recursive: true });
14
+ const tempFile = join(tmpRoot, 'motherduck.html');
15
+ await writeFile(tempFile, html, 'utf8');
16
+ const [staged] = await stageArtifacts({
17
+ taskId: context.taskId,
18
+ runId: context.runId,
19
+ artifacts: [
20
+ {
21
+ path: relative(process.cwd(), tempFile),
22
+ description: 'Design reference page'
23
+ }
24
+ ],
25
+ options: {
26
+ relativeDir: 'design/reference',
27
+ overwrite: true
28
+ }
29
+ });
30
+ const artifact = {
31
+ stage: 'reference',
32
+ status: 'succeeded',
33
+ relative_path: staged.path,
34
+ type: 'motherduck-html',
35
+ description: 'Aggregated reference page'
36
+ };
37
+ appendArtifacts(state, [artifact]);
38
+ upsertStage(state, {
39
+ id: stageId,
40
+ title: 'Build motherduck reference page',
41
+ status: 'succeeded',
42
+ artifacts: [
43
+ {
44
+ relative_path: staged.path,
45
+ stage: 'reference',
46
+ status: 'succeeded',
47
+ description: 'motherduck.html'
48
+ }
49
+ ]
50
+ });
51
+ await saveDesignRunState(context.statePath, state);
52
+ console.log(`[design-reference] staged reference page: ${staged.path}`);
53
+ }
54
+ function buildReferencePage(urls, extractArtifacts) {
55
+ const captures = extractArtifacts.map((artifact) => artifact.relative_path);
56
+ const listItems = urls
57
+ .map((url) => `<li><a href="${url}">${url}</a></li>`)
58
+ .join('\n');
59
+ const captureList = captures
60
+ .map((path) => `<li><code>${path}</code></li>`)
61
+ .join('\n');
62
+ return `<!doctype html>
63
+ <html lang="en">
64
+ <head>
65
+ <meta charset="utf-8" />
66
+ <title>Design Reference</title>
67
+ <style>
68
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
69
+ h1 { margin-bottom: 1rem; }
70
+ section { margin-bottom: 2rem; }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <h1>Design Reference Overview</h1>
75
+ <section>
76
+ <h2>Capture Targets</h2>
77
+ <ol>
78
+ ${listItems}
79
+ </ol>
80
+ </section>
81
+ <section>
82
+ <h2>Staged Artifacts</h2>
83
+ <ul>
84
+ ${captureList}
85
+ </ul>
86
+ </section>
87
+ </body>
88
+ </html>`;
89
+ }
90
+ main().catch((error) => {
91
+ console.error('[design-reference] failed to generate reference page');
92
+ console.error(error instanceof Error ? error.stack ?? error.message : error);
93
+ process.exitCode = 1;
94
+ });
@@ -0,0 +1,206 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ export async function loadDesignRunState(path) {
3
+ try {
4
+ const raw = await readFile(path, 'utf8');
5
+ const parsed = JSON.parse(raw);
6
+ return sanitizeState(parsed);
7
+ }
8
+ catch (error) {
9
+ const nodeError = error;
10
+ if (nodeError && nodeError.code === 'ENOENT') {
11
+ return createEmptyState();
12
+ }
13
+ throw error;
14
+ }
15
+ }
16
+ export async function saveDesignRunState(path, state) {
17
+ const payload = JSON.stringify(state, null, 2);
18
+ await writeFile(path, `${payload}\n`, 'utf8');
19
+ }
20
+ export function upsertStage(state, stage) {
21
+ const existingIndex = state.stages.findIndex((entry) => entry.id === stage.id);
22
+ const normalized = sanitizeStage(stage);
23
+ if (existingIndex >= 0) {
24
+ state.stages[existingIndex] = {
25
+ ...state.stages[existingIndex],
26
+ ...normalized,
27
+ notes: mergeNotes(state.stages[existingIndex].notes, normalized.notes),
28
+ metrics: { ...state.stages[existingIndex].metrics, ...normalized.metrics },
29
+ artifacts: mergeStageArtifacts(state.stages[existingIndex].artifacts, normalized.artifacts)
30
+ };
31
+ return state.stages[existingIndex];
32
+ }
33
+ state.stages.push(normalized);
34
+ return normalized;
35
+ }
36
+ export function appendArtifacts(state, artifacts) {
37
+ for (const artifact of artifacts) {
38
+ const existingIndex = state.artifacts.findIndex((entry) => entry.stage === artifact.stage && entry.relative_path === artifact.relative_path && entry.type === artifact.type);
39
+ if (existingIndex >= 0) {
40
+ state.artifacts[existingIndex] = { ...state.artifacts[existingIndex], ...artifact };
41
+ }
42
+ else {
43
+ state.artifacts.push(artifact);
44
+ }
45
+ }
46
+ }
47
+ export function appendToolkitArtifacts(state, artifacts) {
48
+ const toolkit = ensureToolkitState(state);
49
+ for (const artifact of artifacts) {
50
+ const index = toolkit.artifacts.findIndex((entry) => entry.id === artifact.id &&
51
+ entry.stage === artifact.stage &&
52
+ entry.relative_path === artifact.relative_path);
53
+ if (index >= 0) {
54
+ toolkit.artifacts[index] = {
55
+ ...toolkit.artifacts[index],
56
+ ...artifact
57
+ };
58
+ }
59
+ else {
60
+ toolkit.artifacts.push({ ...artifact });
61
+ }
62
+ }
63
+ }
64
+ export function appendApprovals(state, approvals) {
65
+ for (const approval of approvals) {
66
+ const exists = state.approvals.some((entry) => entry.id === approval.id);
67
+ if (!exists) {
68
+ state.approvals.push(approval);
69
+ }
70
+ }
71
+ }
72
+ export function mergeMetrics(state, metrics) {
73
+ state.metrics = {
74
+ ...state.metrics,
75
+ ...metrics
76
+ };
77
+ }
78
+ export function ensureToolkitState(state) {
79
+ if (!state.toolkit) {
80
+ state.toolkit = {
81
+ contexts: [],
82
+ artifacts: []
83
+ };
84
+ }
85
+ return state.toolkit;
86
+ }
87
+ export function upsertToolkitContext(state, contextRecord) {
88
+ const toolkit = ensureToolkitState(state);
89
+ const index = toolkit.contexts.findIndex((entry) => entry.id === contextRecord.id);
90
+ if (index >= 0) {
91
+ toolkit.contexts[index] = {
92
+ ...toolkit.contexts[index],
93
+ ...contextRecord
94
+ };
95
+ return toolkit.contexts[index];
96
+ }
97
+ toolkit.contexts.push({ ...contextRecord });
98
+ return toolkit.contexts[toolkit.contexts.length - 1];
99
+ }
100
+ function sanitizeState(state) {
101
+ if (!state || typeof state !== 'object') {
102
+ return createEmptyState();
103
+ }
104
+ return {
105
+ configSnapshot: state.configSnapshot ?? null,
106
+ retention: state.retention,
107
+ privacy: state.privacy,
108
+ approvals: Array.isArray(state.approvals) ? [...state.approvals] : [],
109
+ artifacts: Array.isArray(state.artifacts) ? [...state.artifacts] : [],
110
+ toolkit: sanitizeToolkitState(state.toolkit),
111
+ stages: Array.isArray(state.stages) ? state.stages.map(sanitizeStage) : [],
112
+ metrics: state.metrics && typeof state.metrics === 'object' ? { ...state.metrics } : {},
113
+ designPlan: cloneDesignValue(state.designPlan),
114
+ designGuardrail: cloneDesignValue(state.designGuardrail),
115
+ designHistory: cloneDesignValue(state.designHistory),
116
+ designStyleProfile: cloneDesignValue(state.designStyleProfile),
117
+ designMetrics: cloneDesignValue(state.designMetrics)
118
+ };
119
+ }
120
+ function createEmptyState() {
121
+ return {
122
+ approvals: [],
123
+ artifacts: [],
124
+ toolkit: {
125
+ contexts: [],
126
+ artifacts: []
127
+ },
128
+ stages: [],
129
+ metrics: {},
130
+ designPlan: null,
131
+ designGuardrail: null,
132
+ designHistory: null,
133
+ designStyleProfile: null,
134
+ designMetrics: null
135
+ };
136
+ }
137
+ function sanitizeStage(stage) {
138
+ return {
139
+ id: stage.id,
140
+ title: stage.title,
141
+ status: stage.status,
142
+ notes: stage.notes ? [...stage.notes] : [],
143
+ metrics: stage.metrics ? { ...stage.metrics } : undefined,
144
+ artifacts: mergeStageArtifacts([], stage.artifacts)
145
+ };
146
+ }
147
+ function mergeNotes(existing, next) {
148
+ const combined = [...(existing ?? []), ...(next ?? [])];
149
+ if (combined.length === 0) {
150
+ return undefined;
151
+ }
152
+ return Array.from(new Set(combined));
153
+ }
154
+ function mergeStageArtifacts(existing, incoming) {
155
+ if ((!existing || existing.length === 0) && (!incoming || incoming.length === 0)) {
156
+ return undefined;
157
+ }
158
+ const records = [...(existing ?? [])];
159
+ for (const artifact of incoming ?? []) {
160
+ const index = records.findIndex((entry) => entry.relative_path === artifact.relative_path);
161
+ if (index >= 0) {
162
+ records[index] = { ...records[index], ...artifact };
163
+ }
164
+ else {
165
+ records.push({ ...artifact });
166
+ }
167
+ }
168
+ return records;
169
+ }
170
+ function cloneDesignValue(value) {
171
+ if (value === undefined) {
172
+ return undefined;
173
+ }
174
+ if (value === null) {
175
+ return null;
176
+ }
177
+ return JSON.parse(JSON.stringify(value));
178
+ }
179
+ function sanitizeToolkitState(toolkit) {
180
+ if (!toolkit || typeof toolkit !== 'object') {
181
+ return {
182
+ contexts: [],
183
+ artifacts: []
184
+ };
185
+ }
186
+ const contexts = Array.isArray(toolkit.contexts)
187
+ ? toolkit.contexts.map((context) => ({ ...context }))
188
+ : [];
189
+ const artifacts = Array.isArray(toolkit.artifacts)
190
+ ? toolkit.artifacts.map((artifact) => ({ ...artifact }))
191
+ : [];
192
+ const retention = toolkit.retention
193
+ ? {
194
+ days: Number(toolkit.retention.days) || 0,
195
+ autoPurge: Boolean(toolkit.retention.autoPurge),
196
+ policy: toolkit.retention.policy
197
+ }
198
+ : undefined;
199
+ const summary = toolkit.summary && typeof toolkit.summary === 'object' ? { ...toolkit.summary } : undefined;
200
+ return {
201
+ contexts,
202
+ artifacts,
203
+ retention,
204
+ summary: summary ?? null
205
+ };
206
+ }