@tiangong-lca/cli 0.0.1

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 (96) hide show
  1. package/README.md +368 -0
  2. package/bin/tiangong.d.ts +1 -0
  3. package/bin/tiangong.js +32 -0
  4. package/dist/scripts/assert-full-coverage.js +77 -0
  5. package/dist/scripts/assert-full-coverage.js.map +1 -0
  6. package/dist/src/cli.js +2740 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/lib/artifacts.js +86 -0
  9. package/dist/src/lib/artifacts.js.map +1 -0
  10. package/dist/src/lib/dotenv.js +36 -0
  11. package/dist/src/lib/dotenv.js.map +1 -0
  12. package/dist/src/lib/env.js +139 -0
  13. package/dist/src/lib/env.js.map +1 -0
  14. package/dist/src/lib/errors.js +38 -0
  15. package/dist/src/lib/errors.js.map +1 -0
  16. package/dist/src/lib/flow-build-alias-map.js +299 -0
  17. package/dist/src/lib/flow-build-alias-map.js.map +1 -0
  18. package/dist/src/lib/flow-get.js +81 -0
  19. package/dist/src/lib/flow-get.js.map +1 -0
  20. package/dist/src/lib/flow-governance.js +164 -0
  21. package/dist/src/lib/flow-governance.js.map +1 -0
  22. package/dist/src/lib/flow-list.js +187 -0
  23. package/dist/src/lib/flow-list.js.map +1 -0
  24. package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
  25. package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
  26. package/dist/src/lib/flow-publish-version.js +541 -0
  27. package/dist/src/lib/flow-publish-version.js.map +1 -0
  28. package/dist/src/lib/flow-read.js +310 -0
  29. package/dist/src/lib/flow-read.js.map +1 -0
  30. package/dist/src/lib/flow-regen-product.js +1396 -0
  31. package/dist/src/lib/flow-regen-product.js.map +1 -0
  32. package/dist/src/lib/flow-remediate.js +821 -0
  33. package/dist/src/lib/flow-remediate.js.map +1 -0
  34. package/dist/src/lib/http.js +45 -0
  35. package/dist/src/lib/http.js.map +1 -0
  36. package/dist/src/lib/io.js +30 -0
  37. package/dist/src/lib/io.js.map +1 -0
  38. package/dist/src/lib/kb-search.js +83 -0
  39. package/dist/src/lib/kb-search.js.map +1 -0
  40. package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
  41. package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
  42. package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
  43. package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
  44. package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
  45. package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
  46. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
  47. package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
  48. package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
  49. package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
  50. package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
  51. package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
  52. package/dist/src/lib/llm.js +383 -0
  53. package/dist/src/lib/llm.js.map +1 -0
  54. package/dist/src/lib/process-auto-build.js +717 -0
  55. package/dist/src/lib/process-auto-build.js.map +1 -0
  56. package/dist/src/lib/process-batch-build.js +409 -0
  57. package/dist/src/lib/process-batch-build.js.map +1 -0
  58. package/dist/src/lib/process-get.js +66 -0
  59. package/dist/src/lib/process-get.js.map +1 -0
  60. package/dist/src/lib/process-publish-build.js +451 -0
  61. package/dist/src/lib/process-publish-build.js.map +1 -0
  62. package/dist/src/lib/process-resume-build.js +364 -0
  63. package/dist/src/lib/process-resume-build.js.map +1 -0
  64. package/dist/src/lib/publish.js +562 -0
  65. package/dist/src/lib/publish.js.map +1 -0
  66. package/dist/src/lib/remote.js +88 -0
  67. package/dist/src/lib/remote.js.map +1 -0
  68. package/dist/src/lib/review-flow.js +1110 -0
  69. package/dist/src/lib/review-flow.js.map +1 -0
  70. package/dist/src/lib/review-lifecyclemodel.js +727 -0
  71. package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
  72. package/dist/src/lib/review-process.js +702 -0
  73. package/dist/src/lib/review-process.js.map +1 -0
  74. package/dist/src/lib/run.js +106 -0
  75. package/dist/src/lib/run.js.map +1 -0
  76. package/dist/src/lib/state-lock.js +150 -0
  77. package/dist/src/lib/state-lock.js.map +1 -0
  78. package/dist/src/lib/supabase-client.js +269 -0
  79. package/dist/src/lib/supabase-client.js.map +1 -0
  80. package/dist/src/lib/supabase-json-ordered-write.js +186 -0
  81. package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
  82. package/dist/src/lib/supabase-rest.js +138 -0
  83. package/dist/src/lib/supabase-rest.js.map +1 -0
  84. package/dist/src/lib/supabase-session.js +424 -0
  85. package/dist/src/lib/supabase-session.js.map +1 -0
  86. package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
  87. package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
  88. package/dist/src/lib/unstructured.js +203 -0
  89. package/dist/src/lib/unstructured.js.map +1 -0
  90. package/dist/src/lib/user-api-key.js +75 -0
  91. package/dist/src/lib/user-api-key.js.map +1 -0
  92. package/dist/src/lib/validation.js +191 -0
  93. package/dist/src/lib/validation.js.map +1 -0
  94. package/dist/src/main.js +38 -0
  95. package/dist/src/main.js.map +1 -0
  96. package/package.json +81 -0
@@ -0,0 +1,1396 @@
1
+ import { createRequire } from 'node:module';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { writeJsonArtifact, writeJsonLinesArtifact, writeTextArtifact } from './artifacts.js';
7
+ import { CliError } from './errors.js';
8
+ import { coerceText, deepGet, extractFlowRecord, flowDatasetFromRow, isRecord, listify, loadRowsFromFile, normalizeText, } from './flow-governance.js';
9
+ import { resolveRepoRootFrom } from './validation.js';
10
+ const EMERGY_TEXT_KEYWORDS = [
11
+ 'emergy',
12
+ '能值',
13
+ '太阳能值',
14
+ 'solar emergy',
15
+ 'solar emjoule',
16
+ 'sej',
17
+ ];
18
+ function resolveCliRepoRoot() {
19
+ return resolveRepoRootFrom(path.dirname(fileURLToPath(import.meta.url)));
20
+ }
21
+ function buildSdkCandidates() {
22
+ return ['@tiangong-lca/tidas-sdk/core'];
23
+ }
24
+ function resolveSdkModuleFromCandidates(requireFn, candidates) {
25
+ const details = [];
26
+ for (const candidate of candidates) {
27
+ try {
28
+ const loaded = requireFn(candidate);
29
+ if (typeof loaded.createProcess === 'function') {
30
+ return {
31
+ ...loaded,
32
+ location: candidate,
33
+ };
34
+ }
35
+ details.push(`Candidate missing createProcess export: ${candidate}`);
36
+ }
37
+ catch (error) {
38
+ details.push(`Failed to load ${candidate}: ${String(error)}`);
39
+ }
40
+ }
41
+ throw new CliError('Unable to resolve the local tidas-sdk process factory.', {
42
+ code: 'FLOW_REGEN_PROCESS_SDK_NOT_FOUND',
43
+ exitCode: 2,
44
+ details,
45
+ });
46
+ }
47
+ function resolveLocalSdkModule() {
48
+ return resolveSdkModuleFromCandidates(createRequire(import.meta.url), buildSdkCandidates());
49
+ }
50
+ function cloneJson(value) {
51
+ return JSON.parse(JSON.stringify(value));
52
+ }
53
+ function assertInputFile(inputFile, requiredCode, missingCode) {
54
+ if (!inputFile) {
55
+ throw new CliError('Missing required input file value.', {
56
+ code: requiredCode,
57
+ exitCode: 2,
58
+ });
59
+ }
60
+ const resolved = path.resolve(inputFile);
61
+ if (!existsSync(resolved)) {
62
+ throw new CliError(`Input file not found: ${resolved}`, {
63
+ code: missingCode,
64
+ exitCode: 2,
65
+ });
66
+ }
67
+ return resolved;
68
+ }
69
+ function assertInputFiles(inputFiles, requiredCode, missingCode) {
70
+ if (!inputFiles.length) {
71
+ throw new CliError('At least one input file is required.', {
72
+ code: requiredCode,
73
+ exitCode: 2,
74
+ });
75
+ }
76
+ return inputFiles.map((inputFile) => assertInputFile(inputFile, requiredCode, missingCode));
77
+ }
78
+ function assertOutDir(outDir) {
79
+ if (!outDir) {
80
+ throw new CliError('Missing required --out-dir value.', {
81
+ code: 'FLOW_REGEN_OUT_DIR_REQUIRED',
82
+ exitCode: 2,
83
+ });
84
+ }
85
+ return path.resolve(outDir);
86
+ }
87
+ function readJsonObjectFile(filePath, requiredCode, missingCode, invalidCode) {
88
+ const resolved = assertInputFile(filePath, requiredCode, missingCode);
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(readFileSync(resolved, 'utf8'));
92
+ }
93
+ catch (error) {
94
+ throw new CliError(`Expected JSON object file: ${resolved}`, {
95
+ code: invalidCode,
96
+ exitCode: 2,
97
+ details: String(error),
98
+ });
99
+ }
100
+ if (!isRecord(parsed)) {
101
+ throw new CliError(`Expected JSON object file: ${resolved}`, {
102
+ code: invalidCode,
103
+ exitCode: 2,
104
+ });
105
+ }
106
+ return parsed;
107
+ }
108
+ function processDatasetFromRow(row) {
109
+ const payload = isRecord(row.json_ordered)
110
+ ? row.json_ordered
111
+ : isRecord(row.json)
112
+ ? row.json
113
+ : row;
114
+ return isRecord(payload.processDataSet) ? payload.processDataSet : payload;
115
+ }
116
+ function extractProcessIdentity(row) {
117
+ const dataset = processDatasetFromRow(row);
118
+ const info = deepGet(dataset, ['processInformation', 'dataSetInformation'], {});
119
+ const id = coerceText(row.id) || coerceText(info['common:UUID']);
120
+ const version = coerceText(row.version) ||
121
+ coerceText(deepGet(dataset, [
122
+ 'administrativeInformation',
123
+ 'publicationAndOwnership',
124
+ 'common:dataSetVersion',
125
+ ])) ||
126
+ '01.00.000';
127
+ const name = coerceText(deepGet(info, ['name', 'baseName']) ?? info.name) || id;
128
+ return { id, version, name };
129
+ }
130
+ function exchangeRecords(processRow) {
131
+ const dataset = processDatasetFromRow(processRow);
132
+ return listify(deepGet(dataset, ['exchanges', 'exchange'], [])).filter(isRecord);
133
+ }
134
+ function extractProcessReferenceExchange(processRow) {
135
+ const dataset = processDatasetFromRow(processRow);
136
+ const referenceInternalId = coerceText(deepGet(dataset, ['processInformation', 'quantitativeReference', 'referenceToReferenceFlow']));
137
+ const exchanges = exchangeRecords(processRow);
138
+ if (referenceInternalId) {
139
+ const exact = exchanges.find((exchange) => coerceText(exchange['@dataSetInternalID']) === referenceInternalId);
140
+ if (exact) {
141
+ return exact;
142
+ }
143
+ }
144
+ const outputExchange = exchanges.find((exchange) => coerceText(exchange.exchangeDirection).toLowerCase() === 'output');
145
+ if (outputExchange) {
146
+ return outputExchange;
147
+ }
148
+ return exchanges[0] ?? null;
149
+ }
150
+ function extractReferenceText(ref) {
151
+ if (!isRecord(ref)) {
152
+ return coerceText(ref);
153
+ }
154
+ return coerceText(ref['common:shortDescription']);
155
+ }
156
+ function extractProcessReferenceFlowRef(processRow) {
157
+ const exchange = extractProcessReferenceExchange(processRow) ?? {};
158
+ const ref = isRecord(exchange.referenceToFlowDataSet) ? exchange.referenceToFlowDataSet : {};
159
+ return {
160
+ flow_id: coerceText(ref['@refObjectId']),
161
+ flow_version: coerceText(ref['@version']),
162
+ flow_text: extractReferenceText(ref),
163
+ exchange_internal_id: coerceText(exchange['@dataSetInternalID']),
164
+ };
165
+ }
166
+ function processRowKey(processRow) {
167
+ const identity = extractProcessIdentity(processRow);
168
+ if (!identity.id || !identity.version) {
169
+ return '';
170
+ }
171
+ return `${identity.id}@${identity.version}`;
172
+ }
173
+ function versionKey(version) {
174
+ return String(version)
175
+ .split('.')
176
+ .map((part) => {
177
+ const parsed = Number.parseInt(part, 10);
178
+ return Number.isNaN(parsed) ? 0 : parsed;
179
+ });
180
+ }
181
+ function compareVersionKeys(left, right) {
182
+ const length = Math.max(left.length, right.length);
183
+ for (let index = 0; index < length; index += 1) {
184
+ const leftValue = left[index] ?? 0;
185
+ const rightValue = right[index] ?? 0;
186
+ if (leftValue !== rightValue) {
187
+ return leftValue - rightValue;
188
+ }
189
+ }
190
+ return 0;
191
+ }
192
+ function processRowSortComparator(left, right) {
193
+ const leftIdentity = extractProcessIdentity(left);
194
+ const rightIdentity = extractProcessIdentity(right);
195
+ const idCompare = leftIdentity.id.localeCompare(rightIdentity.id);
196
+ if (idCompare !== 0) {
197
+ return idCompare;
198
+ }
199
+ const versionCompare = compareVersionKeys(versionKey(leftIdentity.version), versionKey(rightIdentity.version));
200
+ if (versionCompare !== 0) {
201
+ return versionCompare;
202
+ }
203
+ return leftIdentity.name.localeCompare(rightIdentity.name);
204
+ }
205
+ function buildFlowIndex(rows) {
206
+ const byUuid = {};
207
+ const byUuidVersion = {};
208
+ const byName = {};
209
+ const records = [];
210
+ for (const row of rows) {
211
+ const record = extractFlowRecord(row);
212
+ records.push(record);
213
+ byUuid[record.id] ??= [];
214
+ byUuid[record.id].push(record);
215
+ byUuidVersion[`${record.id}@${record.version}`] = record;
216
+ const normalizedName = normalizeText(record.name);
217
+ byName[normalizedName] ??= [];
218
+ byName[normalizedName].push(record);
219
+ }
220
+ return {
221
+ records,
222
+ byUuid,
223
+ byUuidVersion,
224
+ byName,
225
+ };
226
+ }
227
+ function aliasLookup(aliasMap, flowUuid, flowVersion) {
228
+ const versionedKey = flowVersion ? `${flowUuid}@${flowVersion}` : null;
229
+ for (const key of [versionedKey, flowUuid]) {
230
+ if (!key) {
231
+ continue;
232
+ }
233
+ const candidate = aliasMap[key];
234
+ if (isRecord(candidate)) {
235
+ return candidate;
236
+ }
237
+ }
238
+ return null;
239
+ }
240
+ function buildLocalDatasetUri(datasetKind, uuidValue, version) {
241
+ if (!uuidValue) {
242
+ return '';
243
+ }
244
+ const folderMap = {
245
+ flow: 'flows',
246
+ 'flow data set': 'flows',
247
+ };
248
+ const folder = folderMap[datasetKind.trim().toLowerCase()] ?? 'datasets';
249
+ const versionText = version.trim() || '01.00.000';
250
+ return `../${folder}/${uuidValue}_${versionText}.xml`;
251
+ }
252
+ function preserveShortDescriptionShape(existing, target) {
253
+ if (Array.isArray(existing)) {
254
+ if (existing.length > 0 && isRecord(existing[0])) {
255
+ const patched = cloneJson(existing[0]);
256
+ patched['@xml:lang'] =
257
+ coerceText(target['@xml:lang']) || coerceText(patched['@xml:lang']) || 'en';
258
+ patched['#text'] = coerceText(target['#text']);
259
+ return [patched];
260
+ }
261
+ return [cloneJson(target)];
262
+ }
263
+ if (isRecord(existing)) {
264
+ const patched = cloneJson(existing);
265
+ patched['@xml:lang'] =
266
+ coerceText(target['@xml:lang']) || coerceText(patched['@xml:lang']) || 'en';
267
+ patched['#text'] = coerceText(target['#text']);
268
+ return patched;
269
+ }
270
+ return cloneJson(target);
271
+ }
272
+ function flowReferenceFromRecord(record) {
273
+ const targetShortDescription = record.shortDescription ??
274
+ {
275
+ '@xml:lang': 'en',
276
+ '#text': record.name,
277
+ };
278
+ return {
279
+ '@type': 'flow data set',
280
+ '@refObjectId': record.id,
281
+ '@version': record.version,
282
+ '@uri': buildLocalDatasetUri('flow data set', record.id, record.version),
283
+ 'common:shortDescription': cloneJson(targetShortDescription),
284
+ };
285
+ }
286
+ function patchedFlowReference(currentRef, record) {
287
+ const current = isRecord(currentRef) ? cloneJson(currentRef) : {};
288
+ const target = flowReferenceFromRecord(record);
289
+ current['@type'] = coerceText(current['@type']) || coerceText(target['@type']);
290
+ current['@refObjectId'] = target['@refObjectId'];
291
+ current['@version'] = target['@version'];
292
+ current['@uri'] = target['@uri'];
293
+ current['common:shortDescription'] = preserveShortDescriptionShape(current['common:shortDescription'], target['common:shortDescription']);
294
+ return current;
295
+ }
296
+ function langEntries(value) {
297
+ const entries = [];
298
+ if (Array.isArray(value)) {
299
+ value.forEach((item) => {
300
+ entries.push(...langEntries(item));
301
+ });
302
+ return entries;
303
+ }
304
+ if (!isRecord(value)) {
305
+ return entries;
306
+ }
307
+ if ('#text' in value || '@xml:lang' in value) {
308
+ const text = coerceText(value['#text']);
309
+ if (text) {
310
+ entries.push({
311
+ lang: coerceText(value['@xml:lang']) || 'en',
312
+ text,
313
+ });
314
+ }
315
+ return entries;
316
+ }
317
+ Object.values(value).forEach((nested) => {
318
+ entries.push(...langEntries(nested));
319
+ });
320
+ return entries;
321
+ }
322
+ function textHasEmergyKeyword(text) {
323
+ const normalized = normalizeText(text);
324
+ if (!normalized) {
325
+ return false;
326
+ }
327
+ return EMERGY_TEXT_KEYWORDS.some((keyword) => normalized.includes(normalizeText(keyword)));
328
+ }
329
+ function uniqueNonEmptyTexts(values) {
330
+ const seen = new Set();
331
+ const result = [];
332
+ values.forEach((value) => {
333
+ const text = String(value || '').trim();
334
+ if (!text) {
335
+ return;
336
+ }
337
+ const normalized = normalizeText(text);
338
+ if (seen.has(normalized)) {
339
+ return;
340
+ }
341
+ seen.add(normalized);
342
+ result.push(text);
343
+ });
344
+ return result;
345
+ }
346
+ function flowEmergyScopeDecision(row) {
347
+ const record = extractFlowRecord(row);
348
+ const dataset = flowDatasetFromRow(row);
349
+ const nameNode = deepGet(dataset, ['flowInformation', 'dataSetInformation', 'name'], {});
350
+ const nameCandidates = langEntries(nameNode).map((entry) => entry.text);
351
+ const signals = [];
352
+ uniqueNonEmptyTexts(nameCandidates).forEach((text) => {
353
+ if (textHasEmergyKeyword(text)) {
354
+ signals.push(`emergy_name:${text}`);
355
+ }
356
+ });
357
+ return {
358
+ entity_type: 'flow',
359
+ flow_id: record.id,
360
+ version: record.version,
361
+ name: record.name,
362
+ flow_type: record.flowType,
363
+ excluded: signals.length > 0,
364
+ reason: signals.length > 0 ? 'emergy_named_flow' : '',
365
+ signals,
366
+ };
367
+ }
368
+ function processEmergyScopeDecision(processRow, flowIndex) {
369
+ const identity = extractProcessIdentity(processRow);
370
+ const dataset = processDatasetFromRow(processRow);
371
+ const referenceInfo = extractProcessReferenceFlowRef(processRow);
372
+ const signals = [];
373
+ let matchedRecord = flowIndex?.byUuidVersion[`${referenceInfo.flow_id}@${referenceInfo.flow_version}`] ?? null;
374
+ if (!matchedRecord && flowIndex?.byUuid[referenceInfo.flow_id]?.length) {
375
+ matchedRecord = flowIndex.byUuid[referenceInfo.flow_id][0] ?? null;
376
+ }
377
+ if (matchedRecord) {
378
+ const flowDecision = flowEmergyScopeDecision(matchedRecord.row);
379
+ const flowSignals = flowDecision.signals.filter((value) => typeof value === 'string');
380
+ signals.push(...flowSignals);
381
+ }
382
+ if (!signals.length) {
383
+ const functionalUnitTexts = langEntries(deepGet(dataset, ['processInformation', 'quantitativeReference', 'functionalUnitOrOther'])).map((entry) => entry.text);
384
+ uniqueNonEmptyTexts([referenceInfo.flow_text, ...functionalUnitTexts]).forEach((text) => {
385
+ if (textHasEmergyKeyword(text)) {
386
+ signals.push(`process_ref_text:${text}`);
387
+ }
388
+ });
389
+ }
390
+ return {
391
+ entity_type: 'process',
392
+ process_id: identity.id,
393
+ process_version: identity.version,
394
+ process_name: identity.name,
395
+ reference_flow_id: referenceInfo.flow_id,
396
+ reference_flow_version: referenceInfo.flow_version,
397
+ reference_exchange_internal_id: referenceInfo.exchange_internal_id,
398
+ excluded: signals.length > 0,
399
+ reason: signals.length > 0 ? 'reference_flow_name_mentions_emergy' : '',
400
+ signals,
401
+ };
402
+ }
403
+ function filterEmergyNamedProcesses(rows, flowIndex) {
404
+ const keptRows = [];
405
+ const excludedRows = [];
406
+ rows.forEach((row) => {
407
+ const decision = processEmergyScopeDecision(row, flowIndex);
408
+ if (decision.excluded === true) {
409
+ excludedRows.push(decision);
410
+ return;
411
+ }
412
+ keptRows.push(row);
413
+ });
414
+ return {
415
+ keptRows,
416
+ excludedRows,
417
+ };
418
+ }
419
+ function classifyExchangeRef(processRow, exchange, scopeIndex, catalogIndex, aliasMap) {
420
+ const identity = extractProcessIdentity(processRow);
421
+ const ref = isRecord(exchange.referenceToFlowDataSet) ? exchange.referenceToFlowDataSet : {};
422
+ const flowUuid = coerceText(ref['@refObjectId']);
423
+ const flowVersion = coerceText(ref['@version']);
424
+ const finding = {
425
+ process_id: identity.id,
426
+ process_version: identity.version,
427
+ process_name: identity.name,
428
+ exchange_internal_id: coerceText(exchange['@dataSetInternalID']),
429
+ exchange_direction: exchange.exchangeDirection,
430
+ reference_flow_id: flowUuid,
431
+ reference_flow_version: flowVersion,
432
+ reference_flow_text: extractReferenceText(ref),
433
+ issue_type: 'exists_in_target',
434
+ severity: 'info',
435
+ evidence: {},
436
+ };
437
+ if (!flowUuid) {
438
+ finding.issue_type = 'no_reference';
439
+ finding.severity = 'error';
440
+ return finding;
441
+ }
442
+ const aliasTarget = aliasLookup(aliasMap, flowUuid, flowVersion || null);
443
+ if (aliasTarget) {
444
+ finding.issue_type = 'alias_target_available';
445
+ finding.severity = 'warning';
446
+ finding.evidence = {
447
+ alias_target: aliasTarget,
448
+ };
449
+ }
450
+ if (!flowVersion) {
451
+ if (scopeIndex.byUuid[flowUuid]?.length) {
452
+ finding.issue_type = 'version_missing';
453
+ finding.severity = 'warning';
454
+ return finding;
455
+ }
456
+ if (catalogIndex.byUuid[flowUuid]?.length) {
457
+ finding.issue_type = 'exists_outside_target';
458
+ finding.severity = 'warning';
459
+ return finding;
460
+ }
461
+ finding.issue_type = 'missing_uuid';
462
+ finding.severity = 'error';
463
+ return finding;
464
+ }
465
+ const scopeMatch = scopeIndex.byUuidVersion[`${flowUuid}@${flowVersion}`];
466
+ if (scopeMatch) {
467
+ finding.evidence = {
468
+ scope_group: '',
469
+ flow_name: scopeMatch.name,
470
+ };
471
+ return finding;
472
+ }
473
+ if (scopeIndex.byUuid[flowUuid]?.length) {
474
+ finding.issue_type = 'broken_version';
475
+ finding.severity = 'error';
476
+ finding.evidence = {
477
+ available_versions_in_target: scopeIndex.byUuid[flowUuid]
478
+ .map((record) => record.version)
479
+ .sort(),
480
+ };
481
+ return finding;
482
+ }
483
+ if (catalogIndex.byUuidVersion[`${flowUuid}@${flowVersion}`] ||
484
+ catalogIndex.byUuid[flowUuid]?.length) {
485
+ finding.issue_type = 'exists_outside_target';
486
+ finding.severity = 'warning';
487
+ return finding;
488
+ }
489
+ finding.issue_type = 'missing_uuid';
490
+ finding.severity = 'error';
491
+ return finding;
492
+ }
493
+ function buildFindingMap(findings) {
494
+ const result = {};
495
+ findings.forEach((finding) => {
496
+ result[`${finding.process_id}@${finding.process_version}::${finding.exchange_internal_id}`] =
497
+ finding;
498
+ });
499
+ return result;
500
+ }
501
+ function planExchangeRepair(exchange, scopeIndex, aliasMap, finding, autoPatchPolicy) {
502
+ const ref = isRecord(exchange.referenceToFlowDataSet) ? exchange.referenceToFlowDataSet : {};
503
+ const flowUuid = coerceText(ref['@refObjectId']);
504
+ const flowVersion = coerceText(ref['@version']);
505
+ const exchangeText = extractReferenceText(ref) || coerceText(exchange.exchangeName) || coerceText(exchange.name);
506
+ const base = {
507
+ exchange_internal_id: coerceText(exchange['@dataSetInternalID']),
508
+ current_flow_id: flowUuid,
509
+ current_flow_version: flowVersion,
510
+ exchange_direction: exchange.exchangeDirection,
511
+ exchange_text: exchangeText,
512
+ current_issue_type: finding?.issue_type,
513
+ auto_patch_policy: autoPatchPolicy,
514
+ decision: 'manual_review',
515
+ reason: 'no_deterministic_match',
516
+ process_id: '',
517
+ process_version: '',
518
+ process_name: '',
519
+ };
520
+ if (flowUuid && flowVersion) {
521
+ const currentRecord = scopeIndex.byUuidVersion[`${flowUuid}@${flowVersion}`];
522
+ if (currentRecord) {
523
+ return {
524
+ ...base,
525
+ decision: 'keep_as_is',
526
+ reason: 'already_in_target',
527
+ target_flow_id: currentRecord.id,
528
+ target_flow_version: currentRecord.version,
529
+ };
530
+ }
531
+ }
532
+ const aliasTarget = aliasLookup(aliasMap, flowUuid, flowVersion || null);
533
+ if (aliasTarget) {
534
+ const targetRecord = scopeIndex.byUuidVersion[`${coerceText(aliasTarget.id)}@${coerceText(aliasTarget.version)}`];
535
+ if (targetRecord && autoPatchPolicy !== 'disabled') {
536
+ return {
537
+ ...base,
538
+ decision: 'auto_patch',
539
+ reason: 'direct_alias_map',
540
+ target_flow_id: targetRecord.id,
541
+ target_flow_version: targetRecord.version,
542
+ target_flow_name: targetRecord.name,
543
+ target_reference: patchedFlowReference(ref, targetRecord),
544
+ };
545
+ }
546
+ if (targetRecord) {
547
+ return {
548
+ ...base,
549
+ decision: 'manual_review',
550
+ reason: 'alias_target_found_but_policy_disallows_auto_patch',
551
+ candidate_count: 1,
552
+ candidate_refs: [
553
+ {
554
+ id: targetRecord.id,
555
+ version: targetRecord.version,
556
+ name: targetRecord.name,
557
+ },
558
+ ],
559
+ };
560
+ }
561
+ }
562
+ const candidates = scopeIndex.byName[normalizeText(exchangeText)] ?? [];
563
+ if (candidates.length === 1) {
564
+ const candidate = candidates[0];
565
+ if (autoPatchPolicy === 'alias-or-unique-name') {
566
+ return {
567
+ ...base,
568
+ decision: 'auto_patch',
569
+ reason: 'unique_exact_name_match',
570
+ target_flow_id: candidate.id,
571
+ target_flow_version: candidate.version,
572
+ target_flow_name: candidate.name,
573
+ target_reference: patchedFlowReference(ref, candidate),
574
+ };
575
+ }
576
+ return {
577
+ ...base,
578
+ decision: 'manual_review',
579
+ reason: 'unique_exact_name_match_blocked_by_policy',
580
+ candidate_count: 1,
581
+ candidate_refs: [
582
+ {
583
+ id: candidate.id,
584
+ version: candidate.version,
585
+ name: candidate.name,
586
+ },
587
+ ],
588
+ };
589
+ }
590
+ if (candidates.length > 1) {
591
+ return {
592
+ ...base,
593
+ decision: 'manual_review',
594
+ reason: 'ambiguous_exact_name_match',
595
+ candidate_count: candidates.length,
596
+ candidate_refs: candidates.map((candidate) => ({
597
+ id: candidate.id,
598
+ version: candidate.version,
599
+ name: candidate.name,
600
+ })),
601
+ };
602
+ }
603
+ return base;
604
+ }
605
+ function buildUnifiedJsonDiff(before, after) {
606
+ const beforeLines = JSON.stringify(before, null, 2).split('\n');
607
+ const afterLines = JSON.stringify(after, null, 2).split('\n');
608
+ if (isDeepStrictEqual(beforeLines, afterLines)) {
609
+ return '--- before.json\n+++ after.json\n';
610
+ }
611
+ let prefix = 0;
612
+ while (prefix < beforeLines.length &&
613
+ prefix < afterLines.length &&
614
+ beforeLines[prefix] === afterLines[prefix]) {
615
+ prefix += 1;
616
+ }
617
+ let beforeSuffix = beforeLines.length - 1;
618
+ let afterSuffix = afterLines.length - 1;
619
+ while (beforeSuffix >= prefix &&
620
+ afterSuffix >= prefix &&
621
+ beforeLines[beforeSuffix] === afterLines[afterSuffix]) {
622
+ beforeSuffix -= 1;
623
+ afterSuffix -= 1;
624
+ }
625
+ const beforeChanged = beforeLines.slice(prefix, beforeSuffix + 1);
626
+ const afterChanged = afterLines.slice(prefix, afterSuffix + 1);
627
+ const hunkHeader = `@@ -${prefix + 1},${beforeChanged.length} +${prefix + 1},${afterChanged.length} @@`;
628
+ return [
629
+ '--- before.json',
630
+ '+++ after.json',
631
+ hunkHeader,
632
+ ...beforeChanged.map((line) => `-${line}`),
633
+ ...afterChanged.map((line) => `+${line}`),
634
+ '',
635
+ ].join('\n');
636
+ }
637
+ function deepDiffPaths(before, after, prefix = []) {
638
+ if (typeof before !== typeof after || Array.isArray(before) !== Array.isArray(after)) {
639
+ return [prefix];
640
+ }
641
+ if (Array.isArray(before) && Array.isArray(after)) {
642
+ const paths = [];
643
+ const maxLength = Math.max(before.length, after.length);
644
+ for (let index = 0; index < maxLength; index += 1) {
645
+ if (index >= before.length || index >= after.length) {
646
+ paths.push([...prefix, index]);
647
+ continue;
648
+ }
649
+ paths.push(...deepDiffPaths(before[index], after[index], [...prefix, index]));
650
+ }
651
+ return paths;
652
+ }
653
+ if (isRecord(before) && isRecord(after)) {
654
+ const paths = [];
655
+ const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
656
+ [...keys].sort().forEach((key) => {
657
+ if (!(key in before) || !(key in after)) {
658
+ paths.push([...prefix, key]);
659
+ return;
660
+ }
661
+ paths.push(...deepDiffPaths(before[key], after[key], [...prefix, key]));
662
+ });
663
+ return paths;
664
+ }
665
+ return isDeepStrictEqual(before, after) ? [] : [prefix];
666
+ }
667
+ function pathContainsReferenceToFlow(pathParts) {
668
+ return pathParts.includes('referenceToFlowDataSet');
669
+ }
670
+ function safeProcessKey(processId, version) {
671
+ const versionSlug = version.replace(/[^0-9A-Za-z._-]+/gu, '_') || 'unknown';
672
+ return `${processId}__${versionSlug}`;
673
+ }
674
+ function writeRowsFile(filePath, rows) {
675
+ if (filePath.toLowerCase().endsWith('.jsonl')) {
676
+ return writeJsonLinesArtifact(filePath, rows);
677
+ }
678
+ return writeJsonArtifact(filePath, rows);
679
+ }
680
+ function mergeRowsByIdentity(existingRows, incomingRows) {
681
+ const mergedRows = existingRows.map((row) => cloneJson(row));
682
+ const keyToIndex = {};
683
+ mergedRows.forEach((row, index) => {
684
+ const identity = extractProcessIdentity(row);
685
+ if (identity.id && identity.version) {
686
+ keyToIndex[`${identity.id}@${identity.version}`] = index;
687
+ }
688
+ });
689
+ let inserted = 0;
690
+ let updated = 0;
691
+ let unchanged = 0;
692
+ let skippedInvalid = 0;
693
+ incomingRows.forEach((row) => {
694
+ const identity = extractProcessIdentity(row);
695
+ if (!identity.id || !identity.version) {
696
+ skippedInvalid += 1;
697
+ return;
698
+ }
699
+ const key = `${identity.id}@${identity.version}`;
700
+ if (!(key in keyToIndex)) {
701
+ keyToIndex[key] = mergedRows.length;
702
+ mergedRows.push(cloneJson(row));
703
+ inserted += 1;
704
+ return;
705
+ }
706
+ const existingIndex = keyToIndex[key];
707
+ if (isDeepStrictEqual(mergedRows[existingIndex], row)) {
708
+ unchanged += 1;
709
+ return;
710
+ }
711
+ mergedRows[existingIndex] = cloneJson(row);
712
+ updated += 1;
713
+ });
714
+ return {
715
+ mergedRows,
716
+ counts: {
717
+ inserted,
718
+ updated,
719
+ unchanged,
720
+ skipped_invalid: skippedInvalid,
721
+ },
722
+ };
723
+ }
724
+ function syncProcessPoolFile(poolFile, incomingRows) {
725
+ const poolPath = path.resolve(poolFile);
726
+ const existingRows = existsSync(poolPath) ? loadRowsFromFile(poolPath) : [];
727
+ const { mergedRows, counts } = mergeRowsByIdentity(existingRows, incomingRows);
728
+ mergedRows.sort(processRowSortComparator);
729
+ writeRowsFile(poolPath, mergedRows);
730
+ return {
731
+ pool_file: poolPath,
732
+ pool_pre_count: existingRows.length,
733
+ incoming_count: incomingRows.length,
734
+ pool_post_count: mergedRows.length,
735
+ ...counts,
736
+ };
737
+ }
738
+ function resolveProcessValidator(tidasMode, deps) {
739
+ if (tidasMode === 'skip') {
740
+ return {
741
+ createProcess: null,
742
+ tidasValidation: false,
743
+ };
744
+ }
745
+ try {
746
+ const module = (deps.loadSdkModule ?? resolveLocalSdkModule)();
747
+ if (typeof module.createProcess === 'function') {
748
+ return {
749
+ createProcess: module.createProcess,
750
+ tidasValidation: true,
751
+ };
752
+ }
753
+ if (tidasMode === 'required') {
754
+ throw new CliError('Resolved tidas-sdk core module does not expose createProcess.', {
755
+ code: 'FLOW_REGEN_PROCESS_SDK_INVALID',
756
+ exitCode: 2,
757
+ details: module.location ?? null,
758
+ });
759
+ }
760
+ }
761
+ catch (error) {
762
+ if (tidasMode === 'required') {
763
+ throw error;
764
+ }
765
+ }
766
+ return {
767
+ createProcess: null,
768
+ tidasValidation: false,
769
+ };
770
+ }
771
+ function formatValidationDetails(validation) {
772
+ if (validation === null || validation === undefined) {
773
+ return 'Validator returned no result.';
774
+ }
775
+ if (validation instanceof Error) {
776
+ return validation.message;
777
+ }
778
+ if (typeof validation === 'string') {
779
+ return validation;
780
+ }
781
+ try {
782
+ return JSON.stringify(validation);
783
+ }
784
+ catch {
785
+ return String(validation);
786
+ }
787
+ }
788
+ function evaluateProcessSdkValidation(payload, createProcess) {
789
+ try {
790
+ const entity = createProcess(payload, {
791
+ mode: 'strict',
792
+ throwOnError: false,
793
+ deepValidation: true,
794
+ });
795
+ const validation = typeof entity?.validateEnhanced === 'function'
796
+ ? entity.validateEnhanced()
797
+ : typeof entity?.validate === 'function'
798
+ ? entity.validate()
799
+ : null;
800
+ if (isRecord(validation) && validation.success === true) {
801
+ return null;
802
+ }
803
+ return formatValidationDetails(validation);
804
+ }
805
+ catch (error) {
806
+ return formatValidationDetails(error);
807
+ }
808
+ }
809
+ function validateProcessPatch(originalRow, patchedRow, scopeIndex, createProcess) {
810
+ const identity = extractProcessIdentity(patchedRow);
811
+ const issues = [];
812
+ if (!originalRow) {
813
+ issues.push({
814
+ type: 'missing_original_row',
815
+ severity: 'error',
816
+ });
817
+ return {
818
+ process_id: identity.id,
819
+ process_version: identity.version,
820
+ process_name: identity.name,
821
+ ok: false,
822
+ issues,
823
+ };
824
+ }
825
+ const illegalPaths = deepDiffPaths(originalRow, patchedRow).filter((pathParts) => !pathContainsReferenceToFlow(pathParts));
826
+ if (illegalPaths.length > 0) {
827
+ issues.push({
828
+ type: 'non_reference_changes_detected',
829
+ severity: 'error',
830
+ paths: illegalPaths.map((pathParts) => pathParts.map((part) => String(part)).join('.')),
831
+ });
832
+ }
833
+ const originalQuantitativeReference = coerceText(deepGet(processDatasetFromRow(originalRow), [
834
+ 'processInformation',
835
+ 'quantitativeReference',
836
+ 'referenceToReferenceFlow',
837
+ ]));
838
+ const patchedQuantitativeReference = coerceText(deepGet(processDatasetFromRow(patchedRow), [
839
+ 'processInformation',
840
+ 'quantitativeReference',
841
+ 'referenceToReferenceFlow',
842
+ ]));
843
+ if (originalQuantitativeReference !== patchedQuantitativeReference) {
844
+ issues.push({
845
+ type: 'quantitative_reference_changed',
846
+ severity: 'error',
847
+ before: originalQuantitativeReference,
848
+ after: patchedQuantitativeReference,
849
+ });
850
+ }
851
+ const originalExchangeCount = exchangeRecords(originalRow).length;
852
+ const patchedExchangeCount = exchangeRecords(patchedRow).length;
853
+ if (originalExchangeCount !== patchedExchangeCount) {
854
+ issues.push({
855
+ type: 'exchange_count_changed',
856
+ severity: 'error',
857
+ before: originalExchangeCount,
858
+ after: patchedExchangeCount,
859
+ });
860
+ }
861
+ exchangeRecords(patchedRow).forEach((exchange) => {
862
+ const ref = isRecord(exchange.referenceToFlowDataSet) ? exchange.referenceToFlowDataSet : {};
863
+ const flowId = coerceText(ref['@refObjectId']);
864
+ const flowVersion = coerceText(ref['@version']);
865
+ if (!flowId || !flowVersion) {
866
+ issues.push({
867
+ type: 'missing_flow_reference_after_patch',
868
+ severity: 'error',
869
+ exchange_internal_id: coerceText(exchange['@dataSetInternalID']),
870
+ flow_id: flowId,
871
+ flow_version: flowVersion,
872
+ });
873
+ return;
874
+ }
875
+ if (!scopeIndex.byUuidVersion[`${flowId}@${flowVersion}`]) {
876
+ issues.push({
877
+ type: 'patched_reference_not_in_scope_catalog',
878
+ severity: 'error',
879
+ exchange_internal_id: coerceText(exchange['@dataSetInternalID']),
880
+ flow_id: flowId,
881
+ flow_version: flowVersion,
882
+ });
883
+ }
884
+ });
885
+ if (createProcess) {
886
+ const details = evaluateProcessSdkValidation(processDatasetFromRow(patchedRow), createProcess);
887
+ if (details !== null) {
888
+ issues.push({
889
+ type: 'tidas_validation_failed',
890
+ severity: 'error',
891
+ details,
892
+ });
893
+ }
894
+ }
895
+ return {
896
+ process_id: identity.id,
897
+ process_version: identity.version,
898
+ process_name: identity.name,
899
+ ok: issues.length === 0,
900
+ issues,
901
+ };
902
+ }
903
+ function buildScanStageFiles(outDir) {
904
+ return {
905
+ out_dir: outDir,
906
+ emergy_excluded_processes: path.join(outDir, 'emergy-excluded-processes.json'),
907
+ summary: path.join(outDir, 'scan-summary.json'),
908
+ findings: path.join(outDir, 'scan-findings.json'),
909
+ findings_jsonl: path.join(outDir, 'scan-findings.jsonl'),
910
+ };
911
+ }
912
+ function buildRepairStageFiles(outDir) {
913
+ return {
914
+ out_dir: outDir,
915
+ plan: path.join(outDir, 'repair-plan.json'),
916
+ plan_jsonl: path.join(outDir, 'repair-plan.jsonl'),
917
+ manual_review_queue: path.join(outDir, 'manual-review-queue.jsonl'),
918
+ summary: path.join(outDir, 'repair-summary.json'),
919
+ };
920
+ }
921
+ function buildApplyStageFiles(outDir) {
922
+ return {
923
+ ...buildRepairStageFiles(outDir),
924
+ patched_processes: path.join(outDir, 'patched-processes.json'),
925
+ patch_root: path.join(outDir, 'process-patches'),
926
+ };
927
+ }
928
+ function buildValidateStageFiles(outDir) {
929
+ return {
930
+ out_dir: outDir,
931
+ report: path.join(outDir, 'validation-report.json'),
932
+ failures: path.join(outDir, 'validation-failures.jsonl'),
933
+ };
934
+ }
935
+ function loadOptionalScanFindings(scanFindingsFile, requiredCode, missingCode) {
936
+ if (!scanFindingsFile) {
937
+ return {
938
+ resolvedFile: null,
939
+ scanFindings: [],
940
+ };
941
+ }
942
+ const resolvedFile = assertInputFile(scanFindingsFile, requiredCode, missingCode);
943
+ return {
944
+ resolvedFile,
945
+ scanFindings: loadRowsFromFile(resolvedFile),
946
+ };
947
+ }
948
+ function runScanStage(processes, scopeIndex, catalogIndex, aliasMap, outDir, excludeEmergy) {
949
+ const files = buildScanStageFiles(outDir);
950
+ const { keptRows, excludedRows } = excludeEmergy
951
+ ? filterEmergyNamedProcesses(processes, catalogIndex)
952
+ : { keptRows: processes, excludedRows: [] };
953
+ const findings = [];
954
+ const issueProcessKeys = new Set();
955
+ const issueCounts = {};
956
+ let exchangeCount = 0;
957
+ keptRows.forEach((processRow) => {
958
+ const identity = extractProcessIdentity(processRow);
959
+ const exchanges = exchangeRecords(processRow);
960
+ exchangeCount += exchanges.length;
961
+ exchanges.forEach((exchange) => {
962
+ const finding = classifyExchangeRef(processRow, exchange, scopeIndex, catalogIndex, aliasMap);
963
+ findings.push(finding);
964
+ issueCounts[finding.issue_type] = (issueCounts[finding.issue_type] ?? 0) + 1;
965
+ if (finding.issue_type !== 'exists_in_target') {
966
+ issueProcessKeys.add(`${identity.id}@${identity.version}`);
967
+ }
968
+ });
969
+ });
970
+ const summary = {
971
+ process_count_before_emergy_exclusion: processes.length,
972
+ process_count: keptRows.length,
973
+ emergy_excluded_process_count: excludedRows.length,
974
+ exchange_count: exchangeCount,
975
+ issue_counts: issueCounts,
976
+ processes_with_issues: issueProcessKeys.size,
977
+ };
978
+ writeJsonArtifact(files.emergy_excluded_processes, excludedRows);
979
+ writeJsonArtifact(files.summary, summary);
980
+ writeJsonArtifact(files.findings, findings);
981
+ writeJsonLinesArtifact(files.findings_jsonl, findings);
982
+ return {
983
+ filteredProcesses: keptRows,
984
+ emergyExcludedProcesses: excludedRows,
985
+ findings,
986
+ summary,
987
+ files,
988
+ };
989
+ }
990
+ function runRepairStage(options) {
991
+ const files = options.apply
992
+ ? buildApplyStageFiles(options.outDir)
993
+ : buildRepairStageFiles(options.outDir);
994
+ const findingMap = buildFindingMap(options.scanFindings);
995
+ const repairPlan = [];
996
+ const manualQueue = [];
997
+ const patchedRows = [];
998
+ const decisionCounts = {};
999
+ options.processes.forEach((processRow) => {
1000
+ const identity = extractProcessIdentity(processRow);
1001
+ const processKey = `${identity.id}@${identity.version}`;
1002
+ const workingRow = options.apply ? cloneJson(processRow) : processRow;
1003
+ const processPlan = [];
1004
+ let changed = false;
1005
+ exchangeRecords(workingRow).forEach((exchange) => {
1006
+ const action = planExchangeRepair(exchange, options.scopeIndex, options.aliasMap, findingMap[`${processKey}::${coerceText(exchange['@dataSetInternalID'])}`] ?? null, options.autoPatchPolicy);
1007
+ action.process_id = identity.id;
1008
+ action.process_version = identity.version;
1009
+ action.process_name = identity.name;
1010
+ processPlan.push(action);
1011
+ repairPlan.push(action);
1012
+ decisionCounts[action.decision] = (decisionCounts[action.decision] ?? 0) + 1;
1013
+ if (action.decision === 'manual_review') {
1014
+ manualQueue.push(action);
1015
+ }
1016
+ if (options.apply && action.decision === 'auto_patch' && isRecord(action.target_reference)) {
1017
+ exchange.referenceToFlowDataSet = cloneJson(action.target_reference);
1018
+ changed = true;
1019
+ }
1020
+ });
1021
+ if (options.apply && changed) {
1022
+ patchedRows.push(workingRow);
1023
+ const patchRoot = files.patch_root;
1024
+ const processDir = path.join(patchRoot, safeProcessKey(identity.id, identity.version));
1025
+ writeJsonArtifact(path.join(processDir, 'before.json'), processRow);
1026
+ writeJsonArtifact(path.join(processDir, 'after.json'), workingRow);
1027
+ writeJsonArtifact(path.join(processDir, 'evidence.json'), processPlan.filter((item) => item.decision === 'auto_patch'));
1028
+ writeTextArtifact(path.join(processDir, 'diff.patch'), buildUnifiedJsonDiff(processRow, workingRow));
1029
+ }
1030
+ });
1031
+ const summary = {
1032
+ auto_patch_policy: options.autoPatchPolicy,
1033
+ process_count: options.processes.length,
1034
+ repair_item_count: repairPlan.length,
1035
+ decision_counts: decisionCounts,
1036
+ patched_process_count: options.apply ? patchedRows.length : 0,
1037
+ };
1038
+ if (options.apply && options.processPoolFile) {
1039
+ summary.process_pool_sync = syncProcessPoolFile(options.processPoolFile, patchedRows);
1040
+ }
1041
+ writeJsonArtifact(files.plan, repairPlan);
1042
+ writeJsonLinesArtifact(files.plan_jsonl, repairPlan);
1043
+ writeJsonLinesArtifact(files.manual_review_queue, manualQueue);
1044
+ writeJsonArtifact(files.summary, summary);
1045
+ if (options.apply) {
1046
+ writeJsonArtifact(files.patched_processes, patchedRows);
1047
+ }
1048
+ return {
1049
+ plan: repairPlan,
1050
+ manualQueue,
1051
+ summary,
1052
+ files,
1053
+ patchedRows,
1054
+ };
1055
+ }
1056
+ function runValidateStage(options) {
1057
+ const files = buildValidateStageFiles(options.outDir);
1058
+ const validator = resolveProcessValidator(options.tidasMode, options.deps);
1059
+ const originalMap = {};
1060
+ options.originalRows.forEach((row) => {
1061
+ const key = processRowKey(row);
1062
+ if (key) {
1063
+ originalMap[key] = row;
1064
+ }
1065
+ });
1066
+ const results = options.patchedRows.map((patchedRow) => {
1067
+ const key = processRowKey(patchedRow);
1068
+ return validateProcessPatch(key ? (originalMap[key] ?? null) : null, patchedRow, options.scopeIndex, validator.createProcess ?? null);
1069
+ });
1070
+ const failures = results.filter((result) => !result.ok);
1071
+ const summary = {
1072
+ patched_process_count: options.patchedRows.length,
1073
+ passed: results.length - failures.length,
1074
+ failed: failures.length,
1075
+ tidas_validation: validator.tidasValidation,
1076
+ };
1077
+ const report = {
1078
+ summary,
1079
+ results,
1080
+ };
1081
+ writeJsonArtifact(files.report, report);
1082
+ writeJsonLinesArtifact(files.failures, failures);
1083
+ return {
1084
+ summary,
1085
+ results,
1086
+ failures,
1087
+ files,
1088
+ };
1089
+ }
1090
+ export async function runFlowScanProcessFlowRefs(options, deps = {}) {
1091
+ const processesFile = assertInputFile(options.processesFile, 'FLOW_SCAN_PROCESS_FLOW_REFS_PROCESSES_FILE_REQUIRED', 'FLOW_SCAN_PROCESS_FLOW_REFS_PROCESSES_FILE_NOT_FOUND');
1092
+ const scopeFlowFiles = assertInputFiles(options.scopeFlowFiles ?? [], 'FLOW_SCAN_PROCESS_FLOW_REFS_SCOPE_FLOW_FILES_REQUIRED', 'FLOW_SCAN_PROCESS_FLOW_REFS_SCOPE_FLOW_FILE_NOT_FOUND');
1093
+ const catalogFlowFiles = options.catalogFlowFiles && options.catalogFlowFiles.length > 0
1094
+ ? assertInputFiles(options.catalogFlowFiles, 'FLOW_SCAN_PROCESS_FLOW_REFS_CATALOG_FLOW_FILES_REQUIRED', 'FLOW_SCAN_PROCESS_FLOW_REFS_CATALOG_FLOW_FILE_NOT_FOUND')
1095
+ : scopeFlowFiles;
1096
+ const aliasMapFile = options.aliasMapFile
1097
+ ? assertInputFile(options.aliasMapFile, 'FLOW_SCAN_PROCESS_FLOW_REFS_ALIAS_MAP_REQUIRED', 'FLOW_SCAN_PROCESS_FLOW_REFS_ALIAS_MAP_NOT_FOUND')
1098
+ : null;
1099
+ const outDir = assertOutDir(options.outDir);
1100
+ const now = deps.now ?? (() => new Date());
1101
+ const processes = loadRowsFromFile(processesFile);
1102
+ const scopeRows = scopeFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1103
+ const catalogRows = catalogFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1104
+ const aliasMap = aliasMapFile
1105
+ ? readJsonObjectFile(aliasMapFile, 'FLOW_SCAN_PROCESS_FLOW_REFS_ALIAS_MAP_REQUIRED', 'FLOW_SCAN_PROCESS_FLOW_REFS_ALIAS_MAP_NOT_FOUND', 'FLOW_SCAN_PROCESS_FLOW_REFS_ALIAS_MAP_INVALID')
1106
+ : {};
1107
+ const scopeIndex = buildFlowIndex(scopeRows);
1108
+ const catalogIndex = buildFlowIndex(catalogRows);
1109
+ const scanStage = runScanStage(processes, scopeIndex, catalogIndex, aliasMap, outDir, options.excludeEmergy === true);
1110
+ return {
1111
+ schema_version: 1,
1112
+ generated_at_utc: now().toISOString(),
1113
+ status: 'completed_local_flow_scan_process_flow_refs',
1114
+ processes_file: processesFile,
1115
+ scope_flow_files: scopeFlowFiles,
1116
+ catalog_flow_files: catalogFlowFiles,
1117
+ alias_map_file: aliasMapFile,
1118
+ exclude_emergy: options.excludeEmergy === true,
1119
+ out_dir: outDir,
1120
+ summary: scanStage.summary,
1121
+ files: scanStage.files,
1122
+ };
1123
+ }
1124
+ export async function runFlowPlanProcessFlowRepairs(options, deps = {}) {
1125
+ const processesFile = assertInputFile(options.processesFile, 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_PROCESSES_FILE_REQUIRED', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_PROCESSES_FILE_NOT_FOUND');
1126
+ const scopeFlowFiles = assertInputFiles(options.scopeFlowFiles ?? [], 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_SCOPE_FLOW_FILES_REQUIRED', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_SCOPE_FLOW_FILE_NOT_FOUND');
1127
+ const aliasMapFile = options.aliasMapFile
1128
+ ? assertInputFile(options.aliasMapFile, 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_ALIAS_MAP_REQUIRED', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_ALIAS_MAP_NOT_FOUND')
1129
+ : null;
1130
+ const { resolvedFile: scanFindingsFile, scanFindings } = loadOptionalScanFindings(options.scanFindingsFile ?? null, 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_SCAN_FINDINGS_REQUIRED', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_SCAN_FINDINGS_NOT_FOUND');
1131
+ const outDir = assertOutDir(options.outDir);
1132
+ const autoPatchPolicy = options.autoPatchPolicy ?? 'alias-only';
1133
+ const now = deps.now ?? (() => new Date());
1134
+ const processes = loadRowsFromFile(processesFile);
1135
+ const scopeRows = scopeFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1136
+ const aliasMap = aliasMapFile
1137
+ ? readJsonObjectFile(aliasMapFile, 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_ALIAS_MAP_REQUIRED', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_ALIAS_MAP_NOT_FOUND', 'FLOW_PLAN_PROCESS_FLOW_REPAIRS_ALIAS_MAP_INVALID')
1138
+ : {};
1139
+ const scopeIndex = buildFlowIndex(scopeRows);
1140
+ const repairStage = runRepairStage({
1141
+ processes,
1142
+ scopeIndex,
1143
+ aliasMap,
1144
+ scanFindings,
1145
+ autoPatchPolicy,
1146
+ outDir,
1147
+ apply: false,
1148
+ processPoolFile: null,
1149
+ });
1150
+ return {
1151
+ schema_version: 1,
1152
+ generated_at_utc: now().toISOString(),
1153
+ status: 'completed_local_flow_plan_process_flow_repairs',
1154
+ processes_file: processesFile,
1155
+ scope_flow_files: scopeFlowFiles,
1156
+ alias_map_file: aliasMapFile,
1157
+ scan_findings_file: scanFindingsFile,
1158
+ auto_patch_policy: autoPatchPolicy,
1159
+ out_dir: outDir,
1160
+ summary: repairStage.summary,
1161
+ files: repairStage.files,
1162
+ };
1163
+ }
1164
+ export async function runFlowApplyProcessFlowRepairs(options, deps = {}) {
1165
+ const processesFile = assertInputFile(options.processesFile, 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_PROCESSES_FILE_REQUIRED', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_PROCESSES_FILE_NOT_FOUND');
1166
+ const scopeFlowFiles = assertInputFiles(options.scopeFlowFiles ?? [], 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_SCOPE_FLOW_FILES_REQUIRED', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_SCOPE_FLOW_FILE_NOT_FOUND');
1167
+ const aliasMapFile = options.aliasMapFile
1168
+ ? assertInputFile(options.aliasMapFile, 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_ALIAS_MAP_REQUIRED', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_ALIAS_MAP_NOT_FOUND')
1169
+ : null;
1170
+ const { resolvedFile: scanFindingsFile, scanFindings } = loadOptionalScanFindings(options.scanFindingsFile ?? null, 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_SCAN_FINDINGS_REQUIRED', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_SCAN_FINDINGS_NOT_FOUND');
1171
+ const outDir = assertOutDir(options.outDir);
1172
+ const autoPatchPolicy = options.autoPatchPolicy ?? 'alias-only';
1173
+ const processPoolFile = options.processPoolFile ? path.resolve(options.processPoolFile) : null;
1174
+ const now = deps.now ?? (() => new Date());
1175
+ const processes = loadRowsFromFile(processesFile);
1176
+ const scopeRows = scopeFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1177
+ const aliasMap = aliasMapFile
1178
+ ? readJsonObjectFile(aliasMapFile, 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_ALIAS_MAP_REQUIRED', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_ALIAS_MAP_NOT_FOUND', 'FLOW_APPLY_PROCESS_FLOW_REPAIRS_ALIAS_MAP_INVALID')
1179
+ : {};
1180
+ const scopeIndex = buildFlowIndex(scopeRows);
1181
+ const repairStage = runRepairStage({
1182
+ processes,
1183
+ scopeIndex,
1184
+ aliasMap,
1185
+ scanFindings,
1186
+ autoPatchPolicy,
1187
+ outDir,
1188
+ apply: true,
1189
+ processPoolFile,
1190
+ });
1191
+ return {
1192
+ schema_version: 1,
1193
+ generated_at_utc: now().toISOString(),
1194
+ status: 'completed_local_flow_apply_process_flow_repairs',
1195
+ processes_file: processesFile,
1196
+ scope_flow_files: scopeFlowFiles,
1197
+ alias_map_file: aliasMapFile,
1198
+ scan_findings_file: scanFindingsFile,
1199
+ auto_patch_policy: autoPatchPolicy,
1200
+ process_pool_file: processPoolFile,
1201
+ out_dir: outDir,
1202
+ summary: repairStage.summary,
1203
+ files: repairStage.files,
1204
+ };
1205
+ }
1206
+ function buildReportFiles(outDir, apply) {
1207
+ return {
1208
+ report: path.join(outDir, 'flow-regen-product-report.json'),
1209
+ scan: buildScanStageFiles(path.join(outDir, 'scan')),
1210
+ repair: buildRepairStageFiles(path.join(outDir, 'repair')),
1211
+ apply: apply ? buildApplyStageFiles(path.join(outDir, 'repair-apply')) : null,
1212
+ validate: apply ? buildValidateStageFiles(path.join(outDir, 'validate')) : null,
1213
+ };
1214
+ }
1215
+ export async function runFlowRegenProduct(options, deps = {}) {
1216
+ const processesFile = assertInputFile(options.processesFile, 'FLOW_REGEN_PROCESSES_FILE_REQUIRED', 'FLOW_REGEN_PROCESSES_FILE_NOT_FOUND');
1217
+ const scopeFlowFiles = assertInputFiles(options.scopeFlowFiles ?? [], 'FLOW_REGEN_SCOPE_FLOW_FILES_REQUIRED', 'FLOW_REGEN_SCOPE_FLOW_FILE_NOT_FOUND');
1218
+ const catalogFlowFiles = options.catalogFlowFiles && options.catalogFlowFiles.length > 0
1219
+ ? assertInputFiles(options.catalogFlowFiles, 'FLOW_REGEN_CATALOG_FLOW_FILE_REQUIRED', 'FLOW_REGEN_CATALOG_FLOW_FILE_NOT_FOUND')
1220
+ : scopeFlowFiles;
1221
+ const aliasMapFile = options.aliasMapFile
1222
+ ? assertInputFile(options.aliasMapFile, 'FLOW_REGEN_ALIAS_MAP_REQUIRED', 'FLOW_REGEN_ALIAS_MAP_NOT_FOUND')
1223
+ : null;
1224
+ const outDir = assertOutDir(options.outDir);
1225
+ const processPoolFile = options.processPoolFile ? path.resolve(options.processPoolFile) : null;
1226
+ const apply = options.apply === true;
1227
+ const autoPatchPolicy = options.autoPatchPolicy ?? 'alias-only';
1228
+ const tidasMode = options.tidasMode ?? 'auto';
1229
+ const files = buildReportFiles(outDir, apply);
1230
+ const now = deps.now ?? (() => new Date());
1231
+ const processes = loadRowsFromFile(processesFile);
1232
+ const scopeRows = scopeFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1233
+ const catalogRows = catalogFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1234
+ const aliasMap = aliasMapFile
1235
+ ? readJsonObjectFile(aliasMapFile, 'FLOW_REGEN_ALIAS_MAP_REQUIRED', 'FLOW_REGEN_ALIAS_MAP_NOT_FOUND', 'FLOW_REGEN_ALIAS_MAP_INVALID')
1236
+ : {};
1237
+ const scopeIndex = buildFlowIndex(scopeRows);
1238
+ const catalogIndex = buildFlowIndex(catalogRows);
1239
+ const scanStage = runScanStage(processes, scopeIndex, catalogIndex, aliasMap, files.scan.out_dir, options.excludeEmergy === true);
1240
+ const repairStage = runRepairStage({
1241
+ processes: scanStage.filteredProcesses,
1242
+ scopeIndex,
1243
+ aliasMap,
1244
+ scanFindings: scanStage.findings,
1245
+ autoPatchPolicy,
1246
+ outDir: files.repair.out_dir,
1247
+ apply: false,
1248
+ processPoolFile: null,
1249
+ });
1250
+ const applyStage = apply
1251
+ ? runRepairStage({
1252
+ processes: scanStage.filteredProcesses,
1253
+ scopeIndex,
1254
+ aliasMap,
1255
+ scanFindings: scanStage.findings,
1256
+ autoPatchPolicy,
1257
+ outDir: files.apply.out_dir,
1258
+ apply: true,
1259
+ processPoolFile,
1260
+ })
1261
+ : null;
1262
+ const validateStage = apply && applyStage
1263
+ ? runValidateStage({
1264
+ originalRows: scanStage.filteredProcesses,
1265
+ patchedRows: applyStage.patchedRows,
1266
+ scopeIndex,
1267
+ outDir: files.validate.out_dir,
1268
+ tidasMode,
1269
+ deps,
1270
+ })
1271
+ : null;
1272
+ const report = {
1273
+ schema_version: 1,
1274
+ generated_at_utc: now().toISOString(),
1275
+ status: 'completed_local_flow_regen_product',
1276
+ mode: apply ? 'apply' : 'plan',
1277
+ processes_file: processesFile,
1278
+ scope_flow_files: scopeFlowFiles,
1279
+ catalog_flow_files: catalogFlowFiles,
1280
+ alias_map_file: aliasMapFile,
1281
+ exclude_emergy: options.excludeEmergy === true,
1282
+ auto_patch_policy: autoPatchPolicy,
1283
+ process_pool_file: processPoolFile,
1284
+ tidas_mode: tidasMode,
1285
+ out_dir: outDir,
1286
+ counts: {
1287
+ process_count_before_emergy_exclusion: scanStage.summary.process_count_before_emergy_exclusion,
1288
+ process_count: scanStage.summary.process_count,
1289
+ emergy_excluded_process_count: scanStage.summary.emergy_excluded_process_count,
1290
+ exchange_count: scanStage.summary.exchange_count,
1291
+ issue_counts: scanStage.summary.issue_counts,
1292
+ processes_with_issues: scanStage.summary.processes_with_issues,
1293
+ repair_item_count: repairStage.summary.repair_item_count,
1294
+ decision_counts: repairStage.summary.decision_counts,
1295
+ patched_process_count: applyStage?.summary.patched_process_count ?? 0,
1296
+ validation_passed_count: validateStage?.summary.passed ?? null,
1297
+ validation_failed_count: validateStage?.summary.failed ?? null,
1298
+ },
1299
+ validation: {
1300
+ enabled: apply,
1301
+ tidas_validation: validateStage?.summary.tidas_validation ?? false,
1302
+ ok: validateStage ? validateStage.summary.failed === 0 : null,
1303
+ },
1304
+ files,
1305
+ };
1306
+ writeJsonArtifact(files.report, report);
1307
+ return report;
1308
+ }
1309
+ export async function runFlowValidateProcesses(options, deps = {}) {
1310
+ const originalProcessesFile = assertInputFile(options.originalProcessesFile, 'FLOW_VALIDATE_PROCESSES_ORIGINAL_FILE_REQUIRED', 'FLOW_VALIDATE_PROCESSES_ORIGINAL_FILE_NOT_FOUND');
1311
+ const patchedProcessesFile = assertInputFile(options.patchedProcessesFile, 'FLOW_VALIDATE_PROCESSES_PATCHED_FILE_REQUIRED', 'FLOW_VALIDATE_PROCESSES_PATCHED_FILE_NOT_FOUND');
1312
+ const scopeFlowFiles = assertInputFiles(options.scopeFlowFiles ?? [], 'FLOW_VALIDATE_PROCESSES_SCOPE_FLOW_FILES_REQUIRED', 'FLOW_VALIDATE_PROCESSES_SCOPE_FLOW_FILE_NOT_FOUND');
1313
+ const outDir = assertOutDir(options.outDir);
1314
+ const tidasMode = options.tidasMode ?? 'auto';
1315
+ const now = deps.now ?? (() => new Date());
1316
+ const originalRows = loadRowsFromFile(originalProcessesFile);
1317
+ const patchedRows = loadRowsFromFile(patchedProcessesFile);
1318
+ const scopeRows = scopeFlowFiles.flatMap((filePath) => loadRowsFromFile(filePath));
1319
+ const scopeIndex = buildFlowIndex(scopeRows);
1320
+ const validateStage = runValidateStage({
1321
+ originalRows,
1322
+ patchedRows,
1323
+ scopeIndex,
1324
+ outDir,
1325
+ tidasMode,
1326
+ deps,
1327
+ });
1328
+ return {
1329
+ schema_version: 1,
1330
+ generated_at_utc: now().toISOString(),
1331
+ status: 'completed_local_flow_validate_processes',
1332
+ original_processes_file: originalProcessesFile,
1333
+ patched_processes_file: patchedProcessesFile,
1334
+ scope_flow_files: scopeFlowFiles,
1335
+ out_dir: outDir,
1336
+ tidas_mode: tidasMode,
1337
+ summary: validateStage.summary,
1338
+ files: validateStage.files,
1339
+ results: validateStage.results,
1340
+ };
1341
+ }
1342
+ export const __testInternals = {
1343
+ resolveCliRepoRoot,
1344
+ assertInputFile,
1345
+ assertInputFiles,
1346
+ assertOutDir,
1347
+ readJsonObjectFile,
1348
+ resolveLocalSdkModule,
1349
+ processDatasetFromRow,
1350
+ extractProcessIdentity,
1351
+ exchangeRecords,
1352
+ extractProcessReferenceExchange,
1353
+ extractReferenceText,
1354
+ extractProcessReferenceFlowRef,
1355
+ processRowKey,
1356
+ versionKey,
1357
+ compareVersionKeys,
1358
+ processRowSortComparator,
1359
+ buildFlowIndex,
1360
+ aliasLookup,
1361
+ buildLocalDatasetUri,
1362
+ preserveShortDescriptionShape,
1363
+ flowReferenceFromRecord,
1364
+ patchedFlowReference,
1365
+ langEntries,
1366
+ textHasEmergyKeyword,
1367
+ uniqueNonEmptyTexts,
1368
+ flowEmergyScopeDecision,
1369
+ processEmergyScopeDecision,
1370
+ filterEmergyNamedProcesses,
1371
+ classifyExchangeRef,
1372
+ buildFindingMap,
1373
+ planExchangeRepair,
1374
+ buildUnifiedJsonDiff,
1375
+ deepDiffPaths,
1376
+ pathContainsReferenceToFlow,
1377
+ safeProcessKey,
1378
+ writeRowsFile,
1379
+ mergeRowsByIdentity,
1380
+ syncProcessPoolFile,
1381
+ buildSdkCandidates,
1382
+ resolveSdkModuleFromCandidates,
1383
+ resolveProcessValidator,
1384
+ formatValidationDetails,
1385
+ evaluateProcessSdkValidation,
1386
+ validateProcessPatch,
1387
+ buildScanStageFiles,
1388
+ buildRepairStageFiles,
1389
+ buildApplyStageFiles,
1390
+ buildValidateStageFiles,
1391
+ runScanStage,
1392
+ runRepairStage,
1393
+ runValidateStage,
1394
+ buildReportFiles,
1395
+ };
1396
+ //# sourceMappingURL=flow-regen-product.js.map