@tiangong-ai/cli 0.0.42 → 0.0.43

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 (36) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +45 -18
  3. package/dist/research/orchestration.js +198 -7
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition.d.ts +7 -0
  6. package/dist/research/workspace/acquisition.js +37 -10
  7. package/dist/research/workspace/acquisition.js.map +1 -1
  8. package/dist/research/workspace/audit-bundle.d.ts +18 -0
  9. package/dist/research/workspace/audit-bundle.js +99 -3
  10. package/dist/research/workspace/audit-bundle.js.map +1 -1
  11. package/dist/research/workspace/content-evidence.d.ts +98 -0
  12. package/dist/research/workspace/content-evidence.js +715 -0
  13. package/dist/research/workspace/content-evidence.js.map +1 -0
  14. package/dist/research/workspace/evidence-ledger.d.ts +1 -1
  15. package/dist/research/workspace/evidence-ledger.js +2 -0
  16. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  17. package/dist/research/workspace/inference.d.ts +69 -0
  18. package/dist/research/workspace/inference.js +360 -0
  19. package/dist/research/workspace/inference.js.map +1 -0
  20. package/dist/research/workspace/native-activity.js +14 -5
  21. package/dist/research/workspace/native-activity.js.map +1 -1
  22. package/dist/research/workspace/projects.js +16 -0
  23. package/dist/research/workspace/projects.js.map +1 -1
  24. package/dist/research/workspace/publication-workflow.d.ts +26 -0
  25. package/dist/research/workspace/publication-workflow.js +313 -2
  26. package/dist/research/workspace/publication-workflow.js.map +1 -1
  27. package/dist/research/workspace/runtime.js +131 -30
  28. package/dist/research/workspace/runtime.js.map +1 -1
  29. package/dist/research/workspace/schemas.js +83 -4
  30. package/dist/research/workspace/schemas.js.map +1 -1
  31. package/dist/research/workspace/scientific-review.js +57 -0
  32. package/dist/research/workspace/scientific-review.js.map +1 -1
  33. package/dist/research/workspace/setup-catalog.js +2 -2
  34. package/dist/research/workspace/setup.js +1 -1
  35. package/dist/research/workspace/setup.js.map +1 -1
  36. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -17,8 +17,8 @@ checkPaths:
17
17
  - .docpact/config.yaml
18
18
  - docs/agents/**
19
19
  - src/**
20
- lastReviewedAt: 2026-08-18
21
- lastReviewedCommit: 9205e1b06678ef79042bee89bd3bca3fa77af515
20
+ lastReviewedAt: 2026-08-19
21
+ lastReviewedCommit: cd92a737acbdfd05a36c0d0d5b14c7e531ae6ee7
22
22
  ---
23
23
 
24
24
  # Tiangong AI CLI Contract
package/README.md CHANGED
@@ -13,7 +13,7 @@ checkPaths:
13
13
  - bin/**
14
14
  - src/**
15
15
  lastReviewedAt: 2026-08-18
16
- lastReviewedCommit: 9205e1b06678ef79042bee89bd3bca3fa77af515
16
+ lastReviewedCommit: 34a5c2a3af26fc56e9c9dc94fcbdabd91a6dd1d8
17
17
  ---
18
18
 
19
19
  # Tiangong AI CLI
@@ -417,14 +417,17 @@ tiangong-ai research project init top-journal-paper \
417
417
  --workspace /absolute/path/to/workspace --json
418
418
  ```
419
419
 
420
- The base evidence lifecycle remains
421
- `discover -> acquire -> analyze -> synthesize -> review -> close`, authored in
422
- the current interactive Codex or Claude Code host. A fresh independent reviewer
420
+ The base evidence lifecycle remains producer-authored in the current interactive
421
+ Codex or Claude Code host, but its frozen control sequence is now
422
+ `discover -> acquire -> typed decomposition/atoms -> content freeze -> inference freeze -> analyze -> Claim-Evidence Graph -> synthesize -> review -> close`.
423
+ A fresh independent reviewer
423
424
  must first pass three hash-bound scientific gates: `research-design` before
424
425
  discovery, a real-record and outcome-blind `evidence-construct` canary after
425
- acquisition freezes the evidence snapshot, and `pilot-methods` after that
426
- canary and before analysis. Evidence-construct coverage may cite only frozen
427
- snapshot source IDs and states. Its JSON canary artifacts are promoted and
426
+ acquisition and typed-content freeze, and `pilot-methods` after that canary and
427
+ before analysis. Acquisition always freezes its exact result, including honest
428
+ gaps; a stopped acquisition/content gate prevents inference without discarding
429
+ the acquired evidence. Evidence-construct coverage may cite only frozen
430
+ snapshot source IDs and exact content atoms. Its JSON canary artifacts are promoted and
428
431
  content-addressed through `--canary-artifacts`; reviewer prose cannot override
429
432
  an invented ID, unbound digest, or other mechanical failure.
430
433
 
@@ -457,14 +460,23 @@ audit manifest separately records the raw stored packet-file digest. This keeps
457
460
  packet identity and byte-level transfer verification explicit rather than
458
461
  overloading one hash with both meanings.
459
462
 
460
- After base closure, the current native host writes a final manuscript and
461
- schema-valid publication assessment. `research publication freeze` then
462
- content-addresses the Policy, scientific design and early reviews, evidence
463
- snapshot, base outputs, manuscript, assessment, and supplements.
463
+ After base closure, the current native host writes a final Markdown/plain-text
464
+ manuscript, schema-valid publication assessment, and an explicit submission
465
+ manifest. The manuscript must contain Abstract, Introduction, Methods, Results,
466
+ Discussion, Data availability, Code availability, and References. The
467
+ submission manifest must bind distinct absolute files for cover letter, title
468
+ page, reporting checklist, data availability, code availability, and source
469
+ data; figure/table index, extended data, and supplementary methods are optional.
470
+ `research publication freeze` then content-addresses the Policy, scientific
471
+ design and early reviews, acquisition/content/inference snapshots, reproduced
472
+ analysis, Claim-Evidence Graph, base outputs, manuscript, assessment,
473
+ supplements, role-complete submission files, and reproducibility manifest.
464
474
  Exactly four fresh independent sessions review that frozen generation:
465
475
  evidence, methods/reproducibility, domain/novelty, and journal-editor. A revised
466
- manuscript invalidates prior reviews; reviewer-session reuse is rejected from
467
- the append-only journal even if mutable cache state is removed. The raw opaque
476
+ manuscript invalidates prior reviews. Every reviewer must use the configured
477
+ agent family that differs from the native producer; changing only the session
478
+ ID is not independent. Reviewer-session reuse is rejected from the append-only
479
+ journal even if mutable cache state is removed. The raw opaque
468
480
  producer/reviewer session identifiers are accepted only at the command boundary;
469
481
  generation, packet, review, journal, and closure objects persist only their
470
482
  SHA-256 bindings.
@@ -474,6 +486,7 @@ tiangong-ai research schema show publication-assessment --json
474
486
  tiangong-ai research publication freeze top-journal-paper \
475
487
  --manuscript /absolute/path/to/final-manuscript.md \
476
488
  --assessment /absolute/path/to/publication-assessment.json \
489
+ --submission /absolute/path/to/submission-package.json \
477
490
  --producer-agent codex --producer-session OPAQUE_NATIVE_SESSION \
478
491
  --workspace /absolute/path/to/workspace --json
479
492
  tiangong-ai research publication status top-journal-paper \
@@ -486,9 +499,13 @@ The CLI returns a mechanically bounded ceiling:
486
499
  it. None of these states predicts or guarantees editorial acceptance.
487
500
 
488
501
  Before external handoff or archival, export and independently verify a portable
489
- audit directory. It contains the selected project, portable copies of admitted
502
+ audit directory. Export first revalidates the semantic acquisition, content,
503
+ inference, graph, and publication objects; a copied but stale/tampered chain is
504
+ rejected. Its manifest exposes their intrinsic IDs and hashes under
505
+ `researchChain`. It contains the selected project, portable copies of admitted
490
506
  inputs, formal evidence and artifact bytes, Policy/design/review objects,
491
- outputs, environment fingerprints, and journal proofs. Credentials, setup
507
+ outputs, environment fingerprints, and safe hash-preserving journal proof
508
+ derivatives. Credentials, setup
492
509
  sources, browser profiles, native active state, capsules, unrelated projects,
493
510
  and host-specific absolute paths are excluded.
494
511
 
@@ -673,9 +690,19 @@ resume criteria. Research then stops; it does not spend more budget on
673
690
  low-yield substitutes. If no lawful remaining route exists, the user must narrow
674
691
  or abandon the unsupported scope before a new reviewed generation can resume.
675
692
 
676
- Successful acquisition freezes an immutable evidence snapshot before analysis.
677
- The reviewer and mechanical closure bind and recheck the snapshot chain,
678
- ledger, receipts, selected artifacts, excerpts, analysis, and report. Refresh a
693
+ Acquisition freezes an immutable evidence snapshot even when lawful retrieval
694
+ ends with explicit gaps. Before inference, decompose every acquired PDF,
695
+ spreadsheet, archive, or structured file into exact lineage-bound producer-
696
+ readable artifacts; register line-range or JSON-Pointer evidence atoms; then
697
+ freeze `content-snapshot.json`. `research status --json` exposes acquisition,
698
+ content, inference, and graph state under `evidencePipeline` and does not direct
699
+ the operator to analysis while content preparation is missing or stopped.
700
+ Only passing acquisition/content gates and required scientific reviews can
701
+ freeze `inference-snapshot.json`. Analyze schema v2 binds that exact snapshot,
702
+ a reproduced analysis run, source IDs, atom IDs, and design claim IDs; the CLI
703
+ then creates `claim-evidence-graph.json` mechanically. The reviewer and
704
+ mechanical closure bind and recheck the full chain, ledger, receipts, selected
705
+ artifacts, excerpts, analysis, graph, and report. Refresh a
679
706
  closed result with `research project addendum SOURCE --to TARGET`; the original
680
707
  closure remains unchanged, the child snapshot records a mechanical delta, and
681
708
  default status hides the superseded project (`research status --all` shows full
@@ -13,6 +13,7 @@ import { fetchNativeCandidateSource } from "./workspace/broker.js";
13
13
  import { registerEvidenceArtifact } from "./workspace/artifacts.js";
14
14
  import { exportProjectAuditBundle, verifyProjectAuditBundle } from "./workspace/audit-bundle.js";
15
15
  import { loadCurrentEvidenceSnapshot } from "./workspace/acquisition.js";
16
+ import { freezeEvidenceContentSnapshot, loadCurrentEvidenceContentSnapshot, recordArtifactDecomposition, registerEvidenceAtom, } from "./workspace/content-evidence.js";
16
17
  import { inspectDiscoveryProgress } from "./workspace/discovery-status.js";
17
18
  import { inspectEvidenceAccessStatus } from "./workspace/evidence-exhaustion.js";
18
19
  import { recordDiscoveryAssessmentBatch } from "./workspace/discovery.js";
@@ -20,6 +21,7 @@ import { bindEvidenceDownload } from "./workspace/downloads.js";
20
21
  import { registerNativeDiscoveryCandidate } from "./workspace/evidence-ledger.js";
21
22
  import { recordNativeResearchActivity } from "./workspace/native-activity.js";
22
23
  import { readAndVerifyProjectInputPlan } from "./workspace/input-plan.js";
24
+ import { loadCurrentClaimEvidenceGraph, loadCurrentInferenceSnapshot, } from "./workspace/inference.js";
23
25
  import { addProjectInput, createProjectAddendum, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, setProjectDisposition, } from "./workspace/projects.js";
24
26
  import { evaluateProjectPreflight } from "./workspace/preflight.js";
25
27
  import { closePublication, freezePublicationManuscript, inspectPublicationStatus, preparePublicationReview, publicationAssessmentSchema, publicationReviewSchema, submitPublicationReview, } from "./workspace/publication-workflow.js";
@@ -29,7 +31,7 @@ import { abortNativeResearchStage, inspectNativeResearchStage, prepareNativeRese
29
31
  import { schemaForStage } from "./workspace/schemas.js";
30
32
  import { readAndVerifyScientificDesign, scientificDesignSchema, } from "./workspace/scientific-design.js";
31
33
  import { inspectScientificReviewStatus, prepareScientificReview, scientificGateAssessmentSchema, scientificReviewSchema, submitScientificReview, } from "./workspace/scientific-review.js";
32
- import { pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
34
+ import { isObject, pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
33
35
  import { doctorResearchWorkspace, initializeResearchWorkspace, requireResearchWorkspace, withWorkspaceLock, } from "./workspace/workspace.js";
34
36
  const COMMON_OPTIONS = { help: "boolean", json: "boolean" };
35
37
  const WORKSPACE_OPTIONS = { ...COMMON_OPTIONS, workspace: "string" };
@@ -77,7 +79,7 @@ export function researchOrchestrationHelp() {
77
79
  tiangong-ai research policy validate <project-id> [--workspace <path>] [--json]
78
80
  tiangong-ai research policy approve <project-id> --confirm [--acknowledge-defaults] [--workspace <path>] [--json]
79
81
  tiangong-ai research policy resolve <project-id> [--workspace <path>] [--json]
80
- tiangong-ai research publication freeze <project-id> --manuscript <absolute-file> --assessment <absolute-json> --producer-agent codex|claude --producer-session <opaque-id> [--supplements <absolute-json-array>] [--workspace <path>] [--json]
82
+ tiangong-ai research publication freeze <project-id> --manuscript <absolute-file> --assessment <absolute-json> --submission <absolute-json> --producer-agent codex|claude --producer-session <opaque-id> [--supplements <absolute-json-array>] [--workspace <path>] [--json]
81
83
  tiangong-ai research publication review prepare <project-id> --role evidence|methods-reproducibility|domain-novelty|journal-editor --reviewer-agent codex|claude --reviewer-session <opaque-id> [--workspace <path>] [--json]
82
84
  tiangong-ai research publication review submit <project-id> --role evidence|methods-reproducibility|domain-novelty|journal-editor --review <absolute-json> [--workspace <path>] [--json]
83
85
  tiangong-ai research publication status <project-id> [--workspace <path>] [--json]
@@ -107,6 +109,10 @@ export function researchOrchestrationHelp() {
107
109
  tiangong-ai research project evidence assessment record <project-id> --record <absolute-json> [--workspace <path>] [--json]
108
110
  tiangong-ai research project evidence download bind <project-id> --candidate <id> --record <absolute-json> [--workspace <path>] [--json]
109
111
  tiangong-ai research project evidence artifact register <project-id> --candidate <id> --path <absolute-file> [--download-binding <id> | --derived-from-artifact <id>] [--media-type <type>] [--source-url <https-url>] [--license <declared-license>] [--license-url <https-url>] [--host-type <type>] [--article-version <version>] [--workspace <path>] [--json]
112
+ tiangong-ai research project evidence decomposition record <project-id> --record <absolute-json> [--workspace <path>] [--json]
113
+ tiangong-ai research project evidence atom register <project-id> --record <absolute-json> [--workspace <path>] [--json]
114
+ tiangong-ai research project evidence content freeze <project-id> [--workspace <path>] [--json]
115
+ tiangong-ai research project evidence content status <project-id> [--workspace <path>] [--json]
110
116
  tiangong-ai research schema show <discover|acquire|analyze|synthesize|review|doctor|scientific-design|scientific-assessment-research-design|scientific-assessment-evidence-construct|scientific-assessment-pilot-methods|scientific-review-research-design|scientific-review-evidence-construct|scientific-review-pilot-methods|publication-assessment|publication-review-evidence|publication-review-methods-reproducibility|publication-review-domain-novelty|publication-review-journal-editor> [--compatibility claude-code] [--json]
111
117
  tiangong-ai research status [--project <project-id>] [--all] [--workspace <absolute-path>] [--json]
112
118
  tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
@@ -124,6 +130,7 @@ async function runPublication(argv, io) {
124
130
  manuscript: "string",
125
131
  assessment: "string",
126
132
  supplements: "string",
133
+ submission: "string",
127
134
  "producer-agent": "string",
128
135
  "producer-session": "string",
129
136
  }, "research publication freeze");
@@ -133,8 +140,9 @@ async function runPublication(argv, io) {
133
140
  const manuscriptPath = strictString(args, "manuscript");
134
141
  const assessmentPath = strictString(args, "assessment");
135
142
  const producerSessionId = strictString(args, "producer-session");
136
- if (!manuscriptPath || !assessmentPath || !producerSessionId) {
137
- throw new CliError("research publication freeze requires --manuscript, --assessment, --producer-agent, and --producer-session.", { code: "RESEARCH_PUBLICATION_ARGUMENT_REQUIRED", exitCode: 2 });
143
+ const submissionPath = strictString(args, "submission");
144
+ if (!manuscriptPath || !assessmentPath || !submissionPath || !producerSessionId) {
145
+ throw new CliError("research publication freeze requires --manuscript, --assessment, --submission, --producer-agent, and --producer-session.", { code: "RESEARCH_PUBLICATION_ARGUMENT_REQUIRED", exitCode: 2 });
138
146
  }
139
147
  const root = await workspaceFromArgs(args);
140
148
  const supplementsPath = strictString(args, "supplements");
@@ -144,6 +152,7 @@ async function runPublication(argv, io) {
144
152
  manuscriptPath,
145
153
  assessmentPath,
146
154
  supplementPaths: supplementsPath ? await readAbsolutePathArray(supplementsPath) : [],
155
+ submissionFiles: await readSubmissionFiles(submissionPath),
147
156
  producerAgent: publicationAgent(strictString(args, "producer-agent"), "producer"),
148
157
  producerSessionId,
149
158
  }), args);
@@ -949,6 +958,66 @@ async function runProject(argv, io) {
949
958
  writeJson(io, result, args);
950
959
  return 0;
951
960
  }
961
+ if (evidenceAction === "decomposition") {
962
+ const [decompositionAction, ...decompositionRest] = evidenceRest;
963
+ if (decompositionAction !== "record") {
964
+ throw unknownAction("research project evidence decomposition", decompositionAction ?? "");
965
+ }
966
+ const args = parseStrictArgs(decompositionRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence decomposition record");
967
+ if (strictBoolean(args, "help"))
968
+ return writeHelp(io);
969
+ const projectId = onePositional(args.positionals, "research project evidence decomposition record");
970
+ const recordPath = strictString(args, "record");
971
+ if (!recordPath) {
972
+ throw new CliError("decomposition record requires --record.", {
973
+ code: "RESEARCH_DECOMPOSITION_INVALID",
974
+ exitCode: 2,
975
+ });
976
+ }
977
+ const root = await workspaceFromArgs(args);
978
+ const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_DECOMPOSITION_INVALID");
979
+ const result = await withWorkspaceLock(root, "research.decomposition.record", () => recordArtifactDecomposition({ root, projectId, value: record }));
980
+ writeJson(io, result, args);
981
+ return 0;
982
+ }
983
+ if (evidenceAction === "atom") {
984
+ const [atomAction, ...atomRest] = evidenceRest;
985
+ if (atomAction !== "register") {
986
+ throw unknownAction("research project evidence atom", atomAction ?? "");
987
+ }
988
+ const args = parseStrictArgs(atomRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence atom register");
989
+ if (strictBoolean(args, "help"))
990
+ return writeHelp(io);
991
+ const projectId = onePositional(args.positionals, "research project evidence atom register");
992
+ const recordPath = strictString(args, "record");
993
+ if (!recordPath) {
994
+ throw new CliError("atom register requires --record.", {
995
+ code: "RESEARCH_EVIDENCE_ATOM_INVALID",
996
+ exitCode: 2,
997
+ });
998
+ }
999
+ const root = await workspaceFromArgs(args);
1000
+ const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_EVIDENCE_ATOM_INVALID");
1001
+ const result = await withWorkspaceLock(root, "research.evidence-atom.register", () => registerEvidenceAtom({ root, projectId, value: record }));
1002
+ writeJson(io, result, args);
1003
+ return 0;
1004
+ }
1005
+ if (evidenceAction === "content") {
1006
+ const [contentAction, ...contentRest] = evidenceRest;
1007
+ if (contentAction !== "freeze" && contentAction !== "status") {
1008
+ throw unknownAction("research project evidence content", contentAction ?? "");
1009
+ }
1010
+ const args = parseStrictArgs(contentRest, WORKSPACE_OPTIONS, `research project evidence content ${contentAction}`);
1011
+ if (strictBoolean(args, "help"))
1012
+ return writeHelp(io);
1013
+ const projectId = onePositional(args.positionals, `research project evidence content ${contentAction}`);
1014
+ const root = await workspaceFromArgs(args);
1015
+ const result = contentAction === "freeze"
1016
+ ? await withWorkspaceLock(root, "research.evidence-content.freeze", () => freezeEvidenceContentSnapshot(root, projectId))
1017
+ : await loadCurrentEvidenceContentSnapshot(root, projectId);
1018
+ writeJson(io, result, args);
1019
+ return 0;
1020
+ }
952
1021
  if (evidenceAction !== "fetch") {
953
1022
  throw unknownAction("research project evidence", evidenceAction ?? "");
954
1023
  }
@@ -1272,7 +1341,8 @@ async function runStatus(argv, io) {
1272
1341
  projects: await Promise.all(projects.map(async (project) => {
1273
1342
  const current = refreshProject(project);
1274
1343
  const nativeStage = await inspectNativeResearchStage(root, current);
1275
- const snapshot = await inspectSnapshotForStatus(root, current.id);
1344
+ const evidencePipeline = await inspectEvidencePipelineForStatus(root, current);
1345
+ const snapshot = evidencePipeline.acquisition;
1276
1346
  const readyPackage = nextReadyPackage(current)?.id ?? null;
1277
1347
  const scientificReview = await inspectScientificReviewStatus(root, current.id);
1278
1348
  const evidenceAccess = current.scientificDesign
@@ -1290,12 +1360,13 @@ async function runStatus(argv, io) {
1290
1360
  handoff: current.handoff,
1291
1361
  evidenceState: current.evidenceState,
1292
1362
  snapshot,
1363
+ evidencePipeline,
1293
1364
  nativeStage,
1294
1365
  scientificReview,
1295
1366
  evidenceAccess,
1296
1367
  publication,
1297
1368
  readyPackage,
1298
- recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage, scientificReview, publication),
1369
+ recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage, scientificReview, evidencePipeline, publication),
1299
1370
  usage: current.usage,
1300
1371
  inputs: current.inputs,
1301
1372
  packages: current.packages,
@@ -1306,6 +1377,94 @@ async function runStatus(argv, io) {
1306
1377
  writeJson(io, result, args);
1307
1378
  return 0;
1308
1379
  }
1380
+ async function inspectEvidencePipelineForStatus(root, project) {
1381
+ const projectRoot = join(workspacePaths(root).projects, project.id);
1382
+ const acquisition = await inspectSnapshotForStatus(root, project.id);
1383
+ const contentPath = join(projectRoot, "outputs", "content-snapshot.json");
1384
+ let content = { status: "absent" };
1385
+ if (await pathExists(contentPath)) {
1386
+ try {
1387
+ const snapshot = await loadCurrentEvidenceContentSnapshot(root, project.id);
1388
+ content = {
1389
+ status: "verified",
1390
+ snapshotId: snapshot.snapshotId,
1391
+ snapshotSha256: snapshot.snapshotSha256,
1392
+ decompositionCount: snapshot.decompositions.length,
1393
+ atomCount: snapshot.atoms.length,
1394
+ sourceCount: snapshot.sourceCoverage.length,
1395
+ roleCount: snapshot.roleCoverage.length,
1396
+ insufficientRoleIds: snapshot.roleCoverage
1397
+ .filter((role) => role.decision === "insufficient")
1398
+ .map((role) => role.roleId),
1399
+ gate: snapshot.gate,
1400
+ };
1401
+ }
1402
+ catch (error) {
1403
+ content = {
1404
+ status: "invalid",
1405
+ code: error instanceof CliError ? error.code : "RESEARCH_EVIDENCE_CONTENT_SNAPSHOT_INVALID",
1406
+ };
1407
+ }
1408
+ }
1409
+ const inferencePath = join(projectRoot, "outputs", "inference-snapshot.json");
1410
+ let inference = { status: "absent" };
1411
+ if (await pathExists(inferencePath)) {
1412
+ try {
1413
+ const snapshot = await loadCurrentInferenceSnapshot(root, project.id);
1414
+ inference = {
1415
+ status: "verified",
1416
+ snapshotId: snapshot.snapshotId,
1417
+ snapshotSha256: snapshot.snapshotSha256,
1418
+ sourceCount: snapshot.sources.length,
1419
+ atomCount: snapshot.atoms.length,
1420
+ claimCount: snapshot.claims.length,
1421
+ artifactCount: snapshot.artifactSha256s.length,
1422
+ gate: snapshot.gate,
1423
+ };
1424
+ }
1425
+ catch (error) {
1426
+ inference = {
1427
+ status: "invalid",
1428
+ code: error instanceof CliError ? error.code : "RESEARCH_INFERENCE_SNAPSHOT_INVALID",
1429
+ };
1430
+ }
1431
+ }
1432
+ else if (acquisition.gate?.decision === "stop") {
1433
+ inference = { status: "blocked", gate: acquisition.gate };
1434
+ }
1435
+ else if (content.gate?.decision === "stop") {
1436
+ inference = { status: "blocked", gate: content.gate };
1437
+ }
1438
+ else if (project.scientificDesign && content.status !== "verified") {
1439
+ inference = {
1440
+ status: "blocked",
1441
+ code: "RESEARCH_EVIDENCE_CONTENT_SNAPSHOT_REQUIRED",
1442
+ };
1443
+ }
1444
+ const graphPath = join(projectRoot, "outputs", "claim-evidence-graph.json");
1445
+ let claimGraph = { status: "absent" };
1446
+ if (await pathExists(graphPath)) {
1447
+ try {
1448
+ const graph = await loadCurrentClaimEvidenceGraph(root, project.id);
1449
+ claimGraph = {
1450
+ status: "verified",
1451
+ graphId: graph.graphId,
1452
+ graphSha256: graph.graphSha256,
1453
+ analysisSha256: graph.analysisSha256,
1454
+ analysisRunId: graph.analysisRunId,
1455
+ nodeCount: graph.nodes.length,
1456
+ edgeCount: graph.edges.length,
1457
+ };
1458
+ }
1459
+ catch (error) {
1460
+ claimGraph = {
1461
+ status: "invalid",
1462
+ code: error instanceof CliError ? error.code : "RESEARCH_CLAIM_EVIDENCE_GRAPH_INVALID",
1463
+ };
1464
+ }
1465
+ }
1466
+ return { acquisition, content, inference, claimGraph };
1467
+ }
1309
1468
  async function inspectEvidenceAccessForStatus(root, projectId) {
1310
1469
  try {
1311
1470
  return await inspectEvidenceAccessStatus(root, projectId);
@@ -1352,6 +1511,8 @@ async function inspectSnapshotForStatus(root, projectId) {
1352
1511
  parentSnapshotId: snapshot.parentSnapshotId,
1353
1512
  sourceCount: snapshot.sources.length,
1354
1513
  artifactCount: snapshot.artifacts.length,
1514
+ gapCount: snapshot.gaps.length,
1515
+ gate: snapshot.inferenceGate,
1355
1516
  delta: snapshot.delta,
1356
1517
  };
1357
1518
  }
@@ -1374,7 +1535,7 @@ async function inspectPublicationForStatus(root, projectId) {
1374
1535
  };
1375
1536
  }
1376
1537
  }
1377
- function projectRecommendedAction(root, project, readyPackage, nativeStage, scientificReview, publication) {
1538
+ function projectRecommendedAction(root, project, readyPackage, nativeStage, scientificReview, evidencePipeline, publication) {
1378
1539
  if (project.lineage.supersededBy) {
1379
1540
  return `Continue with superseding project ${project.lineage.supersededBy}.`;
1380
1541
  }
@@ -1398,6 +1559,23 @@ function projectRecommendedAction(root, project, readyPackage, nativeStage, scie
1398
1559
  if (nativeStage.status === "active") {
1399
1560
  return nativeStage.recommendedAction ?? "Resume the active native stage.";
1400
1561
  }
1562
+ if (readyPackage === "analyze") {
1563
+ if (evidencePipeline.content.status === "absent") {
1564
+ return `Decompose every acquired full-text/data artifact, register exact evidence atoms, then freeze typed content: tiangong-ai research project evidence content freeze ${project.id} --workspace ${root}`;
1565
+ }
1566
+ if (evidencePipeline.content.status === "invalid") {
1567
+ return `Typed evidence content is invalid (${evidencePipeline.content.code ?? "unknown"}); repair exact decomposition/atom bindings before analysis.`;
1568
+ }
1569
+ if (evidencePipeline.content.gate?.decision === "stop") {
1570
+ return "Typed evidence coverage is insufficient; complete lawful gap filling or request a scope/access handoff instead of starting inference.";
1571
+ }
1572
+ if (evidencePipeline.acquisition.gate?.decision === "stop") {
1573
+ return "Frozen acquisition gaps block inference; request a scope/access handoff after all acquired content is decomposed instead of continuing substitute search.";
1574
+ }
1575
+ if (evidencePipeline.inference.status === "invalid") {
1576
+ return `Inference snapshot is invalid (${evidencePipeline.inference.code ?? "unknown"}); repair its frozen upstream bindings before analysis.`;
1577
+ }
1578
+ }
1401
1579
  if (scientificReview.nextGate) {
1402
1580
  const gate = scientificReview.nextGate;
1403
1581
  if (gate.status === "stopped") {
@@ -1712,6 +1890,19 @@ async function readAbsolutePathArray(path, option = "supplements") {
1712
1890
  }
1713
1891
  return value;
1714
1892
  }
1893
+ async function readSubmissionFiles(path) {
1894
+ const value = await readBoundedJsonRecord(path, "--submission", "RESEARCH_PUBLICATION_SUBMISSION_PACKAGE_INVALID");
1895
+ if (value.schemaVersion !== 1 ||
1896
+ !Array.isArray(value.files) ||
1897
+ value.files.some((file) => !isObject(file) ||
1898
+ typeof file.role !== "string" ||
1899
+ typeof file.path !== "string" ||
1900
+ !isAbsolute(file.path) ||
1901
+ resolve(file.path) !== file.path)) {
1902
+ throw new CliError("--submission must declare schemaVersion 1 and role/path entries with absolute canonical paths.", { code: "RESEARCH_PUBLICATION_SUBMISSION_PACKAGE_INVALID", exitCode: 2 });
1903
+ }
1904
+ return value.files;
1905
+ }
1715
1906
  function integerOption(value, fallback, label) {
1716
1907
  if (!value)
1717
1908
  return fallback;