@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,495 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { load as parseYaml } from 'js-yaml';
4
+ const DEFAULT_CONFIG_PATH = 'design.config.yaml';
5
+ const DEFAULT_RETENTION_DAYS = 30;
6
+ const DEFAULT_CONFIG = {
7
+ metadata: {
8
+ design: {
9
+ enabled: false,
10
+ captureUrls: [],
11
+ breakpoints: [],
12
+ maskSelectors: [],
13
+ retention: {
14
+ days: DEFAULT_RETENTION_DAYS,
15
+ autoPurge: false
16
+ },
17
+ privacy: {
18
+ allowThirdParty: false,
19
+ requireApproval: true,
20
+ approver: null
21
+ }
22
+ }
23
+ },
24
+ advanced: {
25
+ framerMotion: {
26
+ enabled: false,
27
+ quotaSeconds: 0,
28
+ approver: null
29
+ },
30
+ ffmpeg: {
31
+ enabled: false,
32
+ quotaSeconds: 0,
33
+ approver: null,
34
+ maxDurationSeconds: null
35
+ }
36
+ },
37
+ pipelines: {
38
+ designReference: {
39
+ continueOnFailure: false,
40
+ visualRegression: {
41
+ enabled: true,
42
+ baselineDir: null
43
+ }
44
+ },
45
+ hiFiDesignToolkit: {
46
+ enabled: false,
47
+ sources: [],
48
+ breakpoints: [],
49
+ maskSelectors: [],
50
+ retention: null,
51
+ selfCorrection: {
52
+ enabled: false,
53
+ maxIterations: 1,
54
+ provider: null,
55
+ approvalId: null,
56
+ threshold: null
57
+ },
58
+ publish: {
59
+ updateTokens: true,
60
+ updateComponents: true,
61
+ runVisualRegression: true
62
+ },
63
+ liveAssets: {
64
+ keepScripts: false,
65
+ allowRemoteAssets: false,
66
+ maxStylesheets: null,
67
+ mirrorAssets: false
68
+ },
69
+ interactions: {
70
+ enabled: false,
71
+ scriptPath: null,
72
+ waitMs: null
73
+ }
74
+ }
75
+ }
76
+ };
77
+ const DESIGN_REFERENCE_PIPELINE_ID = 'design-reference';
78
+ const HI_FI_TOOLKIT_PIPELINE_ID = 'hi-fi-design-toolkit';
79
+ export function designPipelineId(result, env = process.env) {
80
+ return selectDesignPipeline(result ?? null, env).id;
81
+ }
82
+ export async function loadDesignConfig(options = {}) {
83
+ const rootDir = options.rootDir ?? process.cwd();
84
+ const path = options.filePath ?? join(rootDir, DEFAULT_CONFIG_PATH);
85
+ try {
86
+ const raw = await readFile(path, 'utf-8');
87
+ const parsed = parseYaml(raw);
88
+ const warnings = [];
89
+ const config = normalizeDesignConfig(parsed, warnings);
90
+ return { config, path, exists: true, warnings };
91
+ }
92
+ catch (error) {
93
+ const nodeError = error;
94
+ if (nodeError && nodeError.code === 'ENOENT') {
95
+ return { config: structuredClone(DEFAULT_CONFIG), path, exists: false, warnings: [] };
96
+ }
97
+ throw new Error(`Failed to load design config at ${path}: ${error.message}`);
98
+ }
99
+ }
100
+ export function shouldActivateDesignPipeline(result, env = process.env) {
101
+ return selectDesignPipeline(result, env).shouldRun;
102
+ }
103
+ function normalizeDesignConfig(raw, warnings) {
104
+ const base = structuredClone(DEFAULT_CONFIG);
105
+ if (!raw || typeof raw !== 'object') {
106
+ return base;
107
+ }
108
+ const metadata = raw.metadata;
109
+ if (metadata && typeof metadata === 'object') {
110
+ const design = metadata.design;
111
+ if (design && typeof design === 'object') {
112
+ base.metadata.design.enabled = coerceBoolean(design.enabled, base.metadata.design.enabled);
113
+ base.metadata.design.captureUrls = normalizeStringArray(design.capture_urls, warnings, 'metadata.design.capture_urls');
114
+ base.metadata.design.breakpoints = normalizeBreakpoints(design.breakpoints, warnings);
115
+ base.metadata.design.maskSelectors = normalizeStringArray(design.mask_selectors, warnings, 'metadata.design.mask_selectors');
116
+ const retentionRaw = design.retention;
117
+ if (retentionRaw && typeof retentionRaw === 'object') {
118
+ const retentionRecord = retentionRaw;
119
+ const retentionDays = coerceNumber(retentionRecord.days, base.metadata.design.retention.days, {
120
+ min: 1,
121
+ field: 'metadata.design.retention.days',
122
+ warnings
123
+ }) ?? base.metadata.design.retention.days;
124
+ base.metadata.design.retention.days = retentionDays;
125
+ base.metadata.design.retention.autoPurge = coerceBoolean(retentionRecord.auto_purge, base.metadata.design.retention.autoPurge);
126
+ }
127
+ const privacyRaw = design.privacy;
128
+ if (privacyRaw && typeof privacyRaw === 'object') {
129
+ const privacyRecord = privacyRaw;
130
+ base.metadata.design.privacy.allowThirdParty = coerceBoolean(privacyRecord.allow_third_party, base.metadata.design.privacy.allowThirdParty);
131
+ base.metadata.design.privacy.requireApproval = coerceBoolean(privacyRecord.require_approval, base.metadata.design.privacy.requireApproval);
132
+ const approver = coerceString(privacyRecord.approver);
133
+ base.metadata.design.privacy.approver = approver ?? base.metadata.design.privacy.approver ?? null;
134
+ }
135
+ }
136
+ }
137
+ const advanced = raw.advanced;
138
+ if (advanced && typeof advanced === 'object') {
139
+ const advancedRecord = advanced;
140
+ base.advanced.framerMotion = normalizeAdvancedConfig(advancedRecord.framer_motion, base.advanced.framerMotion, 'advanced.framer_motion', warnings);
141
+ const ffmpeg = normalizeAdvancedConfig(advancedRecord.ffmpeg, base.advanced.ffmpeg, 'advanced.ffmpeg', warnings);
142
+ const ffmpegRecord = advancedRecord.ffmpeg && typeof advancedRecord.ffmpeg === 'object'
143
+ ? advancedRecord.ffmpeg
144
+ : null;
145
+ const ffmpegExtended = {
146
+ ...ffmpeg,
147
+ maxDurationSeconds: base.advanced.ffmpeg.maxDurationSeconds ?? null
148
+ };
149
+ if (ffmpegRecord) {
150
+ const duration = coerceNumber(ffmpegRecord.max_duration_seconds, null, {
151
+ min: 1,
152
+ allowNull: true,
153
+ field: 'advanced.ffmpeg.max_duration_seconds',
154
+ warnings
155
+ });
156
+ ffmpegExtended.maxDurationSeconds = duration;
157
+ }
158
+ base.advanced.ffmpeg = ffmpegExtended;
159
+ }
160
+ const pipelines = raw.pipelines;
161
+ if (pipelines && typeof pipelines === 'object') {
162
+ const designReference = pipelines['design-reference'];
163
+ if (designReference && typeof designReference === 'object') {
164
+ const record = designReference;
165
+ const continueOnFailure = record.continue_on_failure;
166
+ if (continueOnFailure !== undefined) {
167
+ base.pipelines.designReference.continueOnFailure = coerceBoolean(continueOnFailure, Boolean(base.pipelines.designReference.continueOnFailure));
168
+ }
169
+ const visualRegression = record.visual_regression;
170
+ if (visualRegression && typeof visualRegression === 'object') {
171
+ const vrRecord = visualRegression;
172
+ base.pipelines.designReference.visualRegression = {
173
+ enabled: coerceBoolean(vrRecord.enabled, base.pipelines.designReference.visualRegression?.enabled ?? true),
174
+ baselineDir: coerceString(vrRecord.baseline_dir) ?? null
175
+ };
176
+ }
177
+ }
178
+ const hiFi = pipelines['hi_fi_design_toolkit'];
179
+ if (hiFi && typeof hiFi === 'object') {
180
+ base.pipelines.hiFiDesignToolkit = normalizeToolkitPipelineConfig(hiFi, base, warnings);
181
+ }
182
+ }
183
+ return base;
184
+ }
185
+ function selectDesignPipeline(result, env) {
186
+ const config = result?.config ?? DEFAULT_CONFIG;
187
+ const toolkitEnv = typeof env.DESIGN_TOOLKIT === 'string' ? isTruthyFlag(env.DESIGN_TOOLKIT) : null;
188
+ if (toolkitEnv !== null) {
189
+ return { id: HI_FI_TOOLKIT_PIPELINE_ID, shouldRun: toolkitEnv };
190
+ }
191
+ const designPipelineEnv = typeof env.DESIGN_PIPELINE === 'string' ? isTruthyFlag(env.DESIGN_PIPELINE) : null;
192
+ if (designPipelineEnv !== null) {
193
+ return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: designPipelineEnv };
194
+ }
195
+ const referenceEnv = typeof env.DESIGN_REFERENCE_PIPELINE === 'string' ? isTruthyFlag(env.DESIGN_REFERENCE_PIPELINE) : null;
196
+ if (referenceEnv !== null) {
197
+ return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: referenceEnv };
198
+ }
199
+ const toolkitConfigEnabled = config.pipelines.hiFiDesignToolkit.enabled && config.pipelines.hiFiDesignToolkit.sources.length > 0;
200
+ if (toolkitConfigEnabled) {
201
+ return { id: HI_FI_TOOLKIT_PIPELINE_ID, shouldRun: true };
202
+ }
203
+ if (config.metadata.design.enabled) {
204
+ return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: true };
205
+ }
206
+ return { id: DESIGN_REFERENCE_PIPELINE_ID, shouldRun: false };
207
+ }
208
+ function normalizeBreakpoints(raw, warnings) {
209
+ if (!Array.isArray(raw)) {
210
+ return [];
211
+ }
212
+ const result = [];
213
+ raw.forEach((candidate, index) => {
214
+ if (!candidate || typeof candidate !== 'object') {
215
+ warnings.push(`breakpoints[${index}]: expected object`);
216
+ return;
217
+ }
218
+ const record = candidate;
219
+ const id = coerceString(record.id) ?? `bp-${index + 1}`;
220
+ const width = coerceNumber(record.width, null, {
221
+ min: 1,
222
+ field: `breakpoints[${index}].width`,
223
+ warnings
224
+ });
225
+ const height = coerceNumber(record.height, null, {
226
+ min: 1,
227
+ field: `breakpoints[${index}].height`,
228
+ warnings
229
+ });
230
+ if (width === null || height === null) {
231
+ return;
232
+ }
233
+ const deviceScaleFactor = coerceNumber(record.deviceScaleFactor ?? record.device_scale_factor, null, {
234
+ min: 0.1,
235
+ allowNull: true,
236
+ field: `breakpoints[${index}].deviceScaleFactor`,
237
+ warnings
238
+ });
239
+ const breakpoint = {
240
+ id,
241
+ width,
242
+ height
243
+ };
244
+ if (deviceScaleFactor !== null) {
245
+ breakpoint.deviceScaleFactor = deviceScaleFactor;
246
+ }
247
+ result.push(breakpoint);
248
+ });
249
+ return result;
250
+ }
251
+ function normalizeToolkitPipelineConfig(raw, baseConfig, warnings) {
252
+ const normalized = structuredClone(DEFAULT_CONFIG.pipelines.hiFiDesignToolkit);
253
+ normalized.enabled = coerceBoolean(raw.enabled, normalized.enabled);
254
+ normalized.maskSelectors = normalizeStringArray(raw.mask_selectors ?? raw.maskSelectors, warnings, 'pipelines.hi_fi_design_toolkit.mask_selectors');
255
+ normalized.breakpoints = normalizeBreakpoints(raw.breakpoints, warnings);
256
+ normalized.sources = normalizeToolkitSources(raw.sources, warnings);
257
+ const retentionRaw = raw.retention;
258
+ if (retentionRaw && typeof retentionRaw === 'object') {
259
+ const record = retentionRaw;
260
+ const defaultRetention = baseConfig.metadata.design.retention;
261
+ const days = coerceNumber(record.days, defaultRetention.days, {
262
+ min: 1,
263
+ field: 'pipelines.hi_fi_design_toolkit.retention.days',
264
+ warnings
265
+ }) ?? defaultRetention.days;
266
+ const autoPurge = coerceBoolean(record.auto_purge ?? record.autoPurge, defaultRetention.autoPurge);
267
+ normalized.retention = {
268
+ days,
269
+ autoPurge,
270
+ policy: coerceString(record.policy) ?? 'design.config.retention'
271
+ };
272
+ }
273
+ const selfCorrectionRaw = raw.self_correction ?? raw.selfCorrection;
274
+ if (selfCorrectionRaw && typeof selfCorrectionRaw === 'object') {
275
+ const record = selfCorrectionRaw;
276
+ normalized.selfCorrection = {
277
+ enabled: coerceBoolean(record.enabled, normalized.selfCorrection.enabled),
278
+ maxIterations: coerceNumber(record.max_iterations ?? record.maxIterations, normalized.selfCorrection.maxIterations, {
279
+ min: 1,
280
+ field: 'pipelines.hi_fi_design_toolkit.self_correction.max_iterations',
281
+ warnings
282
+ }) ?? normalized.selfCorrection.maxIterations,
283
+ provider: coerceString(record.provider) ?? normalized.selfCorrection.provider ?? null,
284
+ approvalId: coerceString(record.approval_id ?? record.approvalId) ?? normalized.selfCorrection.approvalId ?? null,
285
+ threshold: coerceNumber(record.threshold, normalized.selfCorrection.threshold ?? null, {
286
+ min: 0,
287
+ allowNull: true,
288
+ field: 'pipelines.hi_fi_design_toolkit.self_correction.threshold',
289
+ warnings
290
+ }) ?? normalized.selfCorrection.threshold ?? null
291
+ };
292
+ }
293
+ const publishRaw = raw.publish;
294
+ if (publishRaw && typeof publishRaw === 'object') {
295
+ const record = publishRaw;
296
+ normalized.publish = {
297
+ updateTokens: coerceBoolean(record.update_tokens ?? record.updateTokens ?? record.sync_tokens, normalized.publish.updateTokens),
298
+ updateComponents: coerceBoolean(record.update_components ?? record.updateComponents ?? record.componentize, normalized.publish.updateComponents),
299
+ runVisualRegression: coerceBoolean(record.run_visual_regression ?? record.visual_regression, normalized.publish.runVisualRegression)
300
+ };
301
+ }
302
+ const liveAssetsRaw = raw.live_assets ?? raw.liveAssets;
303
+ if (liveAssetsRaw && typeof liveAssetsRaw === 'object') {
304
+ const record = liveAssetsRaw;
305
+ normalized.liveAssets = {
306
+ keepScripts: coerceBoolean(record.keep_scripts ?? record.keepScripts, normalized.liveAssets.keepScripts),
307
+ allowRemoteAssets: coerceBoolean(record.allow_remote_assets ?? record.allowRemoteAssets, normalized.liveAssets.allowRemoteAssets),
308
+ maxStylesheets: coerceNumber(record.max_stylesheets ?? record.maxStylesheets, normalized.liveAssets.maxStylesheets ?? null, {
309
+ min: 1,
310
+ allowNull: true,
311
+ field: 'pipelines.hi_fi_design_toolkit.live_assets.max_stylesheets',
312
+ warnings
313
+ }),
314
+ mirrorAssets: coerceBoolean(record.mirror_assets ?? record.mirrorAssets, normalized.liveAssets.mirrorAssets)
315
+ };
316
+ }
317
+ const interactionsRaw = raw.interactions;
318
+ if (interactionsRaw && typeof interactionsRaw === 'object') {
319
+ const record = interactionsRaw;
320
+ normalized.interactions = {
321
+ enabled: coerceBoolean(record.enabled, normalized.interactions.enabled),
322
+ scriptPath: coerceString(record.script_path ?? record.scriptPath) ?? normalized.interactions.scriptPath ?? null,
323
+ waitMs: coerceNumber(record.wait_ms ?? record.waitMs, normalized.interactions.waitMs ?? null, {
324
+ min: 0,
325
+ allowNull: true,
326
+ field: 'pipelines.hi_fi_design_toolkit.interactions.wait_ms',
327
+ warnings
328
+ }) ?? normalized.interactions.waitMs ?? null
329
+ };
330
+ }
331
+ return normalized;
332
+ }
333
+ function normalizeToolkitSources(raw, warnings) {
334
+ if (!raw) {
335
+ return [];
336
+ }
337
+ if (!Array.isArray(raw)) {
338
+ warnings.push('pipelines.hi_fi_design_toolkit.sources: expected array');
339
+ return [];
340
+ }
341
+ const result = [];
342
+ raw.forEach((entry, index) => {
343
+ if (typeof entry === 'string') {
344
+ const url = entry.trim();
345
+ if (!url) {
346
+ return;
347
+ }
348
+ const slug = sanitizeToolkitSlug(url, index);
349
+ result.push({
350
+ id: `source-${index + 1}`,
351
+ url,
352
+ referenceUrl: url,
353
+ slug,
354
+ title: null
355
+ });
356
+ return;
357
+ }
358
+ if (!entry || typeof entry !== 'object') {
359
+ warnings.push(`pipelines.hi_fi_design_toolkit.sources[${index}]: expected string or object`);
360
+ return;
361
+ }
362
+ const record = entry;
363
+ const url = coerceString(record.url ?? record.capture_url ?? record.source);
364
+ if (!url) {
365
+ warnings.push(`pipelines.hi_fi_design_toolkit.sources[${index}]: missing url`);
366
+ return;
367
+ }
368
+ const identifier = coerceString(record.id ?? record.slug ?? record.name) ?? `source-${index + 1}`;
369
+ const slug = sanitizeToolkitSlug(coerceString(record.slug) ?? identifier, index);
370
+ const referenceUrl = coerceString(record.reference_url ?? record.referenceUrl) ?? url;
371
+ const title = coerceString(record.title);
372
+ result.push({
373
+ id: identifier,
374
+ url,
375
+ referenceUrl,
376
+ slug,
377
+ title
378
+ });
379
+ });
380
+ return result;
381
+ }
382
+ function sanitizeToolkitSlug(candidate, index) {
383
+ const normalized = candidate
384
+ .toLowerCase()
385
+ .trim()
386
+ .replace(/[^a-z0-9-_]+/g, '-')
387
+ .replace(/^-+|-+$/g, '')
388
+ .slice(0, 48);
389
+ if (normalized.length > 0) {
390
+ return normalized;
391
+ }
392
+ return `source-${index + 1}`;
393
+ }
394
+ function normalizeAdvancedConfig(raw, defaults, field, warnings) {
395
+ if (!raw || typeof raw !== 'object') {
396
+ return { ...defaults };
397
+ }
398
+ const record = raw;
399
+ return {
400
+ enabled: coerceBoolean(record.enabled, defaults.enabled),
401
+ quotaSeconds: coerceNumber(record.quota_seconds ?? record.quotaSeconds, defaults.quotaSeconds, {
402
+ min: 0,
403
+ field: `${field}.quota_seconds`,
404
+ warnings
405
+ }) ?? defaults.quotaSeconds,
406
+ approver: coerceString(record.approver) ?? defaults.approver ?? null
407
+ };
408
+ }
409
+ function normalizeStringArray(raw, warnings, field) {
410
+ if (!raw) {
411
+ return [];
412
+ }
413
+ if (!Array.isArray(raw)) {
414
+ warnings.push(`${field}: expected array`);
415
+ return [];
416
+ }
417
+ return raw
418
+ .map((value, index) => {
419
+ const text = coerceString(value);
420
+ if (!text) {
421
+ warnings.push(`${field}[${index}]: ignored non-string entry`);
422
+ }
423
+ return text;
424
+ })
425
+ .filter((value) => Boolean(value))
426
+ .map((value) => value.trim())
427
+ .filter((value) => value.length > 0);
428
+ }
429
+ function coerceNumber(value, fallback, options = {}) {
430
+ if (value === null || value === undefined || value === '') {
431
+ if (!options.allowNull && options.field && options.warnings) {
432
+ options.warnings.push(`${options.field}: missing value`);
433
+ }
434
+ return options.allowNull ? null : fallback ?? null;
435
+ }
436
+ const numeric = typeof value === 'number' ? value : Number(value);
437
+ if (!Number.isFinite(numeric)) {
438
+ if (options.warnings && options.field) {
439
+ options.warnings.push(`${options.field}: expected numeric value`);
440
+ }
441
+ return options.allowNull ? null : fallback ?? null;
442
+ }
443
+ if (options.min !== undefined && numeric < options.min) {
444
+ if (options.warnings && options.field) {
445
+ options.warnings.push(`${options.field}: clamped to minimum ${options.min} (received ${numeric})`);
446
+ }
447
+ return options.min;
448
+ }
449
+ if (options.max !== undefined && numeric > options.max) {
450
+ if (options.warnings && options.field) {
451
+ options.warnings.push(`${options.field}: clamped to maximum ${options.max} (received ${numeric})`);
452
+ }
453
+ return options.max;
454
+ }
455
+ return numeric;
456
+ }
457
+ function coerceBoolean(value, fallback) {
458
+ if (typeof value === 'boolean') {
459
+ return value;
460
+ }
461
+ if (typeof value === 'number') {
462
+ return value !== 0;
463
+ }
464
+ if (typeof value === 'string') {
465
+ return isTruthyFlag(value) ?? fallback;
466
+ }
467
+ return fallback;
468
+ }
469
+ function coerceString(value) {
470
+ if (typeof value === 'string') {
471
+ const trimmed = value.trim();
472
+ return trimmed.length > 0 ? trimmed : null;
473
+ }
474
+ return null;
475
+ }
476
+ function isTruthyFlag(value) {
477
+ const normalized = value.trim().toLowerCase();
478
+ if (!normalized) {
479
+ return null;
480
+ }
481
+ if (['1', 'true', 'yes', 'on', 'enabled'].includes(normalized)) {
482
+ return true;
483
+ }
484
+ if (['0', 'false', 'no', 'off', 'disabled'].includes(normalized)) {
485
+ return false;
486
+ }
487
+ return null;
488
+ }
489
+ // structuredClone fallback for Node versions that may lack global support.
490
+ function structuredClone(value) {
491
+ if (typeof globalThis.structuredClone === 'function') {
492
+ return globalThis.structuredClone(value);
493
+ }
494
+ return JSON.parse(JSON.stringify(value));
495
+ }
@@ -0,0 +1,37 @@
1
+ export const EnvUtils = {
2
+ /**
3
+ * Parses a boolean environment variable.
4
+ * Returns true for '1', 'true', 'yes', 'on' (case-insensitive).
5
+ * Returns defaultValue if the variable is undefined or empty.
6
+ */
7
+ getBoolean(key, defaultValue = false) {
8
+ const val = process.env[key]?.toLowerCase().trim();
9
+ if (!val)
10
+ return defaultValue;
11
+ return this.isTrue(val);
12
+ },
13
+ /**
14
+ * Checks if a string value represents a true boolean.
15
+ */
16
+ isTrue(val) {
17
+ return ['1', 'true', 'yes', 'on'].includes(val);
18
+ },
19
+ /**
20
+ * Parses a string environment variable.
21
+ * Returns defaultValue if the variable is undefined.
22
+ */
23
+ getString(key, defaultValue = '') {
24
+ return process.env[key] || defaultValue;
25
+ },
26
+ /**
27
+ * Parses an integer environment variable.
28
+ * Returns defaultValue if the variable is undefined or not a valid number.
29
+ */
30
+ getInt(key, defaultValue) {
31
+ const val = process.env[key];
32
+ if (!val)
33
+ return defaultValue;
34
+ const parsed = parseInt(val, 10);
35
+ return isNaN(parsed) ? defaultValue : parsed;
36
+ }
37
+ };
@@ -0,0 +1,2 @@
1
+ export { loadDesignConfig, shouldActivateDesignPipeline, designPipelineId } from './designConfig.js';
2
+ export * from './env.js';