@tiangong-lca/cli 0.0.7 → 0.0.9

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 (59) hide show
  1. package/README.md +97 -16
  2. package/dist/src/cli.js +1429 -131
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/lib/dataset-author.js +100 -0
  5. package/dist/src/lib/dataset-author.js.map +1 -0
  6. package/dist/src/lib/dataset-bilingual.js +545 -0
  7. package/dist/src/lib/dataset-bilingual.js.map +1 -0
  8. package/dist/src/lib/dataset-contract.js +350 -0
  9. package/dist/src/lib/dataset-contract.js.map +1 -0
  10. package/dist/src/lib/dataset-evidence-search.js +636 -0
  11. package/dist/src/lib/dataset-evidence-search.js.map +1 -0
  12. package/dist/src/lib/dataset-import-lca.js +171 -0
  13. package/dist/src/lib/dataset-import-lca.js.map +1 -0
  14. package/dist/src/lib/dataset-remote-refresh.js +166 -0
  15. package/dist/src/lib/dataset-remote-refresh.js.map +1 -0
  16. package/dist/src/lib/dataset-remote-verify.js +543 -0
  17. package/dist/src/lib/dataset-remote-verify.js.map +1 -0
  18. package/dist/src/lib/dataset-validate.js +63 -7
  19. package/dist/src/lib/dataset-validate.js.map +1 -1
  20. package/dist/src/lib/flow-fetch-rows.js +12 -12
  21. package/dist/src/lib/flow-fetch-rows.js.map +1 -1
  22. package/dist/src/lib/flow-payload-validation.js +51 -0
  23. package/dist/src/lib/flow-payload-validation.js.map +1 -0
  24. package/dist/src/lib/flow-publish-reviewed-data.js +16 -0
  25. package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -1
  26. package/dist/src/lib/flow-publish-version.js +182 -12
  27. package/dist/src/lib/flow-publish-version.js.map +1 -1
  28. package/dist/src/lib/{review-flow.js → flow-qa.js} +86 -28
  29. package/dist/src/lib/flow-qa.js.map +1 -0
  30. package/dist/src/lib/identity-preflight.js +1021 -0
  31. package/dist/src/lib/identity-preflight.js.map +1 -0
  32. package/dist/src/lib/{review-lifecyclemodel.js → lifecyclemodel-qa.js} +54 -54
  33. package/dist/src/lib/lifecyclemodel-qa.js.map +1 -0
  34. package/dist/src/lib/process-auto-build.js +147 -0
  35. package/dist/src/lib/process-auto-build.js.map +1 -1
  36. package/dist/src/lib/process-dedup-review.js +51 -0
  37. package/dist/src/lib/process-dedup-review.js.map +1 -1
  38. package/dist/src/lib/process-flow-build-plan.js +1071 -0
  39. package/dist/src/lib/process-flow-build-plan.js.map +1 -0
  40. package/dist/src/lib/process-payload-validation.js +14 -7
  41. package/dist/src/lib/process-payload-validation.js.map +1 -1
  42. package/dist/src/lib/process-publish-build.js +122 -4
  43. package/dist/src/lib/process-publish-build.js.map +1 -1
  44. package/dist/src/lib/{review-process.js → process-qa.js} +259 -117
  45. package/dist/src/lib/process-qa.js.map +1 -0
  46. package/dist/src/lib/process-refresh-references.js +19 -10
  47. package/dist/src/lib/process-refresh-references.js.map +1 -1
  48. package/dist/src/lib/process-required-fields.js +810 -0
  49. package/dist/src/lib/process-required-fields.js.map +1 -0
  50. package/dist/src/lib/process-save-draft-run.js +4 -1
  51. package/dist/src/lib/process-save-draft-run.js.map +1 -1
  52. package/dist/src/lib/publish.js +100 -0
  53. package/dist/src/lib/publish.js.map +1 -1
  54. package/dist/src/lib/runtime-rulesets.js +283 -0
  55. package/dist/src/lib/runtime-rulesets.js.map +1 -0
  56. package/package.json +2 -2
  57. package/dist/src/lib/review-flow.js.map +0 -1
  58. package/dist/src/lib/review-lifecyclemodel.js.map +0 -1
  59. package/dist/src/lib/review-process.js.map +0 -1
@@ -0,0 +1,100 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { writeJsonArtifact } from './artifacts.js';
4
+ import { runDatasetContract } from './dataset-contract.js';
5
+ import { CliError } from './errors.js';
6
+ import { parseUnstructuredDocument, readUnstructuredRuntimeEnv, } from './unstructured.js';
7
+ export async function runDatasetAuthor(options) {
8
+ const inputPath = requireInputPath(options.inputPath);
9
+ const outDir = requireOutDir(options.outDir);
10
+ const targetTypes = normalizeTargetTypes(options.targetTypes);
11
+ const outputsDir = path.join(outDir, 'outputs');
12
+ const sourceExtractFile = path.join(outputsDir, 'source-extract.json');
13
+ const authoringReportFile = path.join(outputsDir, 'authoring-report.json');
14
+ const parseImpl = options.parseImpl ?? parseUnstructuredDocument;
15
+ const contractImpl = options.contractImpl ?? runDatasetContract;
16
+ const sourceExtract = await parseImpl({
17
+ env: readUnstructuredRuntimeEnv(options.env),
18
+ filePath: inputPath,
19
+ prompt: options.prompt,
20
+ provider: options.provider,
21
+ model: options.model,
22
+ timeoutMs: options.timeoutMs ?? 120000,
23
+ fetchImpl: options.fetchImpl,
24
+ });
25
+ writeJsonArtifact(sourceExtractFile, sourceExtract);
26
+ const contextPacks = [];
27
+ for (const type of targetTypes) {
28
+ const report = await contractImpl({
29
+ type,
30
+ include: ['schema', 'methodology', 'ruleset'],
31
+ profile: 'ai-import',
32
+ outDir: path.join(outDir, 'context', type),
33
+ mode: 'context-pack',
34
+ now: options.now,
35
+ });
36
+ contextPacks.push({ type, report });
37
+ }
38
+ const report = {
39
+ schema_version: 1,
40
+ status: 'evidence_ready',
41
+ generated_at_utc: (options.now ?? new Date()).toISOString(),
42
+ input_path: inputPath,
43
+ target_types: targetTypes,
44
+ files: {
45
+ source_extract: sourceExtractFile,
46
+ authoring_report: authoringReportFile,
47
+ },
48
+ context_packs: contextPacks,
49
+ next_actions: [
50
+ 'Use outputs/source-extract.json and the context pack manifests to generate candidate TIDAS rows.',
51
+ 'Run tiangong-lca dataset validate for each generated row file before mutation planning.',
52
+ 'Place invalid rows in a repair queue and keep source evidence linked to field-level assumptions.',
53
+ ],
54
+ };
55
+ writeJsonArtifact(authoringReportFile, report);
56
+ return report;
57
+ }
58
+ function requireInputPath(value) {
59
+ if (!value?.trim()) {
60
+ throw new CliError('Missing required --input value.', {
61
+ code: 'DATASET_AUTHOR_INPUT_REQUIRED',
62
+ exitCode: 2,
63
+ });
64
+ }
65
+ const resolved = path.resolve(value);
66
+ if (!existsSync(resolved)) {
67
+ throw new CliError(`Input file not found: ${resolved}`, {
68
+ code: 'DATASET_AUTHOR_INPUT_NOT_FOUND',
69
+ exitCode: 2,
70
+ });
71
+ }
72
+ return resolved;
73
+ }
74
+ function requireOutDir(value) {
75
+ if (!value?.trim()) {
76
+ throw new CliError('Missing required --out-dir value.', {
77
+ code: 'DATASET_AUTHOR_OUT_DIR_REQUIRED',
78
+ exitCode: 2,
79
+ });
80
+ }
81
+ return path.resolve(value);
82
+ }
83
+ function normalizeTargetTypes(value) {
84
+ const rawValues = Array.isArray(value) ? value : value ? [value] : [];
85
+ const values = rawValues.flatMap((item) => item
86
+ .split(',')
87
+ .map((part) => part.trim())
88
+ .filter(Boolean));
89
+ if (!values.length) {
90
+ throw new CliError('Missing required --target-types value.', {
91
+ code: 'DATASET_AUTHOR_TARGET_TYPES_REQUIRED',
92
+ exitCode: 2,
93
+ });
94
+ }
95
+ return [...new Set(values)];
96
+ }
97
+ export const __testInternals = {
98
+ normalizeTargetTypes,
99
+ };
100
+ //# sourceMappingURL=dataset-author.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-author.js","sourceRoot":"","sources":["../../../src/lib/dataset-author.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAA8B,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAE3B,MAAM,mBAAmB,CAAC;AAmC3B,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgC;IAEhC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,oBAAoB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC;IACvE,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,yBAAyB,CAAC;IACjE,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,kBAAkB,CAAC;IAEhE,MAAM,aAAa,GAAG,MAAM,SAAS,CAAC;QACpC,GAAG,EAAE,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC;QAC5C,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM;QACtC,SAAS,EAAE,OAAO,CAAC,SAAS;KACc,CAAC,CAAC;IAC9C,iBAAiB,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;IAEpD,MAAM,YAAY,GAAG,EAAE,CAAC;IACxB,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;YAChC,IAAI;YACJ,OAAO,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC;YAC7C,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC;YAC1C,IAAI,EAAE,cAAc;YACpB,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC,CAAC;QACH,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAwB;QAClC,cAAc,EAAE,CAAC;QACjB,MAAM,EAAE,gBAAgB;QACxB,gBAAgB,EAAE,CAAC,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;QAC3D,UAAU,EAAE,SAAS;QACrB,YAAY,EAAE,WAAW;QACzB,KAAK,EAAE;YACL,cAAc,EAAE,iBAAiB;YACjC,gBAAgB,EAAE,mBAAmB;SACtC;QACD,aAAa,EAAE,YAAY;QAC3B,YAAY,EAAE;YACZ,kGAAkG;YAClG,yFAAyF;YACzF,kGAAkG;SACnG;KACF,CAAC;IACF,iBAAiB,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAE/C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,QAAQ,CAAC,iCAAiC,EAAE;YACpD,IAAI,EAAE,+BAA+B;YACrC,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAAC,yBAAyB,QAAQ,EAAE,EAAE;YACtD,IAAI,EAAE,gCAAgC;YACtC,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,KAAgC;IACrD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,QAAQ,CAAC,mCAAmC,EAAE;YACtD,IAAI,EAAE,iCAAiC;YACvC,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAoC;IAChE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACxC,IAAI;SACD,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CACnB,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,QAAQ,CAAC,wCAAwC,EAAE;YAC3D,IAAI,EAAE,sCAAsC;YAC5C,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,oBAAoB;CACrB,CAAC","sourcesContent":["import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { writeJsonArtifact } from './artifacts.js';\nimport { runDatasetContract, type DatasetContractReport } from './dataset-contract.js';\nimport { CliError } from './errors.js';\nimport {\n parseUnstructuredDocument,\n readUnstructuredRuntimeEnv,\n type ParseUnstructuredDocumentOptions,\n} from './unstructured.js';\nimport type { FetchLike } from './http.js';\n\nexport type DatasetAuthorReport = {\n schema_version: 1;\n status: 'evidence_ready';\n generated_at_utc: string;\n input_path: string;\n target_types: string[];\n files: {\n source_extract: string;\n authoring_report: string;\n };\n context_packs: Array<{\n type: string;\n report: DatasetContractReport;\n }>;\n next_actions: string[];\n};\n\nexport type RunDatasetAuthorOptions = {\n inputPath: string;\n targetTypes: string | string[] | undefined;\n outDir: string | null | undefined;\n prompt?: string | undefined;\n provider?: string | undefined;\n model?: string | undefined;\n timeoutMs?: number | undefined;\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n now?: Date | undefined;\n parseImpl?: typeof parseUnstructuredDocument | undefined;\n contractImpl?: typeof runDatasetContract | undefined;\n};\n\nexport async function runDatasetAuthor(\n options: RunDatasetAuthorOptions,\n): Promise<DatasetAuthorReport> {\n const inputPath = requireInputPath(options.inputPath);\n const outDir = requireOutDir(options.outDir);\n const targetTypes = normalizeTargetTypes(options.targetTypes);\n const outputsDir = path.join(outDir, 'outputs');\n const sourceExtractFile = path.join(outputsDir, 'source-extract.json');\n const authoringReportFile = path.join(outputsDir, 'authoring-report.json');\n const parseImpl = options.parseImpl ?? parseUnstructuredDocument;\n const contractImpl = options.contractImpl ?? runDatasetContract;\n\n const sourceExtract = await parseImpl({\n env: readUnstructuredRuntimeEnv(options.env),\n filePath: inputPath,\n prompt: options.prompt,\n provider: options.provider,\n model: options.model,\n timeoutMs: options.timeoutMs ?? 120000,\n fetchImpl: options.fetchImpl,\n } satisfies ParseUnstructuredDocumentOptions);\n writeJsonArtifact(sourceExtractFile, sourceExtract);\n\n const contextPacks = [];\n for (const type of targetTypes) {\n const report = await contractImpl({\n type,\n include: ['schema', 'methodology', 'ruleset'],\n profile: 'ai-import',\n outDir: path.join(outDir, 'context', type),\n mode: 'context-pack',\n now: options.now,\n });\n contextPacks.push({ type, report });\n }\n\n const report: DatasetAuthorReport = {\n schema_version: 1,\n status: 'evidence_ready',\n generated_at_utc: (options.now ?? new Date()).toISOString(),\n input_path: inputPath,\n target_types: targetTypes,\n files: {\n source_extract: sourceExtractFile,\n authoring_report: authoringReportFile,\n },\n context_packs: contextPacks,\n next_actions: [\n 'Use outputs/source-extract.json and the context pack manifests to generate candidate TIDAS rows.',\n 'Run tiangong-lca dataset validate for each generated row file before mutation planning.',\n 'Place invalid rows in a repair queue and keep source evidence linked to field-level assumptions.',\n ],\n };\n writeJsonArtifact(authoringReportFile, report);\n\n return report;\n}\n\nfunction requireInputPath(value: string): string {\n if (!value?.trim()) {\n throw new CliError('Missing required --input value.', {\n code: 'DATASET_AUTHOR_INPUT_REQUIRED',\n exitCode: 2,\n });\n }\n const resolved = path.resolve(value);\n if (!existsSync(resolved)) {\n throw new CliError(`Input file not found: ${resolved}`, {\n code: 'DATASET_AUTHOR_INPUT_NOT_FOUND',\n exitCode: 2,\n });\n }\n return resolved;\n}\n\nfunction requireOutDir(value: string | null | undefined): string {\n if (!value?.trim()) {\n throw new CliError('Missing required --out-dir value.', {\n code: 'DATASET_AUTHOR_OUT_DIR_REQUIRED',\n exitCode: 2,\n });\n }\n return path.resolve(value);\n}\n\nfunction normalizeTargetTypes(value: string | string[] | undefined): string[] {\n const rawValues = Array.isArray(value) ? value : value ? [value] : [];\n const values = rawValues.flatMap((item) =>\n item\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean),\n );\n if (!values.length) {\n throw new CliError('Missing required --target-types value.', {\n code: 'DATASET_AUTHOR_TARGET_TYPES_REQUIRED',\n exitCode: 2,\n });\n }\n return [...new Set(values)];\n}\n\nexport const __testInternals = {\n normalizeTargetTypes,\n};\n"]}
@@ -0,0 +1,545 @@
1
+ import { createHash } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
4
+ import { CliError } from './errors.js';
5
+ import { cloneJson, isRecord, materializeDatasetRows, readDatasetRowsInput, trimToken, } from './dataset-local.js';
6
+ import { runDatasetValidate, } from './dataset-validate.js';
7
+ import { runFlowQa } from './flow-qa.js';
8
+ import { runProcessQa } from './process-qa.js';
9
+ const DEFAULT_SOURCE_LANG = 'en';
10
+ const DEFAULT_TARGET_LANG = 'zh';
11
+ const PLACEHOLDER_RE = /\b(?:TODO|TBD|FIXME|REPLACE_ME)\b|待补充|占位/u;
12
+ const CJK_RE = /[\u3400-\u9fff]/u;
13
+ const LATIN_WORD_RE = /[A-Za-z]{3,}/gu;
14
+ const CJK_PLURAL_SUFFIX_RE = /[\u3400-\u9fff]+s\b/u;
15
+ function nowIso(now = new Date()) {
16
+ return now.toISOString();
17
+ }
18
+ function normalizeType(value) {
19
+ const normalized = value?.trim().toLowerCase();
20
+ if (!normalized || normalized === 'auto') {
21
+ return 'auto';
22
+ }
23
+ if (normalized === 'flow' || normalized === 'flows') {
24
+ return 'flow';
25
+ }
26
+ if (normalized === 'process' || normalized === 'processes') {
27
+ return 'process';
28
+ }
29
+ if (normalized === 'lifecyclemodel' ||
30
+ normalized === 'lifecyclemodels' ||
31
+ normalized === 'model' ||
32
+ normalized === 'models') {
33
+ return 'lifecyclemodel';
34
+ }
35
+ throw new CliError('Expected --type to be auto, flow, process, or lifecyclemodel.', {
36
+ code: 'DATASET_BILINGUAL_TYPE_INVALID',
37
+ exitCode: 2,
38
+ details: value,
39
+ });
40
+ }
41
+ function normalizeLang(value, fallback) {
42
+ const normalized = value?.trim().toLowerCase();
43
+ return normalized || fallback;
44
+ }
45
+ function pointerEscape(segment) {
46
+ return segment.replace(/~/gu, '~0').replace(/\//gu, '~1');
47
+ }
48
+ function pointerUnescape(segment) {
49
+ return segment.replace(/~1/gu, '/').replace(/~0/gu, '~');
50
+ }
51
+ function pointerFromSegments(segments) {
52
+ return `/${segments.map(pointerEscape).join('/')}`;
53
+ }
54
+ function segmentsFromPointer(pointer) {
55
+ if (!pointer.startsWith('/')) {
56
+ throw new CliError(`Expected JSON pointer field_path, got: ${pointer}`, {
57
+ code: 'DATASET_BILINGUAL_FIELD_PATH_INVALID',
58
+ exitCode: 2,
59
+ });
60
+ }
61
+ return pointer.slice(1).split('/').map(pointerUnescape);
62
+ }
63
+ function textPreview(value) {
64
+ return value.length > 180 ? `${value.slice(0, 177)}...` : value;
65
+ }
66
+ function langOf(value) {
67
+ return trimToken(isRecord(value) ? value['@xml:lang'] : null)?.toLowerCase() ?? null;
68
+ }
69
+ function textOf(value) {
70
+ return trimToken(isRecord(value) ? value['#text'] : null);
71
+ }
72
+ function isLangRecord(value) {
73
+ return isRecord(value) && (typeof value['@xml:lang'] === 'string' || '#text' in value);
74
+ }
75
+ function isLangRecordArray(value) {
76
+ return Array.isArray(value) && value.some((item) => isLangRecord(item));
77
+ }
78
+ function hashUnit(parts) {
79
+ const hash = createHash('sha256');
80
+ for (const part of parts) {
81
+ hash.update(String(part ?? ''));
82
+ hash.update('\n');
83
+ }
84
+ return hash.digest('hex').slice(0, 24);
85
+ }
86
+ function filterRowsByType(rows, requestedType) {
87
+ if (requestedType === 'auto') {
88
+ return rows;
89
+ }
90
+ return rows.filter((row) => row.kind === requestedType);
91
+ }
92
+ function rootFieldFromPath(pathSegments) {
93
+ return pathSegments.find((segment) => !/^\d+$/u.test(segment)) ?? null;
94
+ }
95
+ function siblingKeys(parent) {
96
+ if (!isRecord(parent)) {
97
+ return [];
98
+ }
99
+ return Object.keys(parent).sort().slice(0, 20);
100
+ }
101
+ function collectTranslationUnitsFromNode(node, pathSegments, parent, visit, sourceLang, targetLang, units) {
102
+ if (isLangRecordArray(node)) {
103
+ const source = node.find((item) => langOf(item) === sourceLang);
104
+ const target = node.find((item) => langOf(item) === targetLang);
105
+ const sourceText = textOf(source);
106
+ if (sourceText) {
107
+ const fieldPath = pointerFromSegments(pathSegments);
108
+ units.push({
109
+ schema_version: 1,
110
+ unit_id: hashUnit([
111
+ visit.rowIndex,
112
+ visit.id,
113
+ visit.version,
114
+ fieldPath,
115
+ sourceLang,
116
+ targetLang,
117
+ sourceText,
118
+ ]),
119
+ row_index: visit.rowIndex,
120
+ dataset_type: visit.kind,
121
+ dataset_id: visit.id,
122
+ dataset_version: visit.version,
123
+ field_path: fieldPath,
124
+ source_lang: sourceLang,
125
+ target_lang: targetLang,
126
+ source_text: sourceText,
127
+ current_target_text: textOf(target),
128
+ context: {
129
+ root_field: rootFieldFromPath(pathSegments),
130
+ sibling_keys: siblingKeys(parent),
131
+ },
132
+ });
133
+ }
134
+ return;
135
+ }
136
+ if (Array.isArray(node)) {
137
+ node.forEach((item, index) => collectTranslationUnitsFromNode(item, [...pathSegments, String(index)], node, visit, sourceLang, targetLang, units));
138
+ return;
139
+ }
140
+ if (isRecord(node)) {
141
+ for (const [key, value] of Object.entries(node)) {
142
+ collectTranslationUnitsFromNode(value, [...pathSegments, key], node, visit, sourceLang, targetLang, units);
143
+ }
144
+ }
145
+ }
146
+ function collectTranslationUnits(rows, sourceLang, targetLang) {
147
+ const units = [];
148
+ for (const row of rows) {
149
+ collectTranslationUnitsFromNode(row.row, [], null, {
150
+ rowIndex: row.index,
151
+ row: row.row,
152
+ kind: row.kind,
153
+ id: row.id,
154
+ version: row.version,
155
+ }, sourceLang, targetLang, units);
156
+ }
157
+ return units;
158
+ }
159
+ function outFiles(outDir) {
160
+ if (!outDir) {
161
+ return {
162
+ translation_units: null,
163
+ report: null,
164
+ };
165
+ }
166
+ const resolved = path.resolve(outDir);
167
+ return {
168
+ translation_units: path.join(resolved, 'outputs', 'trans-units.jsonl'),
169
+ report: path.join(resolved, 'outputs', 'extract-report.json'),
170
+ };
171
+ }
172
+ function readTranslationInputs(inputPath, rawInput) {
173
+ return readDatasetRowsInput(inputPath, rawInput).map((row) => {
174
+ const translatedText = trimToken(row.translated_text) ??
175
+ trimToken(row.translatedText) ??
176
+ trimToken(row.translation) ??
177
+ trimToken(row.target_text) ??
178
+ trimToken(row.targetText);
179
+ return {
180
+ unit_id: trimToken(row.unit_id) ?? trimToken(row.unitId),
181
+ row_index: typeof row.row_index === 'number'
182
+ ? row.row_index
183
+ : typeof row.rowIndex === 'number'
184
+ ? row.rowIndex
185
+ : null,
186
+ field_path: trimToken(row.field_path) ?? trimToken(row.fieldPath),
187
+ source_lang: trimToken(row.source_lang) ?? trimToken(row.sourceLang),
188
+ target_lang: trimToken(row.target_lang) ?? trimToken(row.targetLang),
189
+ source_text: trimToken(row.source_text) ?? trimToken(row.sourceText),
190
+ translated_text: translatedText,
191
+ basis: trimToken(row.basis) ?? trimToken(row.rationale),
192
+ review_status: trimToken(row.review_status) ?? trimToken(row.reviewStatus),
193
+ reviewer: trimToken(row.reviewer),
194
+ };
195
+ });
196
+ }
197
+ function resolvePointer(root, pointer) {
198
+ let current = root;
199
+ for (const segment of segmentsFromPointer(pointer)) {
200
+ if (Array.isArray(current)) {
201
+ current = current[Number(segment)];
202
+ }
203
+ else if (isRecord(current)) {
204
+ current = current[segment];
205
+ }
206
+ else {
207
+ return undefined;
208
+ }
209
+ }
210
+ return current;
211
+ }
212
+ function cloneSourceLangRecord(container, sourceLang) {
213
+ const source = container.find((item) => langOf(item) === sourceLang);
214
+ const cloned = isRecord(source) ? cloneJson(source) : {};
215
+ cloned['@xml:lang'] = '';
216
+ cloned['#text'] = '';
217
+ return cloned;
218
+ }
219
+ function applyTranslationToRows(options) {
220
+ const translation = options.translation;
221
+ if (translation.row_index === null || translation.row_index < 0) {
222
+ return { applied: false, blocker: 'Translation is missing row_index.' };
223
+ }
224
+ if (!translation.field_path) {
225
+ return { applied: false, blocker: 'Translation is missing field_path.' };
226
+ }
227
+ if (!translation.translated_text) {
228
+ return { applied: false, blocker: 'Translation is missing translated_text.' };
229
+ }
230
+ const row = options.rows[translation.row_index];
231
+ if (!row) {
232
+ return { applied: false, blocker: `No input row exists at index ${translation.row_index}.` };
233
+ }
234
+ const container = resolvePointer(row, translation.field_path);
235
+ if (!Array.isArray(container)) {
236
+ return { applied: false, blocker: `field_path does not point to a language array.` };
237
+ }
238
+ let target = container.find((item) => isRecord(item) && langOf(item) === options.targetLang);
239
+ if (!target) {
240
+ target = cloneSourceLangRecord(container, translation.source_lang ?? 'en');
241
+ container.push(target);
242
+ }
243
+ target['@xml:lang'] = options.targetLang;
244
+ target['#text'] = translation.translated_text;
245
+ const materializedRows = materializeDatasetRows('memory', { rows: [row] });
246
+ const materialized = materializedRows[0];
247
+ return {
248
+ applied: true,
249
+ evidence: {
250
+ unit_id: translation.unit_id ??
251
+ hashUnit([
252
+ translation.row_index,
253
+ translation.field_path,
254
+ translation.source_lang,
255
+ options.targetLang,
256
+ translation.source_text,
257
+ ]),
258
+ row_index: translation.row_index,
259
+ dataset_type: materialized?.kind ?? null,
260
+ dataset_id: materialized?.id ?? null,
261
+ dataset_version: materialized?.version ?? null,
262
+ field_path: translation.field_path,
263
+ source_lang: translation.source_lang ?? 'en',
264
+ target_lang: options.targetLang,
265
+ source_text: translation.source_text ?? '',
266
+ translated_text: translation.translated_text,
267
+ basis: translation.basis,
268
+ review_status: translation.review_status,
269
+ reviewer: translation.reviewer,
270
+ },
271
+ };
272
+ }
273
+ function writeRowsJsonl(filePath, rows) {
274
+ return writeJsonLinesArtifact(filePath, rows);
275
+ }
276
+ function scanText(node, pathSegments, visit) {
277
+ const findings = [];
278
+ if (isLangRecord(node)) {
279
+ const lang = langOf(node);
280
+ const text = textOf(node);
281
+ if (!text) {
282
+ return findings;
283
+ }
284
+ const fieldPath = pointerFromSegments(pathSegments);
285
+ const base = {
286
+ row_index: visit.rowIndex,
287
+ dataset_type: visit.kind,
288
+ dataset_id: visit.id,
289
+ dataset_version: visit.version,
290
+ field_path: fieldPath,
291
+ lang,
292
+ text_preview: textPreview(text),
293
+ };
294
+ if (PLACEHOLDER_RE.test(text)) {
295
+ findings.push({
296
+ ...base,
297
+ code: 'placeholder_text',
298
+ severity: 'blocker',
299
+ message: 'Text contains placeholder markers.',
300
+ });
301
+ }
302
+ if (lang === 'en' && CJK_RE.test(text)) {
303
+ findings.push({
304
+ ...base,
305
+ code: 'english_contains_cjk',
306
+ severity: 'blocker',
307
+ message: 'English text contains CJK characters.',
308
+ });
309
+ }
310
+ if (lang === 'zh' && CJK_PLURAL_SUFFIX_RE.test(text)) {
311
+ findings.push({
312
+ ...base,
313
+ code: 'mechanical_plural_suffix',
314
+ severity: 'blocker',
315
+ message: 'Chinese text contains mechanical mixed-language plural suffixes.',
316
+ });
317
+ }
318
+ if (lang === 'zh') {
319
+ const latinWords = text.match(LATIN_WORD_RE) ?? [];
320
+ if (latinWords.length >= 8) {
321
+ findings.push({
322
+ ...base,
323
+ code: 'zh_latin_word_density',
324
+ severity: 'warning',
325
+ message: 'Chinese text contains many Latin words; review for machine-translation residue.',
326
+ });
327
+ }
328
+ }
329
+ return findings;
330
+ }
331
+ if (Array.isArray(node)) {
332
+ node.forEach((item, index) => {
333
+ findings.push(...scanText(item, [...pathSegments, String(index)], visit));
334
+ });
335
+ return findings;
336
+ }
337
+ if (isRecord(node)) {
338
+ for (const [key, value] of Object.entries(node)) {
339
+ findings.push(...scanText(value, [...pathSegments, key], visit));
340
+ }
341
+ }
342
+ return findings;
343
+ }
344
+ function scanRows(rows) {
345
+ return rows.flatMap((row) => scanText(row.row, [], {
346
+ rowIndex: row.index,
347
+ row: row.row,
348
+ kind: row.kind,
349
+ id: row.id,
350
+ version: row.version,
351
+ }));
352
+ }
353
+ function validateFiles(outDir) {
354
+ if (!outDir) {
355
+ return {
356
+ report: null,
357
+ findings: null,
358
+ };
359
+ }
360
+ const resolved = path.resolve(outDir);
361
+ return {
362
+ report: path.join(resolved, 'outputs', 'bilingual-validate-report.json'),
363
+ findings: path.join(resolved, 'outputs', 'bilingual-findings.jsonl'),
364
+ };
365
+ }
366
+ export async function runDatasetBilingualExtract(options) {
367
+ const requestedType = normalizeType(options.type);
368
+ const sourceLang = normalizeLang(options.sourceLang, DEFAULT_SOURCE_LANG);
369
+ const targetLang = normalizeLang(options.targetLang, DEFAULT_TARGET_LANG);
370
+ const rows = filterRowsByType(materializeDatasetRows(options.inputPath, options.rawInput), requestedType);
371
+ const units = collectTranslationUnits(rows, sourceLang, targetLang);
372
+ const files = outFiles(options.outDir);
373
+ const report = {
374
+ schema_version: 1,
375
+ generated_at_utc: nowIso(options.now),
376
+ input_path: options.inputPath,
377
+ requested_type: requestedType,
378
+ source_lang: sourceLang,
379
+ target_lang: targetLang,
380
+ unit_count: units.length,
381
+ row_count: rows.length,
382
+ files,
383
+ };
384
+ if (files.translation_units) {
385
+ writeJsonLinesArtifact(files.translation_units, units);
386
+ }
387
+ if (files.report) {
388
+ writeJsonArtifact(files.report, report);
389
+ }
390
+ return report;
391
+ }
392
+ export async function runDatasetBilingualApply(options) {
393
+ const targetLang = normalizeLang(options.targetLang, DEFAULT_TARGET_LANG);
394
+ if (!options.outPath) {
395
+ throw new CliError('Missing required --out value.', {
396
+ code: 'DATASET_BILINGUAL_OUT_REQUIRED',
397
+ exitCode: 2,
398
+ });
399
+ }
400
+ if (!options.translationsPath) {
401
+ throw new CliError('Missing required --translations value.', {
402
+ code: 'DATASET_BILINGUAL_TRANSLATIONS_REQUIRED',
403
+ exitCode: 2,
404
+ });
405
+ }
406
+ const rows = readDatasetRowsInput(options.inputPath, options.rawInput).map((row) => cloneJson(row));
407
+ const translations = readTranslationInputs(options.translationsPath, options.rawTranslations);
408
+ const blockers = [];
409
+ const evidence = [];
410
+ let appliedCount = 0;
411
+ let skippedCount = 0;
412
+ for (const translation of translations) {
413
+ const result = applyTranslationToRows({ rows, translation, targetLang });
414
+ if (result.applied) {
415
+ appliedCount += 1;
416
+ evidence.push(result.evidence);
417
+ }
418
+ else {
419
+ skippedCount += 1;
420
+ blockers.push({
421
+ code: 'translation_not_applied',
422
+ message: result.blocker,
423
+ unit_id: translation.unit_id ?? undefined,
424
+ });
425
+ }
426
+ }
427
+ const resolvedOut = path.resolve(options.outPath);
428
+ const resolvedOutDir = options.outDir ? path.resolve(options.outDir) : path.dirname(resolvedOut);
429
+ const evidenceFile = path.join(resolvedOutDir, 'outputs', 'translation-evidence.json');
430
+ const reportFile = path.join(resolvedOutDir, 'outputs', 'bilingual-apply-report.json');
431
+ writeRowsJsonl(resolvedOut, rows);
432
+ writeJsonArtifact(evidenceFile, {
433
+ schema_version: 1,
434
+ generated_at_utc: nowIso(options.now),
435
+ entries: evidence,
436
+ });
437
+ const report = {
438
+ schema_version: 1,
439
+ generated_at_utc: nowIso(options.now),
440
+ input_path: options.inputPath,
441
+ translations_path: options.translationsPath,
442
+ out_path: resolvedOut,
443
+ target_lang: targetLang,
444
+ status: blockers.length > 0 ? 'blocked' : 'completed',
445
+ row_count: rows.length,
446
+ translation_count: translations.length,
447
+ applied_count: appliedCount,
448
+ skipped_count: skippedCount,
449
+ blockers,
450
+ files: {
451
+ translated_rows: resolvedOut,
452
+ translation_evidence: evidenceFile,
453
+ report: reportFile,
454
+ },
455
+ };
456
+ writeJsonArtifact(reportFile, report);
457
+ return report;
458
+ }
459
+ export async function runDatasetBilingualValidate(options) {
460
+ const requestedType = normalizeType(options.type);
461
+ const rows = filterRowsByType(materializeDatasetRows(options.inputPath, options.rawInput), requestedType);
462
+ const findings = scanRows(rows);
463
+ const files = validateFiles(options.outDir);
464
+ const datasetValidateImpl = options.datasetValidateImpl ?? runDatasetValidate;
465
+ const schemaGateOutDir = options.outDir
466
+ ? path.join(path.resolve(options.outDir), 'schema')
467
+ : null;
468
+ const schemaReport = await datasetValidateImpl({
469
+ inputPath: options.inputPath,
470
+ type: requestedType,
471
+ outDir: schemaGateOutDir,
472
+ rawInput: { rows: rows.map((row) => row.row) },
473
+ now: options.now,
474
+ schemas: options.schemas,
475
+ });
476
+ let processReport = null;
477
+ let flowReport = null;
478
+ if (options.outDir && (requestedType === 'process' || requestedType === 'auto')) {
479
+ const processRows = rows.filter((row) => row.kind === 'process').map((row) => row.row);
480
+ if (processRows.length > 0) {
481
+ const processRowsFile = path.join(path.resolve(options.outDir), 'qa-input-processes.jsonl');
482
+ writeJsonLinesArtifact(processRowsFile, processRows);
483
+ processReport = await (options.processQaImpl ?? runProcessQa)({
484
+ rowsFile: processRowsFile,
485
+ outDir: path.join(path.resolve(options.outDir), 'qa', 'process'),
486
+ now: () => options.now ?? new Date(),
487
+ });
488
+ }
489
+ }
490
+ if (options.outDir && (requestedType === 'flow' || requestedType === 'auto')) {
491
+ const flowRows = rows.filter((row) => row.kind === 'flow').map((row) => row.row);
492
+ if (flowRows.length > 0) {
493
+ const flowRowsFile = path.join(path.resolve(options.outDir), 'qa-input-flows.jsonl');
494
+ writeJsonLinesArtifact(flowRowsFile, flowRows);
495
+ flowReport = await (options.flowQaImpl ?? runFlowQa)({
496
+ rowsFile: flowRowsFile,
497
+ outDir: path.join(path.resolve(options.outDir), 'qa', 'flow'),
498
+ now: () => options.now ?? new Date(),
499
+ });
500
+ }
501
+ }
502
+ const blockerCount = findings.filter((finding) => finding.severity === 'blocker').length;
503
+ const warningCount = findings.filter((finding) => finding.severity === 'warning').length;
504
+ const report = {
505
+ schema_version: 1,
506
+ generated_at_utc: nowIso(options.now),
507
+ input_path: options.inputPath,
508
+ requested_type: requestedType,
509
+ status: blockerCount > 0 || schemaReport.counts.invalid > 0 ? 'blocked' : 'completed',
510
+ row_count: rows.length,
511
+ scan: {
512
+ finding_count: findings.length,
513
+ blocker_count: blockerCount,
514
+ warning_count: warningCount,
515
+ findings,
516
+ },
517
+ schema_gate: {
518
+ status: schemaReport.status,
519
+ valid: schemaReport.counts.valid,
520
+ invalid: schemaReport.counts.invalid,
521
+ report_file: schemaReport.files.report,
522
+ },
523
+ qa_gate: {
524
+ status: processReport || flowReport ? 'completed' : 'not_run',
525
+ process_report_file: processReport?.files.report ?? null,
526
+ flow_report_file: flowReport?.files.report ?? null,
527
+ },
528
+ files,
529
+ };
530
+ if (files.findings) {
531
+ writeJsonLinesArtifact(files.findings, findings);
532
+ }
533
+ if (files.report) {
534
+ writeJsonArtifact(files.report, report);
535
+ }
536
+ return report;
537
+ }
538
+ export const __testInternals = {
539
+ collectTranslationUnits,
540
+ normalizeType,
541
+ pointerFromSegments,
542
+ segmentsFromPointer,
543
+ scanRows,
544
+ };
545
+ //# sourceMappingURL=dataset-bilingual.js.map