@tiangong-lca/cli 0.0.11 → 0.0.13

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 (60) hide show
  1. package/README.md +18 -7
  2. package/assets/tidas-schemas/tidas_contacts.json +312 -0
  3. package/assets/tidas-schemas/tidas_contacts_category.json +126 -0
  4. package/assets/tidas-schemas/tidas_data_types.json +359 -0
  5. package/assets/tidas-schemas/tidas_flowproperties.json +303 -0
  6. package/assets/tidas-schemas/tidas_flowproperties_category.json +61 -0
  7. package/assets/tidas-schemas/tidas_flows.json +809 -0
  8. package/assets/tidas-schemas/tidas_flows_elementary_category.json +720 -0
  9. package/assets/tidas-schemas/tidas_flows_product_category.json +59623 -0
  10. package/assets/tidas-schemas/tidas_lciamethods.json +1326 -0
  11. package/assets/tidas-schemas/tidas_lciamethods_category.json +685 -0
  12. package/assets/tidas-schemas/tidas_lifecyclemodels.json +1374 -0
  13. package/assets/tidas-schemas/tidas_locations_category.json +2593 -0
  14. package/assets/tidas-schemas/tidas_processes.json +1646 -0
  15. package/assets/tidas-schemas/tidas_processes_category.json +10795 -0
  16. package/assets/tidas-schemas/tidas_sources.json +228 -0
  17. package/assets/tidas-schemas/tidas_sources_category.json +100 -0
  18. package/assets/tidas-schemas/tidas_unitgroups.json +338 -0
  19. package/assets/tidas-schemas/tidas_unitgroups_category.json +61 -0
  20. package/dist/src/cli.js +847 -13
  21. package/dist/src/cli.js.map +1 -1
  22. package/dist/src/lib/dataset-classification.js +947 -0
  23. package/dist/src/lib/dataset-classification.js.map +1 -0
  24. package/dist/src/lib/dataset-command.js +11 -0
  25. package/dist/src/lib/dataset-command.js.map +1 -1
  26. package/dist/src/lib/dataset-contract.js +2 -0
  27. package/dist/src/lib/dataset-contract.js.map +1 -1
  28. package/dist/src/lib/dataset-curation-queue.js +359 -2
  29. package/dist/src/lib/dataset-curation-queue.js.map +1 -1
  30. package/dist/src/lib/dataset-import-lca.js +18 -0
  31. package/dist/src/lib/dataset-import-lca.js.map +1 -1
  32. package/dist/src/lib/dataset-local.js +94 -1
  33. package/dist/src/lib/dataset-local.js.map +1 -1
  34. package/dist/src/lib/dataset-maintenance-clear-account.js +506 -0
  35. package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -0
  36. package/dist/src/lib/dataset-patch.js +797 -0
  37. package/dist/src/lib/dataset-patch.js.map +1 -0
  38. package/dist/src/lib/dataset-remote-verify.js +188 -3
  39. package/dist/src/lib/dataset-remote-verify.js.map +1 -1
  40. package/dist/src/lib/dataset-save-draft-run.js +725 -0
  41. package/dist/src/lib/dataset-save-draft-run.js.map +1 -0
  42. package/dist/src/lib/dataset-validate.js +32 -2
  43. package/dist/src/lib/dataset-validate.js.map +1 -1
  44. package/dist/src/lib/flow-publish-version.js +7 -2
  45. package/dist/src/lib/flow-publish-version.js.map +1 -1
  46. package/dist/src/lib/flow-qa.js +8 -0
  47. package/dist/src/lib/flow-qa.js.map +1 -1
  48. package/dist/src/lib/identity-preflight.js +895 -117
  49. package/dist/src/lib/identity-preflight.js.map +1 -1
  50. package/dist/src/lib/lifecyclemodel-qa.js +159 -34
  51. package/dist/src/lib/lifecyclemodel-qa.js.map +1 -1
  52. package/dist/src/lib/process-required-fields.js +62 -12
  53. package/dist/src/lib/process-required-fields.js.map +1 -1
  54. package/dist/src/lib/process-save-draft-run.js +10 -0
  55. package/dist/src/lib/process-save-draft-run.js.map +1 -1
  56. package/dist/src/lib/process-save-draft.js +59 -3
  57. package/dist/src/lib/process-save-draft.js.map +1 -1
  58. package/dist/src/lib/supabase-client.js +19 -8
  59. package/dist/src/lib/supabase-client.js.map +1 -1
  60. package/package.json +2 -1
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { readJsonArtifact, writeJsonArtifact, writeJsonLinesArtifact, writeTextArtifact, } from './artifacts.js';
4
+ import { materializeDatasetRows } from './dataset-local.js';
4
5
  import { CliError } from './errors.js';
5
6
  function isRecord(value) {
6
7
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -56,6 +57,11 @@ function uniqueStrings(values) {
56
57
  function copyJson(value) {
57
58
  return JSON.parse(JSON.stringify(value));
58
59
  }
60
+ function sanitizeFileName(value) {
61
+ return (String(value ?? 'missing')
62
+ .replace(/[^A-Za-z0-9._-]+/gu, '_')
63
+ .replace(/^_+|_+$/gu, '') || 'missing');
64
+ }
59
65
  function emptySeverityCounts() {
60
66
  return {
61
67
  error: 0,
@@ -115,11 +121,15 @@ function readOptionalJsonArray(filePath, invalidCode, label) {
115
121
  }
116
122
  return value;
117
123
  }
118
- function buildLayout(runRoot, outDir) {
124
+ function buildLayout(options) {
125
+ const runRoot = options.runRoot;
126
+ const outDir = options.outDir;
119
127
  const runId = path.basename(runRoot);
120
128
  return {
121
129
  runId,
122
130
  runRoot,
131
+ rowsFile: options.rowsFile ?? '',
132
+ inputMode: options.inputMode ?? 'run_dir',
123
133
  outDir,
124
134
  modelsDir: path.join(runRoot, 'models'),
125
135
  reportsDir: path.join(runRoot, 'reports'),
@@ -127,6 +137,7 @@ function buildLayout(runRoot, outDir) {
127
137
  runManifestPath: path.join(runRoot, 'manifests', 'run-manifest.json'),
128
138
  invocationIndexPath: path.join(runRoot, 'manifests', 'invocation-index.json'),
129
139
  validationReportPath: path.join(runRoot, 'reports', 'lifecyclemodel-validate-build-report.json'),
140
+ qaInputSummaryPath: options.inputMode === 'rows_file' ? path.join(outDir, 'qa_input_summary.json') : null,
130
141
  modelSummariesPath: path.join(outDir, 'model_summaries.jsonl'),
131
142
  findingsPath: path.join(outDir, 'findings.jsonl'),
132
143
  summaryPath: path.join(outDir, 'lifecyclemodel_qa_summary.json'),
@@ -137,13 +148,6 @@ function buildLayout(runRoot, outDir) {
137
148
  };
138
149
  }
139
150
  function resolveLayout(options) {
140
- const runDir = nonEmptyString(options.runDir);
141
- if (!runDir) {
142
- throw new CliError('Missing required --run-dir for qa lifecyclemodel.', {
143
- code: 'LIFECYCLEMODEL_QA_RUN_DIR_REQUIRED',
144
- exitCode: 2,
145
- });
146
- }
147
151
  const outDir = nonEmptyString(options.outDir);
148
152
  if (!outDir) {
149
153
  throw new CliError('Missing required --out-dir for qa lifecyclemodel.', {
@@ -151,7 +155,34 @@ function resolveLayout(options) {
151
155
  exitCode: 2,
152
156
  });
153
157
  }
154
- return buildLayout(path.resolve(runDir), path.resolve(outDir));
158
+ const rowsFile = nonEmptyString(options.rowsFile);
159
+ const runDir = nonEmptyString(options.runDir);
160
+ if (rowsFile && runDir) {
161
+ throw new CliError('Use either --rows-file or --run-dir for qa lifecyclemodel, not both.', {
162
+ code: 'LIFECYCLEMODEL_QA_INPUT_MODE_CONFLICT',
163
+ exitCode: 2,
164
+ });
165
+ }
166
+ if (rowsFile) {
167
+ const resolvedRowsFile = path.resolve(rowsFile);
168
+ return buildLayout({
169
+ runRoot: path.join(path.resolve(outDir), 'qa-input', sanitizeFileName(path.basename(rowsFile))),
170
+ rowsFile: resolvedRowsFile,
171
+ inputMode: 'rows_file',
172
+ outDir: path.resolve(outDir),
173
+ });
174
+ }
175
+ if (!runDir) {
176
+ throw new CliError('Missing required --rows-file or --run-dir for qa lifecyclemodel.', {
177
+ code: 'LIFECYCLEMODEL_QA_RUN_DIR_REQUIRED',
178
+ exitCode: 2,
179
+ });
180
+ }
181
+ return buildLayout({
182
+ runRoot: path.resolve(runDir),
183
+ outDir: path.resolve(outDir),
184
+ inputMode: 'run_dir',
185
+ });
155
186
  }
156
187
  function ensureRunRootExists(layout) {
157
188
  if (!existsSync(layout.runRoot)) {
@@ -228,6 +259,7 @@ function discoverModelEntries(layout) {
228
259
  summaryPath: path.join(layout.modelsDir, runName, 'summary.json'),
229
260
  connectionsPath: path.join(layout.modelsDir, runName, 'connections.json'),
230
261
  processCatalogPath: path.join(layout.modelsDir, runName, 'process-catalog.json'),
262
+ inputMode: layout.inputMode,
231
263
  },
232
264
  ];
233
265
  });
@@ -239,6 +271,69 @@ function discoverModelEntries(layout) {
239
271
  }
240
272
  return entries;
241
273
  }
274
+ function materializeRowsFile(layout) {
275
+ if (!layout.rowsFile || !existsSync(layout.rowsFile)) {
276
+ throw new CliError(`Lifecyclemodel QA rows file not found: ${layout.rowsFile}`, {
277
+ code: 'LIFECYCLEMODEL_QA_ROWS_FILE_NOT_FOUND',
278
+ exitCode: 2,
279
+ details: { rowsFile: layout.rowsFile },
280
+ });
281
+ }
282
+ const rows = materializeDatasetRows(layout.rowsFile);
283
+ const entries = [];
284
+ writeJsonArtifact(layout.runManifestPath, {
285
+ schemaVersion: 1,
286
+ runId: layout.runId,
287
+ input_mode: 'rows_file',
288
+ rows_file: layout.rowsFile,
289
+ row_count: rows.length,
290
+ });
291
+ writeJsonArtifact(layout.invocationIndexPath, {
292
+ schema_version: 1,
293
+ invocations: [],
294
+ });
295
+ rows.forEach((row) => {
296
+ if (row.kind && row.kind !== 'lifecyclemodel') {
297
+ throw new CliError(`Expected lifecyclemodel row at index ${row.index}; detected ${row.kind}.`, {
298
+ code: 'LIFECYCLEMODEL_QA_ROWS_FILE_KIND_MISMATCH',
299
+ exitCode: 2,
300
+ details: { index: row.index, detectedKind: row.kind },
301
+ });
302
+ }
303
+ const runName = sanitizeFileName(row.id ?? `row-${row.index + 1}`);
304
+ const modelFile = path.join(layout.modelsDir, runName, 'tidas_bundle', 'lifecyclemodels', `${runName}.json`);
305
+ writeJsonArtifact(modelFile, row.payload);
306
+ entries.push({
307
+ runName,
308
+ modelFiles: [modelFile],
309
+ summaryPath: path.join(layout.modelsDir, runName, 'summary.json'),
310
+ connectionsPath: path.join(layout.modelsDir, runName, 'connections.json'),
311
+ processCatalogPath: path.join(layout.modelsDir, runName, 'process-catalog.json'),
312
+ inputMode: 'rows_file',
313
+ });
314
+ });
315
+ if (layout.qaInputSummaryPath) {
316
+ writeJsonArtifact(layout.qaInputSummaryPath, {
317
+ input_mode: 'rows_file',
318
+ rows_file: layout.rowsFile,
319
+ run_root: layout.runRoot,
320
+ materialized_lifecyclemodel_count: entries.length,
321
+ materialized_models_dir: layout.modelsDir,
322
+ });
323
+ }
324
+ return {
325
+ invocationIndex: {
326
+ schema_version: 1,
327
+ invocations: [],
328
+ },
329
+ validationAggregate: {
330
+ ok: null,
331
+ reportPath: null,
332
+ modelReports: new Map(),
333
+ },
334
+ modelEntries: entries,
335
+ };
336
+ }
242
337
  function normalizeValidationIssue(raw) {
243
338
  if (!isRecord(raw)) {
244
339
  return {
@@ -423,22 +518,22 @@ function buildModelReview(entry, validationAggregate) {
423
518
  const multiplicationFactorCount = multiplicationFactors
424
519
  ? Object.keys(multiplicationFactors).length
425
520
  : 0;
426
- if (!summaryArtifact) {
521
+ if (entry.inputMode === 'run_dir' && !summaryArtifact) {
427
522
  findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_model_summary', 'qa', 'Model bundle is missing summary.json generated by lifecyclemodel auto-build.', {
428
523
  summary_path: entry.summaryPath,
429
524
  }));
430
525
  }
431
- if (!connections) {
526
+ if (entry.inputMode === 'run_dir' && !connections) {
432
527
  findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_connections_artifact', 'qa', 'Model bundle is missing connections.json generated by lifecyclemodel auto-build.', {
433
528
  connections_path: entry.connectionsPath,
434
529
  }));
435
530
  }
436
- if (!processCatalog) {
531
+ if (entry.inputMode === 'run_dir' && !processCatalog) {
437
532
  findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_process_catalog', 'qa', 'Model bundle is missing process-catalog.json generated by lifecyclemodel auto-build.', {
438
533
  process_catalog_path: entry.processCatalogPath,
439
534
  }));
440
535
  }
441
- if (referenceProcessUuids.length === 0) {
536
+ if (entry.inputMode === 'run_dir' && referenceProcessUuids.length === 0) {
442
537
  findings.push(makeFinding(entry.runName, entry.modelFiles[0] ?? null, 'warning', 'missing_reference_process_uuid', 'qa', 'QA could not resolve reference_process_uuid from summary.json.', {
443
538
  summary_path: summaryArtifact ? entry.summaryPath : null,
444
539
  }));
@@ -518,14 +613,14 @@ function buildInvocationIndex(layout, invocationIndex, options, now) {
518
613
  const priorInvocations = Array.isArray(invocationIndex.invocations)
519
614
  ? [...invocationIndex.invocations]
520
615
  : [];
521
- const command = [
522
- 'qa',
523
- 'lifecyclemodel',
524
- '--run-dir',
525
- options.runDir,
526
- '--out-dir',
527
- options.outDir,
528
- ];
616
+ const command = ['qa', 'lifecyclemodel'];
617
+ if (layout.inputMode === 'rows_file') {
618
+ command.push('--rows-file', options.rowsFile);
619
+ }
620
+ else {
621
+ command.push('--run-dir', options.runDir);
622
+ }
623
+ command.push('--out-dir', options.outDir);
529
624
  if (nonEmptyString(options.logicVersion)) {
530
625
  command.push('--logic-version', options.logicVersion);
531
626
  }
@@ -552,6 +647,13 @@ function buildInvocationIndex(layout, invocationIndex, options, now) {
552
647
  };
553
648
  }
554
649
  function buildNextActions(layout, validationAggregate) {
650
+ if (layout.inputMode === 'rows_file') {
651
+ return [
652
+ `inspect: ${layout.findingsPath}`,
653
+ `run: tiangong-lca dataset validate --type lifecyclemodel --input ${layout.rowsFile}`,
654
+ `run: tiangong-lca lifecyclemodel save-draft --input ${layout.rowsFile} --dry-run`,
655
+ ];
656
+ }
555
657
  return [
556
658
  `inspect: ${layout.findingsPath}`,
557
659
  validationAggregate.reportPath
@@ -566,6 +668,8 @@ function renderZhReview(options) {
566
668
  `- run_id: \`${options.runId}\`\n`,
567
669
  `- logic_version: \`${options.logicVersion}\`\n`,
568
670
  `- run_root: \`${options.runRoot}\`\n`,
671
+ `- input_mode: \`${options.inputMode}\`\n`,
672
+ ...(options.rowsFile ? [`- rows_file: \`${options.rowsFile}\`\n`] : []),
569
673
  '\n## 总览\n',
570
674
  `- model bundle 数量: **${options.modelSummaries.length}**\n`,
571
675
  `- findings 数量: **${options.findings.length}**\n`,
@@ -585,7 +689,9 @@ function renderZhReview(options) {
585
689
  lines.push(`|${finding.run_name}|${finding.severity}|${finding.source}|${finding.rule_id}|${finding.message.replace(/\|/gu, '/')}|\n`);
586
690
  });
587
691
  }
588
- lines.push('\n## 说明\n', '- 当前 qa lifecyclemodel 保持 local-first / artifact-first,只读取现有 build run 与 validate-build 产物。\n', '- 当前命令不引入 Python、LangGraph 或 skill 私有 QA runtime。\n');
692
+ lines.push('\n## 说明\n', options.inputMode === 'rows_file'
693
+ ? '- 当前 qa lifecyclemodel rows-file 模式只做 payload-level deterministic QA,不要求 lifecyclemodel build-run 产物。\n'
694
+ : '- 当前 qa lifecyclemodel run-dir 模式保持 local-first / artifact-first,只读取现有 build run 与 validate-build 产物。\n', '- 当前命令不引入 Python、LangGraph 或 skill 私有 QA runtime。\n');
589
695
  return lines.join('');
590
696
  }
591
697
  function renderEnReview(options) {
@@ -594,6 +700,8 @@ function renderEnReview(options) {
594
700
  `- run_id: \`${options.runId}\`\n`,
595
701
  `- logic_version: \`${options.logicVersion}\`\n`,
596
702
  `- run_root: \`${options.runRoot}\`\n`,
703
+ `- input_mode: \`${options.inputMode}\`\n`,
704
+ ...(options.rowsFile ? [`- rows_file: \`${options.rowsFile}\`\n`] : []),
597
705
  '\n## Summary\n',
598
706
  `- model bundles: **${options.modelSummaries.length}**\n`,
599
707
  `- findings: **${options.findings.length}**\n`,
@@ -632,15 +740,21 @@ function renderTiming(options) {
632
740
  }
633
741
  export async function runLifecyclemodelQa(options) {
634
742
  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);
743
+ const input = layout.inputMode === 'rows_file'
744
+ ? materializeRowsFile(layout)
745
+ : (() => {
746
+ ensureRunRootExists(layout);
747
+ readRequiredRunManifest(layout);
748
+ return {
749
+ invocationIndex: readInvocationIndex(layout),
750
+ validationAggregate: readValidationAggregate(layout),
751
+ modelEntries: discoverModelEntries(layout),
752
+ };
753
+ })();
640
754
  const logicVersion = options.logicVersion?.trim() || 'lifecyclemodel-qa-v1.0';
641
755
  const now = options.now ?? (() => new Date());
642
756
  const generatedAt = now();
643
- const reviewedModels = modelEntries.map((entry) => buildModelReview(entry, validationAggregate));
757
+ const reviewedModels = input.modelEntries.map((entry) => buildModelReview(entry, input.validationAggregate));
644
758
  const modelSummaries = reviewedModels.map((model) => model.summary);
645
759
  const findings = reviewedModels.flatMap((model) => model.findings);
646
760
  const report = {
@@ -649,20 +763,23 @@ export async function runLifecyclemodelQa(options) {
649
763
  status: 'completed_local_lifecyclemodel_qa',
650
764
  run_id: layout.runId,
651
765
  run_root: layout.runRoot,
766
+ rows_file: layout.rowsFile,
767
+ input_mode: layout.inputMode,
652
768
  out_dir: layout.outDir,
653
769
  logic_version: logicVersion,
654
770
  model_count: modelSummaries.length,
655
771
  finding_count: findings.length,
656
772
  severity_counts: severityCounts(findings),
657
773
  validation: {
658
- available: validationAggregate.reportPath !== null,
659
- ok: validationAggregate.ok,
660
- report: validationAggregate.reportPath,
774
+ available: input.validationAggregate.reportPath !== null,
775
+ ok: input.validationAggregate.ok,
776
+ report: input.validationAggregate.reportPath,
661
777
  },
662
778
  files: {
663
779
  run_manifest: layout.runManifestPath,
664
780
  invocation_index: layout.invocationIndexPath,
665
- validation_report: validationAggregate.reportPath,
781
+ qa_input_summary: layout.qaInputSummaryPath,
782
+ validation_report: input.validationAggregate.reportPath,
666
783
  model_summaries: layout.modelSummariesPath,
667
784
  findings: layout.findingsPath,
668
785
  summary: layout.summaryPath,
@@ -672,13 +789,15 @@ export async function runLifecyclemodelQa(options) {
672
789
  report: layout.reportPath,
673
790
  },
674
791
  model_summaries: modelSummaries,
675
- next_actions: buildNextActions(layout, validationAggregate),
792
+ next_actions: buildNextActions(layout, input.validationAggregate),
676
793
  };
677
- writeJsonArtifact(layout.invocationIndexPath, buildInvocationIndex(layout, invocationIndex, options, generatedAt));
794
+ writeJsonArtifact(layout.invocationIndexPath, buildInvocationIndex(layout, input.invocationIndex, options, generatedAt));
678
795
  writeJsonLinesArtifact(layout.modelSummariesPath, modelSummaries);
679
796
  writeJsonLinesArtifact(layout.findingsPath, findings);
680
797
  writeJsonArtifact(layout.summaryPath, {
681
798
  run_id: report.run_id,
799
+ rows_file: report.rows_file,
800
+ input_mode: report.input_mode,
682
801
  logic_version: report.logic_version,
683
802
  model_count: report.model_count,
684
803
  finding_count: report.finding_count,
@@ -689,6 +808,8 @@ export async function runLifecyclemodelQa(options) {
689
808
  runId: report.run_id,
690
809
  logicVersion: report.logic_version,
691
810
  runRoot: report.run_root,
811
+ rowsFile: report.rows_file,
812
+ inputMode: report.input_mode,
692
813
  modelSummaries,
693
814
  findings,
694
815
  validation: report.validation,
@@ -697,6 +818,8 @@ export async function runLifecyclemodelQa(options) {
697
818
  runId: report.run_id,
698
819
  logicVersion: report.logic_version,
699
820
  runRoot: report.run_root,
821
+ rowsFile: report.rows_file,
822
+ inputMode: report.input_mode,
700
823
  modelSummaries,
701
824
  findings,
702
825
  validation: report.validation,
@@ -716,6 +839,8 @@ export const __testInternals = {
716
839
  readInvocationIndex,
717
840
  discoverModelEntries,
718
841
  readValidationAggregate,
842
+ materializeRowsFile,
843
+ sanitizeFileName,
719
844
  readModelFileReviewInfo,
720
845
  buildModelReview,
721
846
  buildInvocationIndex,