@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.
- package/README.md +368 -0
- package/bin/tiangong.d.ts +1 -0
- package/bin/tiangong.js +32 -0
- package/dist/scripts/assert-full-coverage.js +77 -0
- package/dist/scripts/assert-full-coverage.js.map +1 -0
- package/dist/src/cli.js +2740 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/lib/artifacts.js +86 -0
- package/dist/src/lib/artifacts.js.map +1 -0
- package/dist/src/lib/dotenv.js +36 -0
- package/dist/src/lib/dotenv.js.map +1 -0
- package/dist/src/lib/env.js +139 -0
- package/dist/src/lib/env.js.map +1 -0
- package/dist/src/lib/errors.js +38 -0
- package/dist/src/lib/errors.js.map +1 -0
- package/dist/src/lib/flow-build-alias-map.js +299 -0
- package/dist/src/lib/flow-build-alias-map.js.map +1 -0
- package/dist/src/lib/flow-get.js +81 -0
- package/dist/src/lib/flow-get.js.map +1 -0
- package/dist/src/lib/flow-governance.js +164 -0
- package/dist/src/lib/flow-governance.js.map +1 -0
- package/dist/src/lib/flow-list.js +187 -0
- package/dist/src/lib/flow-list.js.map +1 -0
- package/dist/src/lib/flow-publish-reviewed-data.js +808 -0
- package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -0
- package/dist/src/lib/flow-publish-version.js +541 -0
- package/dist/src/lib/flow-publish-version.js.map +1 -0
- package/dist/src/lib/flow-read.js +310 -0
- package/dist/src/lib/flow-read.js.map +1 -0
- package/dist/src/lib/flow-regen-product.js +1396 -0
- package/dist/src/lib/flow-regen-product.js.map +1 -0
- package/dist/src/lib/flow-remediate.js +821 -0
- package/dist/src/lib/flow-remediate.js.map +1 -0
- package/dist/src/lib/http.js +45 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/io.js +30 -0
- package/dist/src/lib/io.js.map +1 -0
- package/dist/src/lib/kb-search.js +83 -0
- package/dist/src/lib/kb-search.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js +1328 -0
- package/dist/src/lib/lifecyclemodel-auto-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js +1556 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js +296 -0
- package/dist/src/lib/lifecyclemodel-publish-build.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js +101 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js +1152 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js +238 -0
- package/dist/src/lib/lifecyclemodel-validate-build.js.map +1 -0
- package/dist/src/lib/llm.js +383 -0
- package/dist/src/lib/llm.js.map +1 -0
- package/dist/src/lib/process-auto-build.js +717 -0
- package/dist/src/lib/process-auto-build.js.map +1 -0
- package/dist/src/lib/process-batch-build.js +409 -0
- package/dist/src/lib/process-batch-build.js.map +1 -0
- package/dist/src/lib/process-get.js +66 -0
- package/dist/src/lib/process-get.js.map +1 -0
- package/dist/src/lib/process-publish-build.js +451 -0
- package/dist/src/lib/process-publish-build.js.map +1 -0
- package/dist/src/lib/process-resume-build.js +364 -0
- package/dist/src/lib/process-resume-build.js.map +1 -0
- package/dist/src/lib/publish.js +562 -0
- package/dist/src/lib/publish.js.map +1 -0
- package/dist/src/lib/remote.js +88 -0
- package/dist/src/lib/remote.js.map +1 -0
- package/dist/src/lib/review-flow.js +1110 -0
- package/dist/src/lib/review-flow.js.map +1 -0
- package/dist/src/lib/review-lifecyclemodel.js +727 -0
- package/dist/src/lib/review-lifecyclemodel.js.map +1 -0
- package/dist/src/lib/review-process.js +702 -0
- package/dist/src/lib/review-process.js.map +1 -0
- package/dist/src/lib/run.js +106 -0
- package/dist/src/lib/run.js.map +1 -0
- package/dist/src/lib/state-lock.js +150 -0
- package/dist/src/lib/state-lock.js.map +1 -0
- package/dist/src/lib/supabase-client.js +269 -0
- package/dist/src/lib/supabase-client.js.map +1 -0
- package/dist/src/lib/supabase-json-ordered-write.js +186 -0
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -0
- package/dist/src/lib/supabase-rest.js +138 -0
- package/dist/src/lib/supabase-rest.js.map +1 -0
- package/dist/src/lib/supabase-session.js +424 -0
- package/dist/src/lib/supabase-session.js.map +1 -0
- package/dist/src/lib/tidas-sdk-package-validator.js +493 -0
- package/dist/src/lib/tidas-sdk-package-validator.js.map +1 -0
- package/dist/src/lib/unstructured.js +203 -0
- package/dist/src/lib/unstructured.js.map +1 -0
- package/dist/src/lib/user-api-key.js +75 -0
- package/dist/src/lib/user-api-key.js.map +1 -0
- package/dist/src/lib/validation.js +191 -0
- package/dist/src/lib/validation.js.map +1 -0
- package/dist/src/main.js +38 -0
- package/dist/src/main.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readJsonArtifact, writeJsonArtifact, writeJsonLinesArtifact, writeTextArtifact, } from './artifacts.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function nonEmptyString(value) {
|
|
9
|
+
if (typeof value !== 'string') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const normalized = value.trim();
|
|
13
|
+
return normalized || null;
|
|
14
|
+
}
|
|
15
|
+
function deepGet(value, pathParts) {
|
|
16
|
+
let current = value;
|
|
17
|
+
for (const part of pathParts) {
|
|
18
|
+
if (!isRecord(current)) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
current = current[part];
|
|
22
|
+
}
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
function listify(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (value === undefined || value === null) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return [value];
|
|
33
|
+
}
|
|
34
|
+
function toNumber(value) {
|
|
35
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'string' && value.trim()) {
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function toNonNegativeInteger(value) {
|
|
45
|
+
const parsed = toNumber(value);
|
|
46
|
+
if (parsed === null || !Number.isInteger(parsed) || parsed < 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
function uniqueStrings(values) {
|
|
52
|
+
return [
|
|
53
|
+
...new Set(values.filter((value) => typeof value === 'string' && value.trim().length > 0)),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
function copyJson(value) {
|
|
57
|
+
return JSON.parse(JSON.stringify(value));
|
|
58
|
+
}
|
|
59
|
+
function emptySeverityCounts() {
|
|
60
|
+
return {
|
|
61
|
+
error: 0,
|
|
62
|
+
warning: 0,
|
|
63
|
+
info: 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function severityCounts(findings) {
|
|
67
|
+
return findings.reduce((counts, finding) => {
|
|
68
|
+
counts[finding.severity] += 1;
|
|
69
|
+
return counts;
|
|
70
|
+
}, emptySeverityCounts());
|
|
71
|
+
}
|
|
72
|
+
function readRequiredJsonObject(filePath, missingCode, invalidCode, label) {
|
|
73
|
+
if (!existsSync(filePath)) {
|
|
74
|
+
throw new CliError(`Required lifecyclemodel ${label} artifact not found: ${filePath}`, {
|
|
75
|
+
code: missingCode,
|
|
76
|
+
exitCode: 2,
|
|
77
|
+
details: { filePath, label },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const value = readJsonArtifact(filePath);
|
|
81
|
+
if (!isRecord(value)) {
|
|
82
|
+
throw new CliError(`Expected lifecyclemodel ${label} artifact JSON object: ${filePath}`, {
|
|
83
|
+
code: invalidCode,
|
|
84
|
+
exitCode: 2,
|
|
85
|
+
details: { filePath, label },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function readOptionalJsonObject(filePath, invalidCode, label) {
|
|
91
|
+
if (!existsSync(filePath)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const value = readJsonArtifact(filePath);
|
|
95
|
+
if (!isRecord(value)) {
|
|
96
|
+
throw new CliError(`Expected lifecyclemodel ${label} artifact JSON object: ${filePath}`, {
|
|
97
|
+
code: invalidCode,
|
|
98
|
+
exitCode: 2,
|
|
99
|
+
details: { filePath, label },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
function readOptionalJsonArray(filePath, invalidCode, label) {
|
|
105
|
+
if (!existsSync(filePath)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const value = readJsonArtifact(filePath);
|
|
109
|
+
if (!Array.isArray(value)) {
|
|
110
|
+
throw new CliError(`Expected lifecyclemodel ${label} artifact JSON array: ${filePath}`, {
|
|
111
|
+
code: invalidCode,
|
|
112
|
+
exitCode: 2,
|
|
113
|
+
details: { filePath, label },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
function buildLayout(runRoot, outDir) {
|
|
119
|
+
const runId = path.basename(runRoot);
|
|
120
|
+
return {
|
|
121
|
+
runId,
|
|
122
|
+
runRoot,
|
|
123
|
+
outDir,
|
|
124
|
+
modelsDir: path.join(runRoot, 'models'),
|
|
125
|
+
reportsDir: path.join(runRoot, 'reports'),
|
|
126
|
+
manifestsDir: path.join(runRoot, 'manifests'),
|
|
127
|
+
runManifestPath: path.join(runRoot, 'manifests', 'run-manifest.json'),
|
|
128
|
+
invocationIndexPath: path.join(runRoot, 'manifests', 'invocation-index.json'),
|
|
129
|
+
validationReportPath: path.join(runRoot, 'reports', 'lifecyclemodel-validate-build-report.json'),
|
|
130
|
+
modelSummariesPath: path.join(outDir, 'model_summaries.jsonl'),
|
|
131
|
+
findingsPath: path.join(outDir, 'findings.jsonl'),
|
|
132
|
+
summaryPath: path.join(outDir, 'lifecyclemodel_review_summary.json'),
|
|
133
|
+
reviewZhPath: path.join(outDir, 'lifecyclemodel_review_zh.md'),
|
|
134
|
+
reviewEnPath: path.join(outDir, 'lifecyclemodel_review_en.md'),
|
|
135
|
+
timingPath: path.join(outDir, 'lifecyclemodel_review_timing.md'),
|
|
136
|
+
reportPath: path.join(outDir, 'lifecyclemodel_review_report.json'),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function resolveLayout(options) {
|
|
140
|
+
const runDir = nonEmptyString(options.runDir);
|
|
141
|
+
if (!runDir) {
|
|
142
|
+
throw new CliError('Missing required --run-dir for review lifecyclemodel.', {
|
|
143
|
+
code: 'LIFECYCLEMODEL_REVIEW_RUN_DIR_REQUIRED',
|
|
144
|
+
exitCode: 2,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const outDir = nonEmptyString(options.outDir);
|
|
148
|
+
if (!outDir) {
|
|
149
|
+
throw new CliError('Missing required --out-dir for review lifecyclemodel.', {
|
|
150
|
+
code: 'LIFECYCLEMODEL_REVIEW_OUT_DIR_REQUIRED',
|
|
151
|
+
exitCode: 2,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return buildLayout(path.resolve(runDir), path.resolve(outDir));
|
|
155
|
+
}
|
|
156
|
+
function ensureRunRootExists(layout) {
|
|
157
|
+
if (!existsSync(layout.runRoot)) {
|
|
158
|
+
throw new CliError(`lifecyclemodel review run root not found: ${layout.runRoot}`, {
|
|
159
|
+
code: 'LIFECYCLEMODEL_REVIEW_RUN_NOT_FOUND',
|
|
160
|
+
exitCode: 2,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function readRequiredRunManifest(layout) {
|
|
165
|
+
const manifest = readRequiredJsonObject(layout.runManifestPath, 'LIFECYCLEMODEL_REVIEW_RUN_MANIFEST_MISSING', 'LIFECYCLEMODEL_REVIEW_RUN_MANIFEST_INVALID', 'run-manifest');
|
|
166
|
+
const manifestRunId = nonEmptyString(manifest.runId);
|
|
167
|
+
if (manifestRunId && manifestRunId !== layout.runId) {
|
|
168
|
+
throw new CliError(`lifecyclemodel review run manifest runId mismatch: ${layout.runManifestPath}`, {
|
|
169
|
+
code: 'LIFECYCLEMODEL_REVIEW_RUN_MANIFEST_MISMATCH',
|
|
170
|
+
exitCode: 2,
|
|
171
|
+
details: {
|
|
172
|
+
expected: layout.runId,
|
|
173
|
+
actual: manifestRunId,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return manifest;
|
|
178
|
+
}
|
|
179
|
+
function readInvocationIndex(layout) {
|
|
180
|
+
if (!existsSync(layout.invocationIndexPath)) {
|
|
181
|
+
return {
|
|
182
|
+
schema_version: 1,
|
|
183
|
+
invocations: [],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const value = readJsonArtifact(layout.invocationIndexPath);
|
|
187
|
+
if (!isRecord(value)) {
|
|
188
|
+
throw new CliError(`Expected lifecyclemodel review invocation index JSON object: ${layout.invocationIndexPath}`, {
|
|
189
|
+
code: 'LIFECYCLEMODEL_REVIEW_INVOCATION_INDEX_INVALID',
|
|
190
|
+
exitCode: 2,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (value.invocations === undefined) {
|
|
194
|
+
return {
|
|
195
|
+
schema_version: 1,
|
|
196
|
+
invocations: [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (!Array.isArray(value.invocations)) {
|
|
200
|
+
throw new CliError(`Expected lifecyclemodel review invocation index to contain an invocations array: ${layout.invocationIndexPath}`, {
|
|
201
|
+
code: 'LIFECYCLEMODEL_REVIEW_INVOCATION_INDEX_INVALID',
|
|
202
|
+
exitCode: 2,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
function discoverModelEntries(layout) {
|
|
208
|
+
const runNames = existsSync(layout.modelsDir) ? readdirSync(layout.modelsDir).sort() : [];
|
|
209
|
+
const entries = runNames.flatMap((runName) => {
|
|
210
|
+
const lifecyclemodelsDir = path.join(layout.modelsDir, runName, 'tidas_bundle', 'lifecyclemodels');
|
|
211
|
+
if (!existsSync(lifecyclemodelsDir)) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const modelFiles = readdirSync(lifecyclemodelsDir)
|
|
215
|
+
.filter((entry) => entry.endsWith('.json'))
|
|
216
|
+
.sort()
|
|
217
|
+
.map((entry) => path.join(lifecyclemodelsDir, entry));
|
|
218
|
+
if (modelFiles.length === 0) {
|
|
219
|
+
throw new CliError(`lifecyclemodel review found a bundle without lifecyclemodel JSON files: ${lifecyclemodelsDir}`, {
|
|
220
|
+
code: 'LIFECYCLEMODEL_REVIEW_MODELS_EMPTY',
|
|
221
|
+
exitCode: 2,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
runName,
|
|
227
|
+
modelFiles,
|
|
228
|
+
summaryPath: path.join(layout.modelsDir, runName, 'summary.json'),
|
|
229
|
+
connectionsPath: path.join(layout.modelsDir, runName, 'connections.json'),
|
|
230
|
+
processCatalogPath: path.join(layout.modelsDir, runName, 'process-catalog.json'),
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
});
|
|
234
|
+
if (entries.length === 0) {
|
|
235
|
+
throw new CliError(`lifecyclemodel review run does not contain any model bundles: ${layout.modelsDir}`, {
|
|
236
|
+
code: 'LIFECYCLEMODEL_REVIEW_MODELS_NOT_FOUND',
|
|
237
|
+
exitCode: 2,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return entries;
|
|
241
|
+
}
|
|
242
|
+
function normalizeValidationIssue(raw) {
|
|
243
|
+
if (!isRecord(raw)) {
|
|
244
|
+
return {
|
|
245
|
+
issue_code: 'validation_issue',
|
|
246
|
+
severity: 'error',
|
|
247
|
+
category: 'unknown',
|
|
248
|
+
file_path: '<unknown>',
|
|
249
|
+
message: String(raw),
|
|
250
|
+
location: '<root>',
|
|
251
|
+
context: {},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const severity = raw.severity === 'warning' || raw.severity === 'info' || raw.severity === 'error'
|
|
255
|
+
? raw.severity
|
|
256
|
+
: 'error';
|
|
257
|
+
return {
|
|
258
|
+
issue_code: nonEmptyString(raw.issue_code) ?? 'validation_issue',
|
|
259
|
+
severity,
|
|
260
|
+
category: nonEmptyString(raw.category) ?? 'unknown',
|
|
261
|
+
file_path: nonEmptyString(raw.file_path) ?? '<unknown>',
|
|
262
|
+
message: nonEmptyString(raw.message) ?? JSON.stringify(raw),
|
|
263
|
+
location: nonEmptyString(raw.location) ?? '<root>',
|
|
264
|
+
context: isRecord(raw.context) ? raw.context : {},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function collectValidationIssues(validation) {
|
|
268
|
+
const executionReports = Array.isArray(validation.reports) ? validation.reports : [];
|
|
269
|
+
const seen = new Set();
|
|
270
|
+
const issues = [];
|
|
271
|
+
executionReports.forEach((report) => {
|
|
272
|
+
if (!isRecord(report) || !isRecord(report.report)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const rawIssues = Array.isArray(report.report.issues) ? report.report.issues : [];
|
|
276
|
+
rawIssues.forEach((rawIssue) => {
|
|
277
|
+
const issue = normalizeValidationIssue(rawIssue);
|
|
278
|
+
const key = JSON.stringify([
|
|
279
|
+
issue.issue_code,
|
|
280
|
+
issue.severity,
|
|
281
|
+
issue.category,
|
|
282
|
+
issue.file_path,
|
|
283
|
+
issue.message,
|
|
284
|
+
issue.location,
|
|
285
|
+
]);
|
|
286
|
+
if (seen.has(key)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
seen.add(key);
|
|
290
|
+
issues.push(issue);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
return issues;
|
|
294
|
+
}
|
|
295
|
+
function readValidationAggregate(layout) {
|
|
296
|
+
const aggregate = readOptionalJsonObject(layout.validationReportPath, 'LIFECYCLEMODEL_REVIEW_VALIDATION_REPORT_INVALID', 'validate-build report');
|
|
297
|
+
if (!aggregate) {
|
|
298
|
+
return {
|
|
299
|
+
ok: null,
|
|
300
|
+
reportPath: null,
|
|
301
|
+
modelReports: new Map(),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (aggregate.model_reports !== undefined && !Array.isArray(aggregate.model_reports)) {
|
|
305
|
+
throw new CliError(`Expected lifecyclemodel validate-build report to contain a model_reports array: ${layout.validationReportPath}`, {
|
|
306
|
+
code: 'LIFECYCLEMODEL_REVIEW_VALIDATION_REPORT_INVALID',
|
|
307
|
+
exitCode: 2,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
const modelReports = new Map();
|
|
311
|
+
for (const rawEntry of Array.isArray(aggregate.model_reports) ? aggregate.model_reports : []) {
|
|
312
|
+
if (!isRecord(rawEntry)) {
|
|
313
|
+
throw new CliError(`Expected lifecyclemodel validate-build model report entry JSON object: ${layout.validationReportPath}`, {
|
|
314
|
+
code: 'LIFECYCLEMODEL_REVIEW_VALIDATION_REPORT_INVALID',
|
|
315
|
+
exitCode: 2,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const runName = nonEmptyString(rawEntry.run_name);
|
|
319
|
+
if (!runName || !isRecord(rawEntry.validation)) {
|
|
320
|
+
throw new CliError(`Expected lifecyclemodel validate-build model report entry to contain run_name and validation: ${layout.validationReportPath}`, {
|
|
321
|
+
code: 'LIFECYCLEMODEL_REVIEW_VALIDATION_REPORT_INVALID',
|
|
322
|
+
exitCode: 2,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const validation = rawEntry.validation;
|
|
326
|
+
const executionReports = Array.isArray(validation.reports) ? validation.reports : [];
|
|
327
|
+
modelReports.set(runName, {
|
|
328
|
+
ok: typeof validation.ok === 'boolean' ? validation.ok : null,
|
|
329
|
+
reportFile: nonEmptyString(rawEntry.report_file),
|
|
330
|
+
engineCount: executionReports.length,
|
|
331
|
+
issues: collectValidationIssues(validation),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
ok: typeof aggregate.ok === 'boolean' ? aggregate.ok : null,
|
|
336
|
+
reportPath: layout.validationReportPath,
|
|
337
|
+
modelReports,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function modelRoot(value) {
|
|
341
|
+
return isRecord(value.lifeCycleModelDataSet)
|
|
342
|
+
? value.lifeCycleModelDataSet
|
|
343
|
+
: value;
|
|
344
|
+
}
|
|
345
|
+
function readModelFileReviewInfo(filePath) {
|
|
346
|
+
const payload = readJsonArtifact(filePath);
|
|
347
|
+
if (!isRecord(payload)) {
|
|
348
|
+
throw new CliError(`Expected lifecyclemodel review payload JSON object: ${filePath}`, {
|
|
349
|
+
code: 'LIFECYCLEMODEL_REVIEW_MODEL_INVALID',
|
|
350
|
+
exitCode: 2,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
const root = modelRoot(payload);
|
|
354
|
+
const processInstances = listify(deepGet(root, ['lifeCycleModelInformation', 'technology', 'processes', 'processInstance'])).filter(isRecord);
|
|
355
|
+
const zeroMultiplicationFactorCount = processInstances.filter((instance) => toNumber(instance['@multiplicationFactor']) === 0).length;
|
|
356
|
+
return {
|
|
357
|
+
modelFile: filePath,
|
|
358
|
+
modelUuid: nonEmptyString(deepGet(root, ['lifeCycleModelInformation', 'dataSetInformation', 'common:UUID'])) ?? path.basename(filePath),
|
|
359
|
+
modelVersion: nonEmptyString(deepGet(root, [
|
|
360
|
+
'administrativeInformation',
|
|
361
|
+
'publicationAndOwnership',
|
|
362
|
+
'common:dataSetVersion',
|
|
363
|
+
])) ?? 'unknown',
|
|
364
|
+
referenceProcessInternalId: nonEmptyString(deepGet(root, [
|
|
365
|
+
'lifeCycleModelInformation',
|
|
366
|
+
'quantitativeReference',
|
|
367
|
+
'referenceToReferenceProcess',
|
|
368
|
+
])),
|
|
369
|
+
resultingProcessUuid: nonEmptyString(deepGet(root, [
|
|
370
|
+
'lifeCycleModelInformation',
|
|
371
|
+
'dataSetInformation',
|
|
372
|
+
'referenceToResultingProcess',
|
|
373
|
+
'@refObjectId',
|
|
374
|
+
])),
|
|
375
|
+
processInstanceCount: processInstances.length,
|
|
376
|
+
zeroMultiplicationFactorCount,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function makeFinding(runName, modelFile, severity, ruleId, source, message, evidence) {
|
|
380
|
+
return {
|
|
381
|
+
run_name: runName,
|
|
382
|
+
model_file: modelFile,
|
|
383
|
+
severity,
|
|
384
|
+
rule_id: ruleId,
|
|
385
|
+
source,
|
|
386
|
+
message,
|
|
387
|
+
evidence,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function buildModelReview(entry, validationAggregate) {
|
|
391
|
+
const summaryArtifact = readOptionalJsonObject(entry.summaryPath, 'LIFECYCLEMODEL_REVIEW_SUMMARY_INVALID', 'summary');
|
|
392
|
+
const connections = readOptionalJsonArray(entry.connectionsPath, 'LIFECYCLEMODEL_REVIEW_CONNECTIONS_INVALID', 'connections');
|
|
393
|
+
const processCatalog = readOptionalJsonArray(entry.processCatalogPath, 'LIFECYCLEMODEL_REVIEW_PROCESS_CATALOG_INVALID', 'process-catalog');
|
|
394
|
+
const modelFileInfos = entry.modelFiles.map((modelFile) => readModelFileReviewInfo(modelFile));
|
|
395
|
+
const validation = validationAggregate.modelReports.get(entry.runName) ?? {
|
|
396
|
+
ok: null,
|
|
397
|
+
reportFile: null,
|
|
398
|
+
engineCount: 0,
|
|
399
|
+
issues: [],
|
|
400
|
+
};
|
|
401
|
+
const findings = [];
|
|
402
|
+
validation.issues.forEach((issue) => {
|
|
403
|
+
findings.push(makeFinding(entry.runName, issue.file_path, issue.severity, `validation:${issue.issue_code}`, 'validation', issue.message, {
|
|
404
|
+
category: issue.category,
|
|
405
|
+
location: issue.location,
|
|
406
|
+
context: issue.context,
|
|
407
|
+
}));
|
|
408
|
+
});
|
|
409
|
+
const modelUuids = uniqueStrings(modelFileInfos.map((info) => info.modelUuid));
|
|
410
|
+
const modelVersions = uniqueStrings(modelFileInfos.map((info) => info.modelVersion));
|
|
411
|
+
const resultingProcessUuids = uniqueStrings(modelFileInfos.map((info) => info.resultingProcessUuid));
|
|
412
|
+
const referenceProcessInternalIds = uniqueStrings(modelFileInfos.map((info) => info.referenceProcessInternalId));
|
|
413
|
+
const referenceProcessUuids = uniqueStrings([
|
|
414
|
+
nonEmptyString(summaryArtifact?.reference_process_uuid),
|
|
415
|
+
]);
|
|
416
|
+
const processInstanceCount = modelFileInfos.reduce((sum, info) => sum + info.processInstanceCount, 0);
|
|
417
|
+
const zeroMultiplicationFactorCount = modelFileInfos.reduce((sum, info) => sum + info.zeroMultiplicationFactorCount, 0);
|
|
418
|
+
const summaryProcessCount = toNonNegativeInteger(summaryArtifact?.process_count);
|
|
419
|
+
const summaryEdgeCount = toNonNegativeInteger(summaryArtifact?.edge_count);
|
|
420
|
+
const multiplicationFactors = isRecord(summaryArtifact?.multiplication_factors)
|
|
421
|
+
? summaryArtifact?.multiplication_factors
|
|
422
|
+
: null;
|
|
423
|
+
const multiplicationFactorCount = multiplicationFactors
|
|
424
|
+
? Object.keys(multiplicationFactors).length
|
|
425
|
+
: 0;
|
|
426
|
+
if (!summaryArtifact) {
|
|
427
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_model_summary', 'review', 'Model bundle is missing summary.json generated by lifecyclemodel auto-build.', {
|
|
428
|
+
summary_path: entry.summaryPath,
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
if (!connections) {
|
|
432
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_connections_artifact', 'review', 'Model bundle is missing connections.json generated by lifecyclemodel auto-build.', {
|
|
433
|
+
connections_path: entry.connectionsPath,
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
if (!processCatalog) {
|
|
437
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_process_catalog', 'review', 'Model bundle is missing process-catalog.json generated by lifecyclemodel auto-build.', {
|
|
438
|
+
process_catalog_path: entry.processCatalogPath,
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
if (referenceProcessUuids.length === 0) {
|
|
442
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_reference_process_uuid', 'review', 'Review could not resolve reference_process_uuid from summary.json.', {
|
|
443
|
+
summary_path: summaryArtifact ? entry.summaryPath : null,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
if (resultingProcessUuids.length === 0) {
|
|
447
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_resulting_process_ref', 'review', 'Review could not resolve referenceToResultingProcess from the lifecyclemodel payload.', {
|
|
448
|
+
model_files: entry.modelFiles,
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
if (processInstanceCount === 0) {
|
|
452
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'error', 'empty_process_instances', 'review', 'Lifecyclemodel payload does not contain any processInstance entries.', {
|
|
453
|
+
model_files: entry.modelFiles,
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
if (summaryProcessCount !== null && summaryProcessCount !== processInstanceCount) {
|
|
457
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'process_count_mismatch', 'review', 'summary.json process_count does not match the number of processInstance entries in the payload.', {
|
|
458
|
+
summary_process_count: summaryProcessCount,
|
|
459
|
+
process_instance_count: processInstanceCount,
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
if (connections && summaryEdgeCount !== null && connections.length !== summaryEdgeCount) {
|
|
463
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'edge_count_mismatch', 'review', 'summary.json edge_count does not match the connections.json row count.', {
|
|
464
|
+
summary_edge_count: summaryEdgeCount,
|
|
465
|
+
connection_count: connections.length,
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
if (processCatalog && processCatalog.length !== processInstanceCount) {
|
|
469
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'process_catalog_count_mismatch', 'review', 'process-catalog.json row count does not match the number of processInstance entries in the payload.', {
|
|
470
|
+
process_catalog_count: processCatalog.length,
|
|
471
|
+
process_instance_count: processInstanceCount,
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
if (summaryArtifact &&
|
|
475
|
+
multiplicationFactors &&
|
|
476
|
+
multiplicationFactorCount !== processInstanceCount) {
|
|
477
|
+
findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'multiplication_factor_count_mismatch', 'review', 'summary.json multiplication_factors count does not match the number of processInstance entries in the payload.', {
|
|
478
|
+
multiplication_factor_count: multiplicationFactorCount,
|
|
479
|
+
process_instance_count: processInstanceCount,
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
const counts = severityCounts(findings);
|
|
483
|
+
return {
|
|
484
|
+
summary: {
|
|
485
|
+
run_name: entry.runName,
|
|
486
|
+
model_files: entry.modelFiles,
|
|
487
|
+
model_uuids: modelUuids,
|
|
488
|
+
model_versions: modelVersions,
|
|
489
|
+
reference_process_uuids: referenceProcessUuids,
|
|
490
|
+
reference_process_internal_ids: referenceProcessInternalIds,
|
|
491
|
+
resulting_process_uuids: resultingProcessUuids,
|
|
492
|
+
summary_process_count: summaryProcessCount,
|
|
493
|
+
process_instance_count: processInstanceCount,
|
|
494
|
+
summary_edge_count: summaryEdgeCount,
|
|
495
|
+
connection_count: connections ? connections.length : null,
|
|
496
|
+
process_catalog_count: processCatalog ? processCatalog.length : null,
|
|
497
|
+
multiplication_factor_count: multiplicationFactorCount,
|
|
498
|
+
zero_multiplication_factor_count: zeroMultiplicationFactorCount,
|
|
499
|
+
validation: {
|
|
500
|
+
available: validationAggregate.reportPath !== null,
|
|
501
|
+
ok: validation.ok,
|
|
502
|
+
report_file: validation.reportFile,
|
|
503
|
+
engine_count: validation.engineCount,
|
|
504
|
+
issue_count: validation.issues.length,
|
|
505
|
+
},
|
|
506
|
+
artifacts: {
|
|
507
|
+
summary: summaryArtifact ? entry.summaryPath : null,
|
|
508
|
+
connections: connections ? entry.connectionsPath : null,
|
|
509
|
+
process_catalog: processCatalog ? entry.processCatalogPath : null,
|
|
510
|
+
},
|
|
511
|
+
finding_count: findings.length,
|
|
512
|
+
severity_counts: counts,
|
|
513
|
+
},
|
|
514
|
+
findings,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function buildInvocationIndex(layout, invocationIndex, options, now) {
|
|
518
|
+
const priorInvocations = Array.isArray(invocationIndex.invocations)
|
|
519
|
+
? [...invocationIndex.invocations]
|
|
520
|
+
: [];
|
|
521
|
+
const command = [
|
|
522
|
+
'review',
|
|
523
|
+
'lifecyclemodel',
|
|
524
|
+
'--run-dir',
|
|
525
|
+
options.runDir,
|
|
526
|
+
'--out-dir',
|
|
527
|
+
options.outDir,
|
|
528
|
+
];
|
|
529
|
+
if (nonEmptyString(options.logicVersion)) {
|
|
530
|
+
command.push('--logic-version', options.logicVersion);
|
|
531
|
+
}
|
|
532
|
+
if (nonEmptyString(options.startTs)) {
|
|
533
|
+
command.push('--start-ts', options.startTs);
|
|
534
|
+
}
|
|
535
|
+
if (nonEmptyString(options.endTs)) {
|
|
536
|
+
command.push('--end-ts', options.endTs);
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
...invocationIndex,
|
|
540
|
+
schema_version: typeof invocationIndex.schema_version === 'number' ? invocationIndex.schema_version : 1,
|
|
541
|
+
invocations: [
|
|
542
|
+
...priorInvocations,
|
|
543
|
+
{
|
|
544
|
+
command,
|
|
545
|
+
cwd: options.cwd ?? process.cwd(),
|
|
546
|
+
created_at: now.toISOString(),
|
|
547
|
+
run_id: layout.runId,
|
|
548
|
+
run_root: layout.runRoot,
|
|
549
|
+
report_path: layout.reportPath,
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function buildNextActions(layout, validationAggregate) {
|
|
555
|
+
return [
|
|
556
|
+
`inspect: ${layout.findingsPath}`,
|
|
557
|
+
validationAggregate.reportPath
|
|
558
|
+
? `inspect: ${validationAggregate.reportPath}`
|
|
559
|
+
: `run: tiangong lifecyclemodel validate-build --run-dir ${layout.runRoot}`,
|
|
560
|
+
`run: tiangong lifecyclemodel publish-build --run-dir ${layout.runRoot}`,
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
function renderZhReview(options) {
|
|
564
|
+
const lines = [
|
|
565
|
+
'# lifecyclemodel_review_zh\n',
|
|
566
|
+
`- run_id: \`${options.runId}\`\n`,
|
|
567
|
+
`- logic_version: \`${options.logicVersion}\`\n`,
|
|
568
|
+
`- run_root: \`${options.runRoot}\`\n`,
|
|
569
|
+
'\n## 总览\n',
|
|
570
|
+
`- model bundle 数量: **${options.modelSummaries.length}**\n`,
|
|
571
|
+
`- findings 数量: **${options.findings.length}**\n`,
|
|
572
|
+
`- validation report: \`${options.validation.available ? 'available' : 'missing'}\`\n`,
|
|
573
|
+
];
|
|
574
|
+
lines.push('\n## 模型摘要\n|run name|model uuid 数|processInstance|connections|validation issues|findings|\n|---|---:|---:|---:|---:|---:|\n');
|
|
575
|
+
options.modelSummaries.forEach((summary) => {
|
|
576
|
+
lines.push(`|${summary.run_name}|${summary.model_uuids.length}|${summary.process_instance_count}|${summary.connection_count ?? 0}|${summary.validation.issue_count}|${summary.finding_count}|\n`);
|
|
577
|
+
});
|
|
578
|
+
lines.push('\n## Findings\n');
|
|
579
|
+
if (options.findings.length === 0) {
|
|
580
|
+
lines.push('- 未发现新的 lifecyclemodel review findings。\n');
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
lines.push('\n|run name|severity|source|rule id|message|\n|---|---|---|---|---|\n');
|
|
584
|
+
options.findings.slice(0, 200).forEach((finding) => {
|
|
585
|
+
lines.push(`|${finding.run_name}|${finding.severity}|${finding.source}|${finding.rule_id}|${finding.message.replace(/\|/gu, '/')}|\n`);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
lines.push('\n## 说明\n', '- 当前 review lifecyclemodel 保持 local-first / artifact-first,只读取现有 build run 与 validate-build 产物。\n', '- 当前命令不引入 Python、LangGraph 或 skill 私有 review runtime。\n');
|
|
589
|
+
return lines.join('');
|
|
590
|
+
}
|
|
591
|
+
function renderEnReview(options) {
|
|
592
|
+
const lines = [
|
|
593
|
+
'# lifecyclemodel_review_en\n',
|
|
594
|
+
`- run_id: \`${options.runId}\`\n`,
|
|
595
|
+
`- logic_version: \`${options.logicVersion}\`\n`,
|
|
596
|
+
`- run_root: \`${options.runRoot}\`\n`,
|
|
597
|
+
'\n## Summary\n',
|
|
598
|
+
`- model bundles: **${options.modelSummaries.length}**\n`,
|
|
599
|
+
`- findings: **${options.findings.length}**\n`,
|
|
600
|
+
`- validation report: \`${options.validation.available ? 'available' : 'missing'}\`\n`,
|
|
601
|
+
];
|
|
602
|
+
lines.push('\n## Model overview\n|run name|model uuids|process instances|connections|validation issues|findings|\n|---|---:|---:|---:|---:|---:|\n');
|
|
603
|
+
options.modelSummaries.forEach((summary) => {
|
|
604
|
+
lines.push(`|${summary.run_name}|${summary.model_uuids.length}|${summary.process_instance_count}|${summary.connection_count ?? 0}|${summary.validation.issue_count}|${summary.finding_count}|\n`);
|
|
605
|
+
});
|
|
606
|
+
if (options.findings.length > 0) {
|
|
607
|
+
lines.push('\n## Findings\n');
|
|
608
|
+
options.findings.slice(0, 100).forEach((finding) => {
|
|
609
|
+
lines.push(`- [${finding.severity}] ${finding.run_name}: ${finding.message}\n`);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
return lines.join('');
|
|
613
|
+
}
|
|
614
|
+
function renderTiming(options) {
|
|
615
|
+
const lines = ['# lifecyclemodel_review_timing\n', `- run_id: \`${options.runId}\`\n`];
|
|
616
|
+
if (options.startTs && options.endTs) {
|
|
617
|
+
const started = Date.parse(options.startTs);
|
|
618
|
+
const ended = Date.parse(options.endTs);
|
|
619
|
+
if (!Number.isFinite(started) || !Number.isFinite(ended)) {
|
|
620
|
+
throw new CliError('Expected --start-ts and --end-ts to be valid ISO timestamps.', {
|
|
621
|
+
code: 'LIFECYCLEMODEL_REVIEW_INVALID_TIMESTAMP',
|
|
622
|
+
exitCode: 2,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
lines.push(`- start: \`${options.startTs}\`\n`);
|
|
626
|
+
lines.push(`- end: \`${options.endTs}\`\n`);
|
|
627
|
+
lines.push(`- total elapsed: **${((ended - started) / 60_000).toFixed(2)} min**\n`);
|
|
628
|
+
}
|
|
629
|
+
lines.push(`- model bundles reviewed: \`${options.modelCount}\`\n`);
|
|
630
|
+
lines.push('- major time consumers: model JSON parsing, validation issue aggregation, artifact checks.\n');
|
|
631
|
+
return lines.join('');
|
|
632
|
+
}
|
|
633
|
+
export async function runLifecyclemodelReview(options) {
|
|
634
|
+
const layout = resolveLayout(options);
|
|
635
|
+
ensureRunRootExists(layout);
|
|
636
|
+
readRequiredRunManifest(layout);
|
|
637
|
+
const invocationIndex = readInvocationIndex(layout);
|
|
638
|
+
const validationAggregate = readValidationAggregate(layout);
|
|
639
|
+
const modelEntries = discoverModelEntries(layout);
|
|
640
|
+
const logicVersion = options.logicVersion?.trim() || 'lifecyclemodel-review-v1.0';
|
|
641
|
+
const now = options.now ?? (() => new Date());
|
|
642
|
+
const generatedAt = now();
|
|
643
|
+
const reviewedModels = modelEntries.map((entry) => buildModelReview(entry, validationAggregate));
|
|
644
|
+
const modelSummaries = reviewedModels.map((model) => model.summary);
|
|
645
|
+
const findings = reviewedModels.flatMap((model) => model.findings);
|
|
646
|
+
const report = {
|
|
647
|
+
schema_version: 1,
|
|
648
|
+
generated_at_utc: generatedAt.toISOString(),
|
|
649
|
+
status: 'completed_local_lifecyclemodel_review',
|
|
650
|
+
run_id: layout.runId,
|
|
651
|
+
run_root: layout.runRoot,
|
|
652
|
+
out_dir: layout.outDir,
|
|
653
|
+
logic_version: logicVersion,
|
|
654
|
+
model_count: modelSummaries.length,
|
|
655
|
+
finding_count: findings.length,
|
|
656
|
+
severity_counts: severityCounts(findings),
|
|
657
|
+
validation: {
|
|
658
|
+
available: validationAggregate.reportPath !== null,
|
|
659
|
+
ok: validationAggregate.ok,
|
|
660
|
+
report: validationAggregate.reportPath,
|
|
661
|
+
},
|
|
662
|
+
files: {
|
|
663
|
+
run_manifest: layout.runManifestPath,
|
|
664
|
+
invocation_index: layout.invocationIndexPath,
|
|
665
|
+
validation_report: validationAggregate.reportPath,
|
|
666
|
+
model_summaries: layout.modelSummariesPath,
|
|
667
|
+
findings: layout.findingsPath,
|
|
668
|
+
summary: layout.summaryPath,
|
|
669
|
+
review_zh: layout.reviewZhPath,
|
|
670
|
+
review_en: layout.reviewEnPath,
|
|
671
|
+
timing: layout.timingPath,
|
|
672
|
+
report: layout.reportPath,
|
|
673
|
+
},
|
|
674
|
+
model_summaries: modelSummaries,
|
|
675
|
+
next_actions: buildNextActions(layout, validationAggregate),
|
|
676
|
+
};
|
|
677
|
+
writeJsonArtifact(layout.invocationIndexPath, buildInvocationIndex(layout, invocationIndex, options, generatedAt));
|
|
678
|
+
writeJsonLinesArtifact(layout.modelSummariesPath, modelSummaries);
|
|
679
|
+
writeJsonLinesArtifact(layout.findingsPath, findings);
|
|
680
|
+
writeJsonArtifact(layout.summaryPath, {
|
|
681
|
+
run_id: report.run_id,
|
|
682
|
+
logic_version: report.logic_version,
|
|
683
|
+
model_count: report.model_count,
|
|
684
|
+
finding_count: report.finding_count,
|
|
685
|
+
severity_counts: copyJson(report.severity_counts),
|
|
686
|
+
validation: copyJson(report.validation),
|
|
687
|
+
});
|
|
688
|
+
writeTextArtifact(layout.reviewZhPath, renderZhReview({
|
|
689
|
+
runId: report.run_id,
|
|
690
|
+
logicVersion: report.logic_version,
|
|
691
|
+
runRoot: report.run_root,
|
|
692
|
+
modelSummaries,
|
|
693
|
+
findings,
|
|
694
|
+
validation: report.validation,
|
|
695
|
+
}));
|
|
696
|
+
writeTextArtifact(layout.reviewEnPath, renderEnReview({
|
|
697
|
+
runId: report.run_id,
|
|
698
|
+
logicVersion: report.logic_version,
|
|
699
|
+
runRoot: report.run_root,
|
|
700
|
+
modelSummaries,
|
|
701
|
+
findings,
|
|
702
|
+
validation: report.validation,
|
|
703
|
+
}));
|
|
704
|
+
writeTextArtifact(layout.timingPath, renderTiming({
|
|
705
|
+
runId: report.run_id,
|
|
706
|
+
startTs: options.startTs,
|
|
707
|
+
endTs: options.endTs,
|
|
708
|
+
modelCount: report.model_count,
|
|
709
|
+
}));
|
|
710
|
+
writeJsonArtifact(layout.reportPath, report);
|
|
711
|
+
return report;
|
|
712
|
+
}
|
|
713
|
+
export const __testInternals = {
|
|
714
|
+
buildLayout,
|
|
715
|
+
resolveLayout,
|
|
716
|
+
readInvocationIndex,
|
|
717
|
+
discoverModelEntries,
|
|
718
|
+
readValidationAggregate,
|
|
719
|
+
readModelFileReviewInfo,
|
|
720
|
+
buildModelReview,
|
|
721
|
+
buildInvocationIndex,
|
|
722
|
+
buildNextActions,
|
|
723
|
+
renderZhReview,
|
|
724
|
+
renderEnReview,
|
|
725
|
+
renderTiming,
|
|
726
|
+
};
|
|
727
|
+
//# sourceMappingURL=review-lifecyclemodel.js.map
|