@tiangong-lca/pcr 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ import { readdirSync, realpathSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { ConsumptionError, atPointer, entriesAt, object, paginate, pointerToken, preview, readJsonDocument } from "./consumption-data.mjs";
4
+
5
+ // Field paths, not a second TIDAS schema or consumer validation policy.
6
+ // Basis: public-spec candidate 0.2.3, revision f118660dbcbfbf736be74837cce0bf26cd177245.
7
+ const DATASETS = [
8
+ ["process", "processDataSet", "processInformation", "process data set"],
9
+ ["lifecyclemodel", "lifeCycleModelDataSet", "lifeCycleModelInformation", "life cycle model data set"],
10
+ ["flow", "flowDataSet", "flowInformation", "flow data set"],
11
+ ["flowproperty", "flowPropertyDataSet", "flowPropertiesInformation", "flow property data set"],
12
+ ["unitgroup", "unitGroupDataSet", "unitGroupInformation", "unit group data set"],
13
+ ["source", "sourceDataSet", "sourceInformation", "source data set"],
14
+ ["contact", "contactDataSet", "contactInformation", "contact data set"],
15
+ ["lciamethod", "LCIAMethodDataSet", "LCIAMethodInformation", "LCIA method data set"],
16
+ ];
17
+ export const INSPECTION_SECTIONS = ["summary", "exchanges", "instances", "references", "documents"];
18
+ const MAX_DOCUMENTS = 200;
19
+ const MAX_BYTES = 64 * 1024 * 1024;
20
+
21
+ function identify(document, required = true) {
22
+ const matches = DATASETS.filter(([, root]) => object(document.value) && Object.hasOwn(document.value, root));
23
+ if (matches.length !== 1 || !object(document.value[matches[0]?.[1]])) {
24
+ if (!required && matches.length === 0) return null;
25
+ throw new ConsumptionError("PCR_TIDAS_SHAPE", `Expected one native TIDAS dataset root in ${document.file}. Wrapped database rows must be exported as native dataset JSON first.`, { roots: DATASETS.map(([, root]) => root) });
26
+ }
27
+ const [kind, root, informationKey, referenceType] = matches[0];
28
+ const body = document.value[root];
29
+ const info = body[informationKey];
30
+ const uuid = info?.dataSetInformation?.["common:UUID"] ?? null;
31
+ const version = body.administrativeInformation?.publicationAndOwnership?.["common:dataSetVersion"] ?? null;
32
+ if ((uuid !== null && typeof uuid !== "string") || (version !== null && typeof version !== "string")) {
33
+ throw new ConsumptionError("PCR_TIDAS_IDENTITY", `Dataset UUID/version must be strings when supplied in ${document.file}. Inspect the native identity fields or run the TIDAS format validator.`, {
34
+ uuid_pointer: `/${root}/${informationKey}/dataSetInformation/common:UUID`,
35
+ version_pointer: `/${root}/administrativeInformation/publicationAndOwnership/common:dataSetVersion`,
36
+ });
37
+ }
38
+ return {
39
+ ...document, kind, root, informationKey, referenceType,
40
+ uuid, version,
41
+ };
42
+ }
43
+
44
+ export function documentIdentity(doc) {
45
+ return { file: doc.file, sha256: doc.sha256, kind: doc.kind, uuid: doc.uuid, version: doc.version };
46
+ }
47
+
48
+ function relatedFiles(directory) {
49
+ const base = path.resolve(directory);
50
+ const files = [];
51
+ try {
52
+ if (!statSync(base).isDirectory()) throw new Error("not a directory");
53
+ let visited = 0;
54
+ function visit(current, depth) {
55
+ if (depth > 12) throw new ConsumptionError("PCR_RELATED_LIMIT", "Related directory nesting exceeds 12 levels. Select a smaller local package.");
56
+ for (const entry of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
57
+ if (++visited > 5000) throw new ConsumptionError("PCR_RELATED_LIMIT", "Related directory exceeds 5000 entries. Select a smaller local package.");
58
+ const file = path.join(current, entry.name);
59
+ if (entry.isSymbolicLink()) throw new ConsumptionError("PCR_RELATED_SYMLINK", `Related packages must contain ordinary files/directories, not symlinks: ${file}`);
60
+ if (entry.isDirectory()) visit(file, depth + 1);
61
+ else if (entry.isFile() && entry.name.endsWith(".json")) {
62
+ files.push(file);
63
+ if (files.length > MAX_DOCUMENTS) throw new ConsumptionError("PCR_RELATED_LIMIT", `More than ${MAX_DOCUMENTS} JSON files. Select a smaller related package.`);
64
+ }
65
+ }
66
+ }
67
+ visit(base, 0);
68
+ } catch (error) {
69
+ if (error instanceof ConsumptionError) throw error;
70
+ throw new ConsumptionError("PCR_RELATED_READ", `Cannot inspect --related directory ${base}: ${error.message}`);
71
+ }
72
+ return files;
73
+ }
74
+
75
+ export function loadTidasContext({ input, related = null }) {
76
+ const primary = identify(readJsonDocument(input));
77
+ const documents = [primary];
78
+ const ignoredFiles = [];
79
+ let totalBytes = primary.bytes;
80
+ const primaryRealPath = realpathSync(primary.file);
81
+ for (const file of related ? relatedFiles(related) : []) {
82
+ if (realpathSync(file) === primaryRealPath) continue;
83
+ const raw = readJsonDocument(file);
84
+ totalBytes += raw.bytes;
85
+ if (totalBytes > MAX_BYTES) throw new ConsumptionError("PCR_RELATED_LIMIT", `Related input exceeds ${MAX_BYTES} bytes. Select a smaller local package.`);
86
+ const doc = identify(raw, false);
87
+ if (doc) documents.push(doc);
88
+ else ignoredFiles.push({ file: raw.file, sha256: raw.sha256, reason: "No recognized native TIDAS dataset root." });
89
+ }
90
+ return { primary, documents, ignoredFiles, related: related ? path.resolve(related) : null };
91
+ }
92
+
93
+ export function inputEvidence(doc, pointer) {
94
+ return { file: doc.file, sha256: doc.sha256, pointer };
95
+ }
96
+
97
+ export function extractReferences(context) {
98
+ const references = [];
99
+ for (const doc of context.documents) {
100
+ const stack = [{ value: doc.value, pointer: "" }];
101
+ while (stack.length) {
102
+ const { value, pointer } = stack.pop();
103
+ if (value === null || typeof value !== "object") continue;
104
+ if (object(value) && (Object.hasOwn(value, "@refObjectId") || (Object.hasOwn(value, "@type") && Object.hasOwn(value, "@uri")))) {
105
+ const uuid = value["@refObjectId"] ?? null;
106
+ const version = value["@version"] ?? null;
107
+ const type = value["@type"] ?? null;
108
+ const kind = DATASETS.find((entry) => entry[3] === type)?.[0];
109
+ const usableIdentity = typeof uuid === "string" && uuid.length > 0 && kind !== undefined
110
+ && (!Object.hasOwn(value, "@version") || (typeof version === "string" && version.length > 0));
111
+ const sameIdentity = usableIdentity ? context.documents.filter((candidate) => candidate.kind === kind && candidate.uuid === uuid) : [];
112
+ const candidates = sameIdentity.filter((candidate) => version === null || candidate.version === version);
113
+ const status = !usableIdentity ? "unresolved_identity" : candidates.length > 1 ? "ambiguous"
114
+ : candidates.length === 1 ? "resolved" : sameIdentity.length ? "version_mismatch" : "missing";
115
+ references.push({
116
+ source: inputEvidence(doc, pointer), uuid, version, type, uri: value["@uri"] ?? null,
117
+ status, version_selection: version === null ? "unspecified" : "exact",
118
+ candidates: (candidates.length ? candidates : sameIdentity).map(documentIdentity),
119
+ });
120
+ }
121
+ const entries = Object.entries(value);
122
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
123
+ const [key, child] = entries[index];
124
+ if (child !== null && typeof child === "object") stack.push({ value: child, pointer: `${pointer}/${pointerToken(key)}` });
125
+ }
126
+ }
127
+ }
128
+ return references;
129
+ }
130
+
131
+ export function inspectTidas({ input, related, section = "summary", pointer, page = 1, pageSize = 10 }) {
132
+ const context = loadTidasContext({ input, related });
133
+ const { primary } = context;
134
+ const references = extractReferences(context);
135
+ const report = {
136
+ schema_version: 1, inspection_kind: "tiangong-pcr-tidas-inspection",
137
+ primary: documentIdentity(primary), related_directory: context.related,
138
+ schema_validation: "not_performed",
139
+ scope: "Only explicitly supplied local JSON datasets; no URI is fetched or followed.",
140
+ counts: {
141
+ documents: context.documents.length, ignored_json_files: context.ignoredFiles.length,
142
+ references: references.length, resolved: references.filter((ref) => ref.status === "resolved").length,
143
+ unresolved: references.filter((ref) => ref.status !== "resolved").length,
144
+ },
145
+ limitations: [
146
+ "Inspection exposes fields and local references; it does not establish TIDAS validity or PCR compliance.",
147
+ "Missing local evidence is not proof that a lifecycle operation or upstream dataset does not exist.",
148
+ "Exchange units require flow, flow-property and unit-group evidence; names or amounts alone do not establish units.",
149
+ ],
150
+ };
151
+ if (pointer !== undefined) {
152
+ return { ...report, section: "pointer", source: inputEvidence(primary, pointer), value: atPointer(primary.value, pointer) };
153
+ }
154
+ if (!INSPECTION_SECTIONS.includes(section)) throw new ConsumptionError("PCR_INSPECT_SECTION", `Use --section ${INSPECTION_SECTIONS.join("|")}.`);
155
+ if (section === "summary") {
156
+ const infoPath = `/${primary.root}/${primary.informationKey}`;
157
+ const info = primary.value[primary.root][primary.informationKey] ?? {};
158
+ const fields = ["dataSetInformation", "quantitativeReference", "time", "geography", "technology"]
159
+ .filter((key) => Object.hasOwn(info, key))
160
+ .map((key) => ({ name: key, source: inputEvidence(primary, `${infoPath}/${key}`), ...preview(info[key]) }));
161
+ return { ...report, section, fields, ignored_files: context.ignoredFiles.slice(0, 10), ignored_files_truncated: context.ignoredFiles.length > 10 };
162
+ }
163
+ let items;
164
+ if (section === "documents") items = context.documents.map(documentIdentity);
165
+ else if (section === "references") items = references;
166
+ else {
167
+ const expectedKind = section === "exchanges" ? "process" : "lifecyclemodel";
168
+ if (primary.kind !== expectedKind) throw new ConsumptionError("PCR_INSPECT_SECTION", `${section} requires a ${expectedKind} input. Use --section summary or --pointer for this ${primary.kind}.`);
169
+ const pointer = section === "exchanges" ? "/processDataSet/exchanges/exchange"
170
+ : "/lifeCycleModelDataSet/lifeCycleModelInformation/technology/processes/processInstance";
171
+ let value;
172
+ try { value = atPointer(primary.value, pointer); }
173
+ catch (error) { if (error.code !== "PCR_POINTER_NOT_FOUND") throw error; }
174
+ items = entriesAt(value, pointer).map((entry) => ({ source: inputEvidence(primary, entry.pointer), ...preview(entry.value) }));
175
+ }
176
+ return { ...report, section, ...paginate(items, page, pageSize) };
177
+ }
@@ -1,9 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { OfflineLibrary } from "../../pcr-core/src/offline-library.mjs";
3
3
  import { withPcrSource } from "../../pcr-core/src/source-context.mjs";
4
- import { existsSync, readFileSync } from "node:fs";
4
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
+ import { GUIDANCE_TOPICS, selectGuidance } from "../../pcr-core/src/consumption-guidance.mjs";
8
+ import { inspectTidas, INSPECTION_SECTIONS } from "../../pcr-core/src/tidas-inspection.mjs";
9
+ import { prepareReview, checkReview } from "../../pcr-core/src/consumption-review.mjs";
10
+ import { calculate } from "../../pcr-core/src/consumption-calculation.mjs";
11
+ import { readJsonDocument, sha256 } from "../../pcr-core/src/consumption-data.mjs";
7
12
 
8
13
  import {
9
14
  CLASSIFICATION_COVERAGE_STATUSES,
@@ -46,7 +51,12 @@ const COMMAND_OPTIONS = {
46
51
  "coverage:list": new Set(["classification", "status", "page", "page-size"]),
47
52
  resolve: new Set(["classification", "pcr"]),
48
53
  show: new Set(["pcr", "lang"]),
49
- guidance: new Set(["pcr"]),
54
+ guidance: new Set(["pcr", "topic", "pointer", "page", "page-size", "output"]),
55
+ inspect: new Set(["input", "related", "section", "pointer", "page", "page-size", "output"]),
56
+ calculate: new Set(["input", "output"]),
57
+ review: new Set(),
58
+ "review:prepare": new Set(["pcr", "input", "related", "output"]),
59
+ "review:check": new Set(["pcr", "input", "related", "report", "output"]),
50
60
  "validate-model": new Set(["pcr", "input", "fail-on"]),
51
61
  "validate-dataset": new Set(["pcr", "input", "fail-on"]),
52
62
  "feedback:draft": new Set([
@@ -84,6 +94,11 @@ const COMMAND_DEFINITIONS = [
84
94
  { key: "resolve", command: "resolve", formats: ["json"], defaultFormat: "json" },
85
95
  { key: "show", command: "show", formats: ["markdown"], defaultFormat: "markdown" },
86
96
  { key: "guidance", command: "guidance", formats: ["json"], defaultFormat: "json" },
97
+ { key: "inspect", command: "inspect", formats: ["json"], defaultFormat: "json" },
98
+ { key: "calculate", command: "calculate", formats: ["json"], defaultFormat: "json" },
99
+ { key: "review", command: "review", formats: ["json"], defaultFormat: "json" },
100
+ { key: "review:prepare", command: "review", positional: ["prepare"], formats: ["json"], defaultFormat: "json" },
101
+ { key: "review:check", command: "review", positional: ["check"], formats: ["json"], defaultFormat: "json" },
87
102
  { key: "validate-model", command: "validate-model", formats: ["json"], defaultFormat: "json" },
88
103
  { key: "validate-dataset", command: "validate-dataset", formats: ["json"], defaultFormat: "json" },
89
104
  {
@@ -125,6 +140,10 @@ export function runTiangongPcr(argv) {
125
140
  if (!command || command === "help" || options.help) return runCommand(argv);
126
141
  if (command === "library" && positional.length === 0) throw new CliError("PCR_CLI_MISSING_SUBCOMMAND", "Use library info or library verify with --library <file>.");
127
142
  if (options.root && options.library) throw new CliError("PCR_CLI_SOURCE_CONFLICT", "Choose either --root or --library.");
143
+ if (["inspect", "calculate"].includes(command)) {
144
+ if (options.library || options["library-sha256"]) throw new CliError("PCR_CLI_SOURCE_UNUSED", `${command} uses local input only; omit --library and --library-sha256.`);
145
+ return runCommand(argv);
146
+ }
128
147
  const filename = options.library ?? (options.root ? null : process.env.PCR_LIBRARY ?? (existsSync(path.join(defaultRoot, "library/catalog.yaml")) ? null : installedLibrary()));
129
148
  if (!filename) {
130
149
  if (options["library-sha256"] || command === "library") throw new CliError("PCR_LIBRARY_REQUIRED", "Select a local snapshot with --library <library.sqlite>.");
@@ -234,8 +253,39 @@ function runCommand(argv, selected = null) {
234
253
  }
235
254
  if (command === "guidance") {
236
255
  requireOption(options, "pcr");
256
+ if (["topic", "pointer", "page", "page-size"].some((key) => options[key] !== undefined)) {
257
+ validatePointerOptions(options, "topic");
258
+ const report = selectGuidance({ root, pcrId: String(options.pcr), topic: options.topic, pointer: options.pointer, ...consumptionPage(options) });
259
+ return consumptionOutput(withConsumptionContinuation(report, "guidance", options), options);
260
+ }
261
+ if (options.output) return consumptionOutput(buildGuidance({ root, pcrId: String(options.pcr) }), options);
237
262
  return ok(writeOutput(buildGuidance({ root, pcrId: String(options.pcr) }), format, JSON.stringify));
238
263
  }
264
+ if (command === "inspect") {
265
+ requireOption(options, "input");
266
+ validatePointerOptions(options, "section");
267
+ if (options.pointer === undefined && (!options.section || options.section === "summary") && (options.page || options["page-size"])) {
268
+ throw new CliError("PCR_CLI_OPTION_CONFLICT", "Summary is not paginated. Choose --section references|exchanges|instances|documents for paging.");
269
+ }
270
+ const report = inspectTidas({ input: options.input, related: options.related, section: options.section, pointer: options.pointer, ...consumptionPage(options) });
271
+ return consumptionOutput(withConsumptionContinuation(report, "inspect", options), options);
272
+ }
273
+ if (command === "calculate") {
274
+ requireOption(options, "input");
275
+ const source = readJsonDocument(options.input);
276
+ return consumptionOutput({ ...calculate(source.value), request_source: { file: source.file, sha256: source.sha256 } }, options);
277
+ }
278
+ if (command === "review") {
279
+ if (!positional.length) throw new CliError("PCR_CLI_MISSING_SUBCOMMAND", "Use review prepare to create an Agent draft or review check to verify its envelope. Run review --help.");
280
+ requireOption(options, "input");
281
+ requireOption(options, "pcr");
282
+ const args = { root, pcrId: String(options.pcr), input: options.input, related: options.related };
283
+ if (positional[0] === "prepare") return consumptionOutput(prepareReview(args), options);
284
+ requireOption(options, "report");
285
+ const source = readJsonDocument(options.report);
286
+ const report = checkReview({ ...args, report: source.value });
287
+ return consumptionOutput({ ...report, report_source: { file: source.file, sha256: source.sha256 } }, options, report.envelope_valid ? 0 : 2);
288
+ }
239
289
  if (command === "validate-model") {
240
290
  requireOption(options, "pcr");
241
291
  requireOption(options, "input");
@@ -297,6 +347,48 @@ function ok(stdout, exitCode = 0) {
297
347
  return { stdout, stderr: "", exitCode };
298
348
  }
299
349
 
350
+ function consumptionPage(options) {
351
+ return {
352
+ page: positiveIntegerOption(options.page, "page", 1),
353
+ pageSize: positiveIntegerOption(options["page-size"], "page-size", 10, 100),
354
+ };
355
+ }
356
+
357
+ function validatePointerOptions(options, selector) {
358
+ if (options.pointer !== undefined && [selector, "page", "page-size"].some((key) => options[key] !== undefined)) {
359
+ throw new CliError("PCR_CLI_OPTION_CONFLICT", `Use --pointer alone, or --${selector} with optional pagination.`);
360
+ }
361
+ }
362
+
363
+ function withConsumptionContinuation(report, command, options) {
364
+ const parts = command === "guidance" ? commandPrefix(options) : ["tiangong-pcr"];
365
+ parts.push(command);
366
+ if (command === "guidance") parts.push(...sourceArguments(options));
367
+ for (const key of command === "guidance" ? ["pcr", "topic"] : ["input", "related", "section"]) {
368
+ if (options[key] !== undefined) parts.push(`--${key}`, shellToken(["input", "related"].includes(key) ? path.resolve(options[key]) : options[key]));
369
+ }
370
+ const pagination = report.pagination;
371
+ return {
372
+ ...report,
373
+ next_command: pagination?.has_more ? `${parts.join(" ")} --page ${pagination.page + 1} --page-size ${pagination.page_size} --format json` : null,
374
+ next_step: command === "guidance"
375
+ ? "Read relevant topics; use --pointer with an item's source.pointer for complete text. Judge applicability and cite the returned source."
376
+ : "Inspect paged exchanges/instances and references; use --pointer with source.pointer for full fields. Resolve evidence gaps before methodological conclusions.",
377
+ };
378
+ }
379
+
380
+ function consumptionOutput(report, options, exitCode = 0) {
381
+ const output = `${JSON.stringify(report, null, 2)}\n`;
382
+ if (options.output) {
383
+ const filename = path.resolve(String(options.output));
384
+ try { writeFileSync(filename, output, { encoding: "utf8", flag: "wx" }); }
385
+ catch (error) { throw new CliError("PCR_CLI_OUTPUT_WRITE", `Cannot create new output file ${filename}: ${error.message}. Choose an unused path in an existing directory.`); }
386
+ return ok(`${JSON.stringify({ artifact: filename, sha256: sha256(output), bytes: Buffer.byteLength(output), kind: report.report_kind ?? report.check_kind ?? report.guidance_kind ?? report.inspection_kind ?? report.calculation_kind, ...(report.envelope_valid === undefined ? {} : { envelope_valid: report.envelope_valid, methodology_approval: false }), next_step: "Read the saved artifact. Source inputs were not modified." }, null, 2)}\n`, exitCode);
387
+ }
388
+ if (output.length > 32768) throw new CliError("PCR_CLI_OUTPUT_LARGE", "Result exceeds 32768 characters. Use --output <new-file> for the complete artifact or request a smaller page/pointer.");
389
+ return ok(output, exitCode);
390
+ }
391
+
300
392
  function fail(error, requestedFormat, requestedRoot, selected = null) {
301
393
  const normalized = normalizeError(error);
302
394
  if (
@@ -1349,6 +1441,10 @@ This command rejects scaffold, incomplete, deprecated, or otherwise unusable PCR
1349
1441
 
1350
1442
  Options:
1351
1443
  --pcr <pcr-id> PCR id.
1444
+ --topic <topic> Bounded, cited selection (default overview when paging).
1445
+ --pointer <JSON Pointer> Read a complete value from the verified structured projection.
1446
+ --page <n> --page-size <n> Topic pagination; default 10, maximum 100 items.
1447
+ --output <new-file> Save complete JSON without overwriting an existing file.
1352
1448
  --format json Output format. JSON is recommended for Agents.
1353
1449
  --root <path> PCR repository root.
1354
1450
  --library <file> Local library.sqlite (or PCR_LIBRARY). Mutually exclusive with --root.
@@ -1358,16 +1454,119 @@ Options:
1358
1454
  JSON output includes:
1359
1455
  pcr, readiness, system_boundary, reference_flow, measurement_rules, process_map,
1360
1456
  process_inventory, allocation_rules, validation_rules, data_sources, and validation_notes.
1457
+ Without selection flags, the legacy full guidance shape is preserved.
1458
+ Selected guidance includes source pointers, content hashes, readiness and pagination.
1459
+
1460
+ Topics:
1461
+ ${GUIDANCE_TOPICS.join(", ")}
1462
+
1463
+ Example:
1464
+ tiangong-pcr guidance --pcr <pcr-id> --topic boundary --format json
1465
+ tiangong-pcr guidance --pcr <pcr-id> --pointer /system_boundary/rules/0 --format json
1361
1466
 
1362
1467
  Agent next step:
1363
- Build the foreground data package from the guidance, then run validate-dataset.
1364
- Use process or lifecyclemodel publication and validate-model only as a downstream projection.
1468
+ Choose general LCA authoring, optional TIDAS authoring or existing-data review.
1469
+ Judge applicability and declared scope. Cite returned source references; expand truncated items with --pointer.
1470
+ `;
1471
+ }
1472
+ if (definition.key === "inspect") {
1473
+ return `Usage: tiangong-pcr inspect --input <native-tidas.json> [options]
1474
+
1475
+ Read local TIDAS process/model/support datasets without changing them or fetching references.
1476
+ This is fact inspection, not TIDAS schema validation or PCR compliance. No PCR library is needed.
1477
+
1478
+ Options:
1479
+ --input <file> One native TIDAS JSON dataset; wrapped rows are unsupported.
1480
+ --related <directory> Explicit local package: at most 200 JSON files / 64 MiB total.
1481
+ --section <section> ${INSPECTION_SECTIONS.join("|")}; default summary.
1482
+ --pointer <JSON Pointer> Read a full original value; mutually exclusive with section/paging.
1483
+ --page <n> --page-size <n> Paged sections: default 10, maximum 100 items.
1484
+ --output <new-file> Save complete JSON; existing files are never overwritten.
1485
+ --format json --help JSON output or this help.
1486
+
1487
+ Output:
1488
+ Original file/hash/pointer evidence, explicitly partial scope, local reference statuses and next_command.
1489
+ Previews may be truncated; --pointer retrieves the complete value, --output saves large results.
1490
+ References match kind, UUID and requested version; duplicate matches are ambiguous.
1491
+ Missing/unsupported identities remain unresolved. Unspecified versions are disclosed.
1492
+ Related JSON without a dataset root is ignored explicitly; malformed JSON and symlinks fail.
1493
+
1494
+ Examples:
1495
+ tiangong-pcr inspect --input wheat.process.json --related ./datasets --section exchanges --format json
1496
+ tiangong-pcr inspect --input wheat.model.json --related ./datasets --section references --format json
1497
+
1498
+ Next:
1499
+ Inspect scope and reference basis, resolve missing evidence, select PCR guidance and use review prepare.
1500
+ Unit interpretation needs local flow, flow-property and unit-group evidence. Use the TIDAS toolkit for schema validation.
1501
+ `;
1502
+ }
1503
+ if (definition.key === "calculate") {
1504
+ return `Usage: tiangong-pcr calculate --input <calculation.json> [--output <new-file>] [--format json]
1505
+
1506
+ Perform reproducible arithmetic using explicit values, units, basis and evidence. No library is needed.
1507
+ All requests require operation, basis and evidence. Numeric values may be finite numbers or decimal strings.
1508
+
1509
+ Normalization example:
1510
+ {"operation":"normalize","amount":50,"unit":"kg N","source_reference":1000,
1511
+ "target_reference":1,"reference_unit":"kg grain","basis":"same moisture and gate",
1512
+ "evidence":"production record A; explicit source and target reference amounts"}
1513
+
1514
+ Conversion fields:
1515
+ operation: convert; amount, from_unit, to_unit, factor, basis, evidence.
1516
+ factor is supplied in to_unit / from_unit; no unit equivalence or factor is inferred.
1517
+
1518
+ Balance fields:
1519
+ operation: balance; inputs and outputs: [{"amount":1,"label":"record A"}],
1520
+ accumulation (explicit, even if zero), unit, basis, evidence.
1521
+ All terms must use the same physical basis and unit. Residual = inputs - outputs - accumulation.
1522
+
1523
+ Output:
1524
+ Request hash, supplied request, formula, result and interpretation limits; floating-point arithmetic.
1525
+ No threshold, physical validity or anomaly verdict is inferred. Missing values are not zero.
1526
+ Next: retain the calculation artifact and assess its applicability and tolerance in the Agent review.
1527
+ `;
1528
+ }
1529
+ if (definition.command === "review") {
1530
+ return `Usage:
1531
+ tiangong-pcr review prepare --pcr <pcr-id> --input <native-tidas.json> [--related <directory>] --output <new-draft.json>
1532
+ tiangong-pcr review check --pcr <pcr-id> --input <native-tidas.json> [--related <directory>] --report <review.json>
1533
+
1534
+ prepare creates an unreviewed Agent artifact with exact input/PCR bindings, scope and uncovered topics.
1535
+ The Agent investigates and edits that artifact. check verifies its envelope and evidence locations.
1536
+ Neither command runs an LLM, validates TIDAS schemas or approves methodology.
1537
+
1538
+ Options:
1539
+ --pcr <pcr-id> --input <file> Explicit PCR and native TIDAS input, unchanged throughout review.
1540
+ --related <directory> Same explicitly supplied local package used during inspection.
1541
+ --report <file> Agent review JSON, required only by check.
1542
+ --output <new-file> Save complete output without replacing an existing file.
1543
+ --root <path> Repository source, mutually exclusive with --library.
1544
+ --library <file> Local snapshot or PCR_LIBRARY / installed library.
1545
+ --library-sha256 <sha256:hex> Verify and pin that snapshot.
1546
+ --format json --help JSON output or this help.
1547
+
1548
+ Agent report:
1549
+ status: draft|reviewed; scope: description, applicability, limitations.
1550
+ coverage: [{topic, status: reviewed|not_applicable|not_reviewed, rationale}].
1551
+ findings: [{id, kind: confirmed_issue|suspected_anomaly|evidence_gap, severity: error|warning|info,
1552
+ observation, rationale, input_refs, pcr_refs, suggested_action, questions, alternative_explanations?}].
1553
+ Copy input_refs from inspect source objects and pcr_refs from selected guidance source objects.
1554
+ Cite the nearest existing parent for a missing field. pcr_refs may be empty for purely structural observations.
1555
+ Preserve free reasoning and unresolved questions; do not infer missing lifecycle stages from a partial process.
1556
+
1557
+ Exit codes:
1558
+ 0 preparation completed or report envelope valid (drafts may be valid);
1559
+ 1 usage/input/runtime error; 2 report shape, binding or evidence-location problem.
1560
+ envelope_valid and methodology_approval=false are separate from Agent findings and severity.
1561
+
1562
+ Next: use the Skill review reference, investigate applicable topics and share findings with their scope limits.
1365
1563
  `;
1366
1564
  }
1367
1565
  if (definition.key === "validate-model") {
1368
1566
  return `Usage: tiangong-pcr validate-model --pcr <pcr-id> --input <file> [options]
1369
1567
 
1370
- Check a process or lifecyclemodel draft against selected PCR guidance.
1568
+ Legacy diagnostic: check whether PCR reference qualifiers occur in the input text.
1569
+ JSON is treated as text; this does not parse TIDAS fields or perform semantic review.
1371
1570
 
1372
1571
  Options:
1373
1572
  --pcr <pcr-id> PCR id.
@@ -1387,14 +1586,15 @@ Exit codes:
1387
1586
  1 usage, input, or runtime error; 2 validation is inconclusive or the --fail-on threshold was reached.
1388
1587
 
1389
1588
  Agent next step:
1390
- Address findings in the model. If the PCR guidance is missing or unclear, create feedback draft.
1589
+ Treat matches as text-presence evidence only. Use inspect and review prepare/check for Agent-led TIDAS review.
1391
1590
  `;
1392
1591
  }
1393
1592
  if (definition.key === "validate-dataset") {
1394
1593
  return `Usage: tiangong-pcr validate-dataset --pcr <pcr-id> --input <file> [options]
1395
1594
 
1396
1595
  Check a foreground data package JSON object against the implemented subset of selected PCR guidance.
1397
- The report states exactly which requirement families were checked or skipped.
1596
+ Currently checks collection protocol ID presence, not values, units, evidence quality or TIDAS validity.
1597
+ This optional legacy package format is not required for general LCA authoring or native TIDAS review.
1398
1598
 
1399
1599
  Options:
1400
1600
  --pcr <pcr-id> PCR id.
@@ -1468,7 +1668,11 @@ Commands:
1468
1668
  coverage list --classification <system>:<version> [--status <coverage-status>] [--page <n>] [--page-size <n>] [--format json|table]
1469
1669
  resolve (--classification <system>:<version>:<code> | --pcr <pcr-id>) [--format json]
1470
1670
  show --pcr <pcr-id> [--lang en-US|zh-CN]
1471
- guidance --pcr <pcr-id> [--format json]
1671
+ guidance --pcr <pcr-id> [--topic <topic> | --pointer <pointer>] [--output <new-file>] [--format json]
1672
+ inspect --input <native-tidas.json> [--related <directory>] [--section <section> | --pointer <pointer>]
1673
+ calculate --input <calculation.json> [--output <new-file>]
1674
+ review prepare --pcr <pcr-id> --input <native-tidas.json> [--related <directory>] --output <new-draft.json>
1675
+ review check --pcr <pcr-id> --input <native-tidas.json> [--related <directory>] --report <review.json>
1472
1676
  validate-model --pcr <pcr-id> --input <file> [--format json] [--fail-on never|error|warning]
1473
1677
  validate-dataset --pcr <pcr-id> --input <file> [--format json] [--fail-on never|error|warning]
1474
1678
  feedback draft --type <type> [--pcr <pcr-id>] [--summary <text>]
@@ -1479,8 +1683,9 @@ Agent workflow:
1479
1683
  If a PCR id is available, run resolve --pcr <pcr-id> --format json. Supply exactly one selector.
1480
1684
  3. If no code is available, use tree/list to browse explicit material PCR hierarchy. list defaults to 10 records per page.
1481
1685
  4. Check resolution_status and readiness. Run guidance only for mapped material PCRs when usable_for_guidance is true.
1482
- 5. Build a foreground data package, then run validate-dataset.
1483
- 6. Only for downstream publication projections, build a process or lifecyclemodel and run validate-model.
1686
+ 5. Guide general LCA data authoring, optionally map a draft into TIDAS, or inspect existing TIDAS inputs.
1687
+ 6. For review, investigate applicable guidance and evidence, fill review prepare's draft, then use review check.
1688
+ CLI checks do not approve methodology. Existing validate-* commands are limited legacy diagnostics.
1484
1689
  7. If PCR guidance is missing or ambiguous, run feedback draft with the observed gap.
1485
1690
  `;
1486
1691
  }