@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,94 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function loadToolkitPermit(repoRoot) {
4
+ const permitPath = join(repoRoot, 'compliance', 'permit.json');
5
+ try {
6
+ const raw = await readFile(permitPath, 'utf8');
7
+ return JSON.parse(raw);
8
+ }
9
+ catch (error) {
10
+ const nodeError = error;
11
+ if (nodeError?.code === 'ENOENT') {
12
+ console.warn('[design-toolkit] compliance/permit.json not found; proceeding without permit enforcement');
13
+ return { allowedSources: [] };
14
+ }
15
+ throw error;
16
+ }
17
+ }
18
+ export function ensureSourcePermitted(url, permit) {
19
+ const allowed = new Set((permit.allowedSources ?? [])
20
+ .map((entry) => {
21
+ try {
22
+ return new URL(entry.origin).origin;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ })
28
+ .filter((origin) => Boolean(origin)));
29
+ const origin = new URL(url).origin;
30
+ if (allowed.size === 0 || allowed.has(origin)) {
31
+ return true;
32
+ }
33
+ console.warn(`[design-toolkit] Extraction target ${origin} is not listed in compliance/permit.json; continuing without permit match`);
34
+ return false;
35
+ }
36
+ export function resolveToolkitSources(pipeline, metadata) {
37
+ const sourceList = pipeline.sources.length > 0 ? pipeline.sources : buildSourcesFromCaptureUrls(metadata.captureUrls);
38
+ const breakpoints = pipeline.breakpoints.length > 0 ? pipeline.breakpoints : metadata.breakpoints;
39
+ const maskSelectors = pipeline.maskSelectors.length > 0 ? pipeline.maskSelectors : metadata.maskSelectors;
40
+ const finalBreakpoints = breakpoints.length > 0 ? breakpoints : defaultBreakpoints();
41
+ return sourceList.map((source, index) => ({
42
+ ...source,
43
+ referenceUrl: source.referenceUrl ?? source.url,
44
+ slug: source.slug ?? slugifyToolkitValue(source.id ?? source.url, index),
45
+ breakpoints: finalBreakpoints,
46
+ maskSelectors
47
+ }));
48
+ }
49
+ export function computeToolkitRetention(pipeline, fallback) {
50
+ if (pipeline.retention) {
51
+ return {
52
+ days: pipeline.retention.days,
53
+ autoPurge: pipeline.retention.autoPurge,
54
+ policy: pipeline.retention.policy ?? fallback.policy
55
+ };
56
+ }
57
+ return fallback;
58
+ }
59
+ export function buildRetentionMetadata(retention, now) {
60
+ const expiryMs = now.getTime() + Math.max(1, retention.days) * 24 * 60 * 60 * 1000;
61
+ return {
62
+ ...retention,
63
+ expiry: new Date(expiryMs).toISOString()
64
+ };
65
+ }
66
+ export function slugifyToolkitValue(value, index) {
67
+ const normalized = value
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9-_]+/g, '-')
70
+ .replace(/^-+|-+$/g, '')
71
+ .slice(0, 48);
72
+ if (normalized.length > 0) {
73
+ return normalized;
74
+ }
75
+ return `source-${index + 1}`;
76
+ }
77
+ function defaultBreakpoints() {
78
+ return [
79
+ {
80
+ id: 'desktop',
81
+ width: 1280,
82
+ height: 720
83
+ }
84
+ ];
85
+ }
86
+ function buildSourcesFromCaptureUrls(urls) {
87
+ return urls.map((url, index) => ({
88
+ id: `source-${index + 1}`,
89
+ url,
90
+ referenceUrl: url,
91
+ slug: slugifyToolkitValue(url, index),
92
+ title: null
93
+ }));
94
+ }
@@ -0,0 +1,258 @@
1
+ import { copyFile, mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { loadDesignContext } from '../context.js';
5
+ import { appendApprovals, appendToolkitArtifacts, ensureToolkitState, loadDesignRunState, saveDesignRunState, upsertStage, upsertToolkitContext } from '../state.js';
6
+ import { stageArtifacts } from '../../../../orchestrator/src/persistence/ArtifactStager.js';
7
+ import { buildRetentionMetadata, computeToolkitRetention, ensureSourcePermitted, loadToolkitPermit, resolveToolkitSources, slugifyToolkitValue } from './common.js';
8
+ import { capturePageSnapshot } from './snapshot.js';
9
+ async function main() {
10
+ const context = await loadDesignContext();
11
+ const state = await loadDesignRunState(context.statePath);
12
+ const stageId = 'design-toolkit-extract';
13
+ const toolkitState = ensureToolkitState(state);
14
+ const metadata = context.config.config.metadata.design;
15
+ const pipelineConfig = context.config.config.pipelines.hiFiDesignToolkit;
16
+ const sources = resolveToolkitSources(pipelineConfig, metadata);
17
+ if (sources.length === 0) {
18
+ upsertStage(state, {
19
+ id: stageId,
20
+ title: 'Toolkit context acquisition',
21
+ status: 'skipped',
22
+ notes: ['No hi-fi design toolkit sources configured.']
23
+ });
24
+ await saveDesignRunState(context.statePath, state);
25
+ console.log('[design-toolkit-extract] skipped — no sources configured');
26
+ return;
27
+ }
28
+ const permit = await loadToolkitPermit(context.repoRoot).catch((error) => {
29
+ console.warn('[design-toolkit-extract] permit load failed, proceeding without enforcement', error);
30
+ return { allowedSources: [] };
31
+ });
32
+ const fallbackRetention = {
33
+ days: state.retention?.days ?? metadata.retention.days,
34
+ autoPurge: state.retention?.autoPurge ?? metadata.retention.autoPurge,
35
+ policy: state.retention?.policy ?? 'design.config.retention'
36
+ };
37
+ toolkitState.retention = computeToolkitRetention(pipelineConfig, fallbackRetention);
38
+ const now = new Date();
39
+ const tmpRoot = join(tmpdir(), `design-toolkit-context-${Date.now()}`);
40
+ await mkdir(tmpRoot, { recursive: true });
41
+ let successCount = 0;
42
+ const failures = [];
43
+ const stagedArtifacts = [];
44
+ const approvals = [];
45
+ for (const source of sources) {
46
+ try {
47
+ ensureSourcePermitted(source.url, permit);
48
+ const contextResult = await stageContextArtifact({
49
+ context,
50
+ source,
51
+ tmpRoot,
52
+ retentionDays: toolkitState.retention?.days ?? fallbackRetention.days,
53
+ retentionPolicy: toolkitState.retention?.policy ?? fallbackRetention.policy,
54
+ autoPurge: toolkitState.retention?.autoPurge ?? fallbackRetention.autoPurge,
55
+ timestamp: now,
56
+ liveAssets: pipelineConfig.liveAssets,
57
+ interactionsEnabled: pipelineConfig.interactions?.enabled ?? false
58
+ });
59
+ successCount += 1;
60
+ stagedArtifacts.push(contextResult.artifact);
61
+ approvals.push({
62
+ id: `playwright-${source.slug}`,
63
+ actor: metadata.privacy.approver ?? 'design-reviewer',
64
+ reason: `Playwright extraction approved for ${source.url}`,
65
+ timestamp: new Date().toISOString()
66
+ });
67
+ upsertToolkitContext(state, {
68
+ id: source.id ?? slugifyToolkitValue(source.url, successCount - 1),
69
+ slug: source.slug,
70
+ title: source.title ?? null,
71
+ url: source.url,
72
+ referenceUrl: source.referenceUrl ?? source.url,
73
+ relativeDir: contextResult.artifact.relative_path.split('/').slice(0, -1).join('/'),
74
+ breakpoints: source.breakpoints.map((bp) => bp.id),
75
+ snapshotHtmlPath: contextResult.paths.inlineHtmlPath,
76
+ snapshotRawHtmlPath: contextResult.paths.rawHtmlPath,
77
+ snapshotCssPath: contextResult.paths.stylesPath,
78
+ palettePath: contextResult.paths.palettePath,
79
+ sectionsPath: contextResult.paths.sectionsPath,
80
+ palettePreview: contextResult.palettePreview,
81
+ fontFamilies: contextResult.fontFamilies,
82
+ runtimeCanvasColors: contextResult.runtimeCanvasColors,
83
+ resolvedFonts: contextResult.resolvedFonts,
84
+ interactionScriptPath: pipelineConfig.interactions?.scriptPath ?? null,
85
+ interactionWaitMs: pipelineConfig.interactions?.waitMs ?? null
86
+ });
87
+ }
88
+ catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ failures.push(`${source.url}: ${message}`);
91
+ console.error(`[design-toolkit-extract] failed to process ${source.url}: ${message}`);
92
+ }
93
+ }
94
+ if (stagedArtifacts.length > 0) {
95
+ appendToolkitArtifacts(state, stagedArtifacts);
96
+ }
97
+ if (approvals.length > 0) {
98
+ appendApprovals(state, approvals);
99
+ }
100
+ const stageStatus = failures.length === 0 && successCount > 0 ? 'succeeded' : 'failed';
101
+ upsertStage(state, {
102
+ id: stageId,
103
+ title: 'Toolkit context acquisition',
104
+ status: stageStatus,
105
+ notes: failures.length > 0 ? failures : undefined,
106
+ metrics: {
107
+ source_count: sources.length,
108
+ captured_count: successCount
109
+ },
110
+ artifacts: stagedArtifacts.map((artifact) => ({
111
+ relative_path: artifact.relative_path,
112
+ stage: 'extract',
113
+ status: artifact.status,
114
+ description: artifact.description ?? undefined
115
+ }))
116
+ });
117
+ await saveDesignRunState(context.statePath, state);
118
+ if (stageStatus === 'failed') {
119
+ throw new Error('One or more toolkit sources failed staging.');
120
+ }
121
+ console.log(`[design-toolkit-extract] staged ${successCount} / ${sources.length} sources`);
122
+ }
123
+ async function stageContextArtifact(options) {
124
+ const { context, source, tmpRoot, retentionDays, retentionPolicy, autoPurge, timestamp, liveAssets, interactionsEnabled } = options;
125
+ const primaryBreakpoint = source.breakpoints[0];
126
+ const viewport = primaryBreakpoint
127
+ ? {
128
+ width: primaryBreakpoint.width,
129
+ height: primaryBreakpoint.height,
130
+ deviceScaleFactor: primaryBreakpoint.deviceScaleFactor
131
+ }
132
+ : undefined;
133
+ const snapshot = await capturePageSnapshot(source.url, {
134
+ keepScripts: liveAssets?.keepScripts ?? false,
135
+ maxStylesheets: liveAssets?.maxStylesheets ?? undefined,
136
+ mirrorAssets: liveAssets?.mirrorAssets ?? false,
137
+ allowRemoteAssets: liveAssets?.allowRemoteAssets ?? false,
138
+ viewport,
139
+ runInteractions: Boolean(interactionsEnabled)
140
+ });
141
+ const slugDir = join(tmpRoot, source.slug);
142
+ await mkdir(slugDir, { recursive: true });
143
+ const contextPayload = {
144
+ id: source.id,
145
+ url: source.url,
146
+ referenceUrl: source.referenceUrl,
147
+ breakpoints: source.breakpoints,
148
+ maskSelectors: source.maskSelectors,
149
+ capturedAt: timestamp.toISOString(),
150
+ colorPalettePreview: snapshot.colorPalette.slice(0, 12),
151
+ fontFamilies: snapshot.fontFamilies,
152
+ sectionCount: snapshot.sections.length,
153
+ runtimeCanvasColors: snapshot.runtimeCanvasColors,
154
+ resolvedFonts: snapshot.resolvedFonts
155
+ };
156
+ const contextPath = join(slugDir, 'context.json');
157
+ const inlineHtmlPath = join(slugDir, 'inline.html');
158
+ const rawHtmlPath = join(slugDir, 'original.html');
159
+ const stylesPath = join(slugDir, 'styles.css');
160
+ const palettePath = join(slugDir, 'palette.json');
161
+ const sectionsPath = join(slugDir, 'sections.json');
162
+ await Promise.all([
163
+ writeFile(contextPath, `${JSON.stringify(contextPayload, null, 2)}\n`, 'utf8'),
164
+ writeFile(inlineHtmlPath, snapshot.inlineHtml, 'utf8'),
165
+ writeFile(rawHtmlPath, snapshot.originalHtml, 'utf8'),
166
+ writeFile(stylesPath, snapshot.aggregatedCss, 'utf8'),
167
+ writeFile(palettePath, `${JSON.stringify(snapshot.colorPalette, null, 2)}\n`, 'utf8'),
168
+ writeFile(sectionsPath, `${JSON.stringify(snapshot.sections, null, 2)}\n`, 'utf8')
169
+ ]);
170
+ if (snapshot.assets.length > 0) {
171
+ for (const asset of snapshot.assets) {
172
+ const assetPath = join(slugDir, asset.relativePath);
173
+ await mkdir(dirname(assetPath), { recursive: true });
174
+ await writeFile(assetPath, asset.buffer);
175
+ }
176
+ }
177
+ const stagedFiles = await stageArtifacts({
178
+ taskId: context.taskId,
179
+ runId: context.runId,
180
+ artifacts: [
181
+ {
182
+ path: relative(process.cwd(), contextPath),
183
+ description: `Toolkit context for ${source.url}`
184
+ },
185
+ {
186
+ path: relative(process.cwd(), inlineHtmlPath),
187
+ description: `Inlined Cognition clone for ${source.url}`
188
+ },
189
+ {
190
+ path: relative(process.cwd(), rawHtmlPath),
191
+ description: `Original HTML for ${source.url}`
192
+ },
193
+ {
194
+ path: relative(process.cwd(), stylesPath),
195
+ description: `Aggregated CSS for ${source.url}`
196
+ },
197
+ {
198
+ path: relative(process.cwd(), palettePath),
199
+ description: `Color palette for ${source.url}`
200
+ },
201
+ {
202
+ path: relative(process.cwd(), sectionsPath),
203
+ description: `Section summaries for ${source.url}`
204
+ }
205
+ ],
206
+ options: {
207
+ relativeDir: join('design-toolkit', 'context', source.slug),
208
+ overwrite: true
209
+ }
210
+ });
211
+ const [contextRecord, inlineRecord, rawRecord, stylesRecord, paletteRecord, sectionsRecord] = stagedFiles;
212
+ const contextDir = join(process.cwd(), dirname(contextRecord.path));
213
+ if (snapshot.assets.length > 0) {
214
+ for (const asset of snapshot.assets) {
215
+ const tempPath = join(slugDir, asset.relativePath);
216
+ const destinationPath = join(contextDir, asset.relativePath);
217
+ await mkdir(dirname(destinationPath), { recursive: true });
218
+ await copyFile(tempPath, destinationPath);
219
+ }
220
+ }
221
+ const retention = buildRetentionMetadata({
222
+ days: retentionDays,
223
+ autoPurge,
224
+ policy: retentionPolicy
225
+ }, timestamp);
226
+ return {
227
+ artifact: {
228
+ id: source.id,
229
+ stage: 'extract',
230
+ status: 'succeeded',
231
+ relative_path: contextRecord.path,
232
+ description: `Context snapshot for ${source.slug}`,
233
+ retention,
234
+ metrics: {
235
+ breakpoint_count: source.breakpoints.length,
236
+ color_count: snapshot.colorPalette.length,
237
+ section_count: snapshot.sections.length
238
+ }
239
+ },
240
+ paths: {
241
+ contextPath: contextRecord.path,
242
+ inlineHtmlPath: inlineRecord.path,
243
+ rawHtmlPath: rawRecord.path,
244
+ stylesPath: stylesRecord.path,
245
+ palettePath: paletteRecord.path,
246
+ sectionsPath: sectionsRecord.path
247
+ },
248
+ palettePreview: snapshot.colorPalette.slice(0, 12),
249
+ fontFamilies: snapshot.fontFamilies,
250
+ runtimeCanvasColors: snapshot.runtimeCanvasColors,
251
+ resolvedFonts: snapshot.resolvedFonts
252
+ };
253
+ }
254
+ main().catch((error) => {
255
+ console.error('[design-toolkit-extract] failed to stage toolkit context');
256
+ console.error(error instanceof Error ? error.stack ?? error.message : error);
257
+ process.exitCode = 1;
258
+ });
@@ -0,0 +1,202 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { spawn } from 'node:child_process';
5
+ import { loadDesignContext } from '../context.js';
6
+ import { appendToolkitArtifacts, ensureToolkitState, loadDesignRunState, saveDesignRunState, upsertStage } from '../state.js';
7
+ import { stageArtifacts } from '../../../../orchestrator/src/persistence/ArtifactStager.js';
8
+ import { buildRetentionMetadata } from './common.js';
9
+ import { resolveToolkitPublishActions } from './publishActions.js';
10
+ const DESIGN_SYSTEM_DIR = 'packages/design-system';
11
+ async function main() {
12
+ const context = await loadDesignContext();
13
+ const state = await loadDesignRunState(context.statePath);
14
+ const stageId = 'design-toolkit-publish';
15
+ const toolkitState = ensureToolkitState(state);
16
+ const contexts = toolkitState.contexts.filter((entry) => entry.tokensPath);
17
+ const pipelineConfig = context.config.config.pipelines.hiFiDesignToolkit;
18
+ const publishActions = resolveToolkitPublishActions(pipelineConfig);
19
+ const hasAnyAction = publishActions.updateTokens || publishActions.updateComponents || publishActions.runVisualRegression;
20
+ if ((publishActions.updateTokens || publishActions.updateComponents) && contexts.length === 0) {
21
+ upsertStage(state, {
22
+ id: stageId,
23
+ title: 'Toolkit publish + design-system integration',
24
+ status: 'skipped',
25
+ notes: ['No token artifacts available. Run earlier stages first.']
26
+ });
27
+ await saveDesignRunState(context.statePath, state);
28
+ console.log('[design-toolkit-publish] skipped — no token artifacts');
29
+ return;
30
+ }
31
+ if (!hasAnyAction) {
32
+ upsertStage(state, {
33
+ id: stageId,
34
+ title: 'Toolkit publish + design-system integration',
35
+ status: 'skipped',
36
+ notes: ['publish.update_* config disabled all publish actions.']
37
+ });
38
+ await saveDesignRunState(context.statePath, state);
39
+ console.log('[design-toolkit-publish] skipped — publish actions disabled via config');
40
+ return;
41
+ }
42
+ const retention = toolkitState.retention ?? {
43
+ days: state.retention?.days ?? 30,
44
+ autoPurge: state.retention?.autoPurge ?? false,
45
+ policy: state.retention?.policy ?? 'design.config.retention'
46
+ };
47
+ const repoRoot = context.repoRoot;
48
+ const tokensTarget = join(repoRoot, DESIGN_SYSTEM_DIR, 'tokens', 'src', 'hi-fi');
49
+ const componentTarget = join(repoRoot, DESIGN_SYSTEM_DIR, 'src', 'components', 'hi-fi');
50
+ const dirPromises = [];
51
+ if (publishActions.updateTokens) {
52
+ dirPromises.push(mkdir(tokensTarget, { recursive: true }).then(() => undefined));
53
+ }
54
+ if (publishActions.updateComponents) {
55
+ dirPromises.push(mkdir(componentTarget, { recursive: true }).then(() => undefined));
56
+ }
57
+ await Promise.all(dirPromises);
58
+ let tokensWritten = 0;
59
+ let componentsWritten = 0;
60
+ if (publishActions.updateTokens || publishActions.updateComponents) {
61
+ for (const entry of contexts) {
62
+ const sourcePath = join(repoRoot, entry.tokensPath);
63
+ if (publishActions.updateTokens) {
64
+ const destPath = join(tokensTarget, `${entry.slug}.json`);
65
+ const tokensRaw = await readFile(sourcePath, 'utf8');
66
+ const tokens = JSON.parse(tokensRaw);
67
+ tokens.metadata = {
68
+ ...(tokens.metadata ?? {}),
69
+ manifest: context.manifestPath
70
+ };
71
+ await writeFile(destPath, `${JSON.stringify(tokens, null, 2)}\n`, 'utf8');
72
+ tokensWritten += 1;
73
+ }
74
+ if (publishActions.updateComponents) {
75
+ const componentDir = join(componentTarget, entry.slug);
76
+ await mkdir(componentDir, { recursive: true });
77
+ const componentName = toPascalCase(entry.slug);
78
+ const componentFile = join(componentDir, 'index.ts');
79
+ const componentSource = `export const ${componentName} = {\n id: '${entry.slug}',\n tokensFile: '../../tokens/src/hi-fi/${entry.slug}.json',\n referencePath: '${entry.referencePath ?? ''}'\n};\n`;
80
+ await writeFile(componentFile, componentSource, 'utf8');
81
+ componentsWritten += 1;
82
+ }
83
+ }
84
+ }
85
+ const commandResults = await runDesignSystemCommands(repoRoot, publishActions);
86
+ const tmpRoot = join(tmpdir(), `design-toolkit-publish-${Date.now()}`);
87
+ await mkdir(tmpRoot, { recursive: true });
88
+ const logPath = join(tmpRoot, 'publish-summary.json');
89
+ await writeFile(logPath, JSON.stringify(commandResults, null, 2), 'utf8');
90
+ const [stagedLog] = await stageArtifacts({
91
+ taskId: context.taskId,
92
+ runId: context.runId,
93
+ artifacts: [
94
+ {
95
+ path: relative(process.cwd(), logPath),
96
+ description: 'Design-system publish logs'
97
+ }
98
+ ],
99
+ options: {
100
+ relativeDir: 'design-toolkit/publish/logs',
101
+ overwrite: true
102
+ }
103
+ });
104
+ const retentionMetadata = buildRetentionMetadata(retention, new Date());
105
+ const artifact = {
106
+ id: 'design-system-publish',
107
+ stage: 'publish',
108
+ status: commandResults.failed === 0 ? 'succeeded' : 'failed',
109
+ relative_path: stagedLog.path,
110
+ description: 'Design-system integration summary',
111
+ retention: retentionMetadata,
112
+ metrics: {
113
+ contexts_updated: Math.max(tokensWritten, componentsWritten),
114
+ tokens_written: tokensWritten,
115
+ components_written: componentsWritten,
116
+ commands_passed: commandResults.passed,
117
+ commands_failed: commandResults.failed,
118
+ commands_executed: commandResults.executed.length
119
+ }
120
+ };
121
+ appendToolkitArtifacts(state, [artifact]);
122
+ const stageNotes = [];
123
+ if (!publishActions.updateTokens) {
124
+ stageNotes.push('Token publishing disabled via config.');
125
+ }
126
+ if (!publishActions.updateComponents) {
127
+ stageNotes.push('Component publishing disabled via config.');
128
+ }
129
+ if (!publishActions.runVisualRegression) {
130
+ stageNotes.push('Visual regression command disabled via config.');
131
+ }
132
+ upsertStage(state, {
133
+ id: stageId,
134
+ title: 'Toolkit publish + design-system integration',
135
+ status: artifact.status === 'succeeded' ? 'succeeded' : 'failed',
136
+ notes: commandResults.failed > 0
137
+ ? [...stageNotes, ...commandResults.errors]
138
+ : stageNotes.length > 0
139
+ ? stageNotes
140
+ : undefined,
141
+ metrics: artifact.metrics ?? undefined,
142
+ artifacts: [
143
+ {
144
+ relative_path: stagedLog.path,
145
+ stage: 'publish',
146
+ status: artifact.status,
147
+ description: 'publish-summary.json'
148
+ }
149
+ ]
150
+ });
151
+ await saveDesignRunState(context.statePath, state);
152
+ if (artifact.status === 'failed') {
153
+ throw new Error('One or more design-system commands failed.');
154
+ }
155
+ console.log('[design-toolkit-publish] updated design-system tokens/components');
156
+ }
157
+ async function runDesignSystemCommands(repoRoot, actions) {
158
+ const commands = [];
159
+ if (actions.updateTokens) {
160
+ commands.push({ args: ['npm', '--prefix', DESIGN_SYSTEM_DIR, 'run', 'build:tokens'], label: 'build:tokens' });
161
+ }
162
+ if (actions.updateComponents) {
163
+ commands.push({ args: ['npm', '--prefix', DESIGN_SYSTEM_DIR, 'run', 'lint'], label: 'lint' });
164
+ }
165
+ if (actions.runVisualRegression) {
166
+ commands.push({ args: ['npm', '--prefix', DESIGN_SYSTEM_DIR, 'run', 'test:visual'], label: 'test:visual' });
167
+ }
168
+ const errors = [];
169
+ const executed = [];
170
+ let passed = 0;
171
+ let failed = 0;
172
+ for (const command of commands) {
173
+ const exitCode = await spawnCommand(command.args, repoRoot);
174
+ executed.push(command.label);
175
+ if (exitCode === 0) {
176
+ passed += 1;
177
+ }
178
+ else {
179
+ failed += 1;
180
+ errors.push(`${command.label} failed with exit code ${exitCode}`);
181
+ }
182
+ }
183
+ return { passed, failed, errors, executed };
184
+ }
185
+ function spawnCommand(args, cwd) {
186
+ return new Promise((resolve) => {
187
+ const child = spawn(args[0], args.slice(1), { stdio: 'inherit', cwd });
188
+ child.on('close', (code) => resolve(code ?? 1));
189
+ child.on('error', () => resolve(1));
190
+ });
191
+ }
192
+ function toPascalCase(value) {
193
+ return value
194
+ .split(/[-_\s]+/)
195
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
196
+ .join('');
197
+ }
198
+ main().catch((error) => {
199
+ console.error('[design-toolkit-publish] failed to publish toolkit outputs');
200
+ console.error(error instanceof Error ? error.stack ?? error.message : error);
201
+ process.exitCode = 1;
202
+ });
@@ -0,0 +1,12 @@
1
+ export function resolveToolkitPublishActions(pipeline) {
2
+ const publish = pipeline.publish ?? {
3
+ updateTokens: true,
4
+ updateComponents: true,
5
+ runVisualRegression: true
6
+ };
7
+ return {
8
+ updateTokens: publish.updateTokens !== false,
9
+ updateComponents: publish.updateComponents !== false,
10
+ runVisualRegression: publish.runVisualRegression !== false
11
+ };
12
+ }