@tiangong-ai/cli 0.0.57 → 0.0.58

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/AGENTS.md +2 -2
  2. package/README.md +106 -3
  3. package/dist/research/orchestration.js +96 -26
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-forecast.d.ts +18 -0
  6. package/dist/research/workspace/acquisition-forecast.js +116 -0
  7. package/dist/research/workspace/acquisition-forecast.js.map +1 -0
  8. package/dist/research/workspace/acquisition.d.ts +13 -1
  9. package/dist/research/workspace/acquisition.js +71 -62
  10. package/dist/research/workspace/acquisition.js.map +1 -1
  11. package/dist/research/workspace/artifacts.d.ts +18 -0
  12. package/dist/research/workspace/artifacts.js +40 -2
  13. package/dist/research/workspace/artifacts.js.map +1 -1
  14. package/dist/research/workspace/content-evidence.d.ts +37 -0
  15. package/dist/research/workspace/content-evidence.js +287 -137
  16. package/dist/research/workspace/content-evidence.js.map +1 -1
  17. package/dist/research/workspace/downloads.js +2 -2
  18. package/dist/research/workspace/downloads.js.map +1 -1
  19. package/dist/research/workspace/evidence-content-schema.d.ts +14 -0
  20. package/dist/research/workspace/evidence-content-schema.js +123 -0
  21. package/dist/research/workspace/evidence-content-schema.js.map +1 -0
  22. package/dist/research/workspace/evidence-ledger.d.ts +1 -1
  23. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  24. package/dist/research/workspace/evidence-role-coverage.d.ts +35 -0
  25. package/dist/research/workspace/evidence-role-coverage.js +81 -0
  26. package/dist/research/workspace/evidence-role-coverage.js.map +1 -0
  27. package/dist/research/workspace/journal.d.ts +2 -0
  28. package/dist/research/workspace/journal.js +6 -0
  29. package/dist/research/workspace/journal.js.map +1 -1
  30. package/dist/research/workspace/preflight.d.ts +2 -0
  31. package/dist/research/workspace/preflight.js +14 -5
  32. package/dist/research/workspace/preflight.js.map +1 -1
  33. package/dist/research/workspace/projects.d.ts +7 -0
  34. package/dist/research/workspace/projects.js +65 -19
  35. package/dist/research/workspace/projects.js.map +1 -1
  36. package/dist/research/workspace/review-executor.d.ts +2 -0
  37. package/dist/research/workspace/review-executor.js +69 -1
  38. package/dist/research/workspace/review-executor.js.map +1 -1
  39. package/dist/research/workspace/runtime.d.ts +8 -1
  40. package/dist/research/workspace/runtime.js +54 -28
  41. package/dist/research/workspace/runtime.js.map +1 -1
  42. package/dist/research/workspace/schemas.js +10 -2
  43. package/dist/research/workspace/schemas.js.map +1 -1
  44. package/dist/research/workspace/scientific-design.d.ts +2 -1
  45. package/dist/research/workspace/scientific-design.js +39 -2
  46. package/dist/research/workspace/scientific-design.js.map +1 -1
  47. package/dist/research/workspace/scientific-review-execution.d.ts +19 -0
  48. package/dist/research/workspace/scientific-review-execution.js +445 -0
  49. package/dist/research/workspace/scientific-review-execution.js.map +1 -0
  50. package/dist/research/workspace/scientific-review.d.ts +110 -3
  51. package/dist/research/workspace/scientific-review.js +113 -20
  52. package/dist/research/workspace/scientific-review.js.map +1 -1
  53. package/dist/research/workspace/setup-catalog.js +2 -2
  54. package/dist/research/workspace/types.d.ts +4 -1
  55. package/dist/research/workspace/types.js +14 -1
  56. package/dist/research/workspace/types.js.map +1 -1
  57. package/dist/research/workspace/workspace.js +13 -1
  58. package/dist/research/workspace/workspace.js.map +1 -1
  59. 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-09-01
21
- lastReviewedCommit: ca716c2fea59d0a8085aa32dbd8f65a686b3353e
20
+ lastReviewedAt: 2026-09-02
21
+ lastReviewedCommit: 8a18ba69f432d2c639517549157ef29545722cff
22
22
  ---
23
23
 
24
24
  # Tiangong AI CLI Contract
package/README.md CHANGED
@@ -12,8 +12,8 @@ checkPaths:
12
12
  - package.json
13
13
  - bin/**
14
14
  - src/**
15
- lastReviewedAt: 2026-09-01
16
- lastReviewedCommit: ca716c2fea59d0a8085aa32dbd8f65a686b3353e
15
+ lastReviewedAt: 2026-09-02
16
+ lastReviewedCommit: 8a18ba69f432d2c639517549157ef29545722cff
17
17
  ---
18
18
 
19
19
  # Tiangong AI CLI
@@ -650,7 +650,33 @@ tiangong-ai research project scientific review submit top-journal-paper \
650
650
  --workspace /absolute/path/to/workspace --json
651
651
  ```
652
652
 
653
- Repeat the same prepare/submit route for `evidence-construct`, adding an
653
+ For a prepared packet, use explicit isolated execution instead of writing a
654
+ custom reviewer runner:
655
+
656
+ ```bash
657
+ tiangong-ai research reviewer status --workspace /absolute/path/to/workspace --json
658
+ tiangong-ai research project scientific review execute top-journal-paper \
659
+ --role research-design --confirm-review-cost \
660
+ --workspace /absolute/path/to/workspace --json
661
+ ```
662
+
663
+ Confirm the bounded cost before execution. The command uses the configured
664
+ `native-direct` or `sandbox-bridge` reviewer, copies exact hash-verified packet
665
+ inputs and human Policy documents into its capsule, and submits only a
666
+ schema-valid, packet/session-bound review. A saved successful execution is
667
+ replayed without another model call after revalidating its immutable proof.
668
+ Failures require explicit `--retry` and remain bounded by the attempt budget;
669
+ unreported usage and interrupted wall time retain conservative reservations.
670
+ A nonpassing mechanical packet can receive an independent stop verdict, never
671
+ an override. The existing manual submit command remains available for an exact
672
+ independent review.
673
+
674
+ Reviewer status is read-only and transport-aware. Native-direct does not
675
+ require a bridge connection. Smoke configuration readiness is explicitly not
676
+ production readiness and does not demand an attestation that smoke mode never
677
+ writes. Production still requires its current reviewer doctor attestation.
678
+
679
+ Repeat the same prepare/execute route for `evidence-construct`, adding an
654
680
  owner-reviewed JSON array of absolute canonical canary paths with
655
681
  `--canary-artifacts /absolute/path/to/canary-paths.json`, and then for
656
682
  `pilot-methods` at its stage boundary. A top-journal fork or addendum is a
@@ -966,6 +992,25 @@ resume criteria. Research then stops; it does not spend more budget on
966
992
  low-yield substitutes. If no lawful remaining route exists, the user must narrow
967
993
  or abandon the unsupported scope before a new reviewed generation can resume.
968
994
 
995
+ Before submitting an acquisition audit, inspect its exact current eligibility:
996
+
997
+ ```bash
998
+ tiangong-ai research project evidence content forecast PROJECT \
999
+ --input /absolute/path/acquisition-audit.json --workspace /absolute/workspace --json
1000
+ ```
1001
+
1002
+ This read-only check uses the freeze path's source projection and coverage
1003
+ rules, checks registered artifact bytes and provenance, and forecasts required
1004
+ roles from potentially assignable source dimensions. Exit `3` identifies known
1005
+ deficits; exit `0` means only potential eligibility, never successful atom
1006
+ registration, content freeze, independence certification, or review. Pending
1007
+ input materialization, decomposition, and exact atom assignments remain
1008
+ explicit. Re-run after material acquisition changes, not after every atom.
1009
+ Flat `sourceTypeRequirements` arrays mean **all-of**. A design may instead use
1010
+ `{"allOf":["academic-paper"],"anyOf":["government","industry"],"atLeast":{"count":2,"from":["academic-paper","government","industry"]}}`;
1011
+ every present group applies, and counts use distinct types. Forecast, typed
1012
+ content, and scientific review use the same source-type evaluator.
1013
+
969
1014
  Acquisition freezes an immutable evidence snapshot even when lawful retrieval
970
1015
  ends with explicit gaps. Before inference, decompose every acquired PDF,
971
1016
  spreadsheet, archive, or structured file into exact lineage-bound producer-
@@ -984,6 +1029,46 @@ closure remains unchanged, the child snapshot records a mechanical delta, and
984
1029
  default status hides the superseded project (`research status --all` shows full
985
1030
  lineage).
986
1031
 
1032
+ For many content records, use `research project evidence decomposition batch
1033
+ <project-id> --record <absolute-json>` or `research project evidence atom batch
1034
+ <project-id> --record <absolute-json>`, with `--workspace` and `--json` as needed.
1035
+ The input is `{"schemaVersion":1,"records":[...]}`; each item has exactly the same
1036
+ schema and validation as the corresponding single-record command. A batch is
1037
+ bounded to 500 records and 4 MiB of input. The CLI verifies acquisition/artifact
1038
+ bindings once per batch, groups atoms by artifact to read and parse each referenced
1039
+ document once without retaining all files in memory, and commits one hash-bound
1040
+ immutable envelope through one ledger event. A bad item commits nothing; identical
1041
+ replay is idempotent and a changed ID is rejected. Uncommitted envelopes are never
1042
+ visible and a retry can safely complete their commit. `work` reports deterministic
1043
+ verification, read, and append counts; no persistent verification cache is trusted.
1044
+
1045
+ Retrieve these CLI-owned input schemas with `research schema show evidence-atom`,
1046
+ `artifact-decomposition`, `evidence-atom-batch`, or `artifact-decomposition-batch`
1047
+ and `--json`. Help and command intake share the batch-limit constants. Schemas
1048
+ validate structure; execution still checks exact artifact/lineage bindings,
1049
+ locator semantics, stage, and sensitive content. A schema pass is not admission.
1050
+
1051
+ Before a potentially large download, use the offline command
1052
+ `research project evidence artifact preflight --bytes <known-bytes> --workspace
1053
+ <path> --json`, or replace `--bytes` with `--path <exact-local-file>` for a stat-only
1054
+ check. `budget.maxBytesPerArtifact` bounds one acquired/downloaded file;
1055
+ `budget.maxBytesPerPackage` separately bounds aggregate generated stage outputs.
1056
+ Both are exposed in preflight, and native packets expose `maxArtifactBytes` beside
1057
+ `maxOutputBytes`. Their defaults remain 20 MiB in smoke mode and 512 MiB in
1058
+ production. A missing artifact field in an existing configuration retains its
1059
+ existing package limit in memory without rewriting the owner's file; an explicit
1060
+ invalid limit is rejected. Preflight exit 3 means stop and request a provider-side
1061
+ subset/filter or smaller official export preserving required variables/provenance.
1062
+ A size pass is not content acceptance: download binding, format, archive-expansion,
1063
+ SHA-256, and snapshot checks still apply. This is not a large-file streaming or
1064
+ external-reference bypass.
1065
+
1066
+ Use acquisition `gaps` only for unresolved blocking evidence deficiencies;
1067
+ `limitations` holds non-blocking scope constraints and intentionally sealed
1068
+ outcomes. Future Policy obligations stay in the scientific design and appear
1069
+ in `futureGateObligations`, including ordinary planned rules without pending
1070
+ model/uncertainty objects. Their declared gate remains authoritative.
1071
+
987
1072
  Accepted local inputs are normalized into immutable input-backed artifacts while
988
1073
  the acquire package is still active. JSON, CSV, Markdown, and plain-text inputs
989
1074
  therefore have an atom-capable identity even when the producer omitted an
@@ -1001,6 +1086,12 @@ JSON/JSONL `projectId`, so historical blocked siblings do not alter its exit
1001
1086
  status. Omit `--project` and use `--max-parallel` only for an intentional
1002
1087
  workspace-wide run.
1003
1088
 
1089
+ Run/status share the same due scientific-gate decision: pending/prepared review,
1090
+ revision-required, and stopped are distinct from a runnable native stage.
1091
+ Future gates do not prevent earlier discovery or acquisition. Legitimate
1092
+ historical and user/external-wait project states remain doctor-readable;
1093
+ unknown states and broken evidence bindings still block readiness.
1094
+
1004
1095
  Inputs are admitted by SHA-256. Native producer preparation creates an
1005
1096
  ephemeral, hash-bound packet directory but does not copy agent authentication
1006
1097
  or start an agent. The independent reviewer runs with a dedicated capsule HOME
@@ -1279,6 +1370,18 @@ pilot-methods reviews before analysis. `research status --all` marks any legacy
1279
1370
  fork directory without a `project.forked` commit marker as
1280
1371
  `authority.state = "invalid"` rather than authoritative.
1281
1372
 
1373
+ To repair completed acquisition before analysis, use
1374
+ `research project fork SOURCE --to TARGET --resume-through discover`. This
1375
+ preserves the original frozen audit, inherits verified discovery/receipts and
1376
+ exact acquired artifacts, and opens TARGET's acquire stage. Reuse the returned
1377
+ artifact IDs from the source acquisition audit, run
1378
+ `project evidence content forecast TARGET --input AUDIT`,
1379
+ then prepare/submit TARGET's acquire stage and rebuild typed content. There is
1380
+ no need to repeat paid searches or download unchanged files. Top-journal
1381
+ recovery additionally requires a Policy approved for TARGET and `--design`,
1382
+ `--design-producer-agent`, and `--design-producer-session`; new generation
1383
+ reviews cannot inherit the source generation's scientific approval.
1384
+
1282
1385
  ## Research Search
1283
1386
 
1284
1387
  Forward research-oriented search requests to SCI, report, patent, and ESG edge
@@ -11,21 +11,24 @@ import { researchDataCredentialIds, setCapabilityCredentialValue, } from "./work
11
11
  import { configureExternalSkillProfile, doctorExternalCapabilities, EXTERNAL_SKILL_CONTEXT_PROFILE, EXTERNAL_SKILL_MEDIA_PROFILE, EXTERNAL_SKILL_PROFILE, importExternalCapability, inspectExternalSkillCatalog, } from "./workspace/external-skills.js";
12
12
  import { appendJournalEvent, readJournal } from "./workspace/journal.js";
13
13
  import { fetchNativeCandidateSource } from "./workspace/broker.js";
14
- import { registerEvidenceArtifact } from "./workspace/artifacts.js";
14
+ import { preflightEvidenceArtifact, registerEvidenceArtifact } from "./workspace/artifacts.js";
15
15
  import { executeResearchDataCapability } from "./workspace/data-evidence-adapter.js";
16
16
  import { exportProjectAuditBundle, verifyProjectAuditBundle } from "./workspace/audit-bundle.js";
17
17
  import { loadCurrentEvidenceSnapshot } from "./workspace/acquisition.js";
18
- import { freezeEvidenceContentSnapshot, loadCurrentEvidenceContentSnapshot, recordArtifactDecomposition, registerEvidenceAtom, } from "./workspace/content-evidence.js";
18
+ import { inspectAcquisitionForecast } from "./workspace/acquisition-forecast.js";
19
+ import { freezeEvidenceContentSnapshot, loadCurrentEvidenceContentSnapshot, recordArtifactDecomposition, registerEvidenceAtom, registerEvidenceContentBatch, } from "./workspace/content-evidence.js";
19
20
  import { inspectDiscoveryProgress } from "./workspace/discovery-status.js";
21
+ import { EVIDENCE_CONTENT_LIMITS, EVIDENCE_CONTENT_SCHEMA_NAMES, evidenceContentInputSchema, isEvidenceContentSchemaName, } from "./workspace/evidence-content-schema.js";
20
22
  import { inspectEvidenceAccessStatus } from "./workspace/evidence-exhaustion.js";
21
23
  import { recordDiscoveryAssessmentBatch } from "./workspace/discovery.js";
22
24
  import { bindEvidenceDownload } from "./workspace/downloads.js";
23
25
  import { registerNativeDiscoveryCandidate } from "./workspace/evidence-ledger.js";
24
26
  import { recordNativeResearchActivity } from "./workspace/native-activity.js";
25
- import { inspectReviewerBridgeStatus, startReviewerBridgeSidecar, } from "./workspace/review-executor.js";
27
+ import { inspectReviewerStatus, startReviewerBridgeSidecar } from "./workspace/review-executor.js";
26
28
  import { readAndVerifyProjectInputPlan } from "./workspace/input-plan.js";
29
+ import { executeScientificReview } from "./workspace/scientific-review-execution.js";
27
30
  import { loadCurrentClaimEvidenceGraph, loadCurrentInferenceSnapshot, } from "./workspace/inference.js";
28
- import { addProjectInput, createProjectAddendum, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, setProjectDisposition, } from "./workspace/projects.js";
31
+ import { addProjectInput, createProjectAddendum, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, setProjectDisposition, scientificGateRecommendedAction, } from "./workspace/projects.js";
29
32
  import { evaluateProjectPreflight } from "./workspace/preflight.js";
30
33
  import { closePublication, freezePublicationManuscript, inspectPublicationStatus, preparePublicationReview, publicationAssessmentSchema, publicationReviewSchema, submitPublicationReview, } from "./workspace/publication-workflow.js";
31
34
  import { approveResearchPolicy, initializeResearchPolicy, inspectResearchPolicyCatalog, inspectResearchPolicyStatus, loadApprovedResearchPolicy, } from "./workspace/research-policy.js";
@@ -110,6 +113,7 @@ export function researchOrchestrationHelp() {
110
113
  tiangong-ai research project access status <project-id> [--workspace <path>] [--json]
111
114
  tiangong-ai research project scientific review prepare <project-id> --role research-design|evidence-construct|pilot-methods --assessment <absolute-json> [--canary-artifacts <absolute-json-array>] --reviewer-agent codex|claude --reviewer-session <opaque-id> [--workspace <path>] [--json]
112
115
  tiangong-ai research project scientific review submit <project-id> --role research-design|evidence-construct|pilot-methods --review <absolute-json> [--workspace <path>] [--json]
116
+ tiangong-ai research project scientific review execute <project-id> --role research-design|evidence-construct|pilot-methods --confirm-review-cost [--retry] [--workspace <path>] [--json]
113
117
  tiangong-ai research project scientific status <project-id> [--workspace <path>] [--json]
114
118
  tiangong-ai research project audit export <project-id> --output <absolute-new-directory> [--workspace <path>] [--json]
115
119
  tiangong-ai research project audit verify --bundle <absolute-directory> [--json]
@@ -124,13 +128,21 @@ export function researchOrchestrationHelp() {
124
128
  tiangong-ai research project evidence download bind <project-id> --candidate <id> --record <absolute-json> [--workspace <path>] [--json]
125
129
  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]
126
130
  tiangong-ai research project evidence decomposition record <project-id> --record <absolute-json> [--workspace <path>] [--json]
131
+ tiangong-ai research project evidence decomposition batch <project-id> --record <absolute-json> [--workspace <path>] [--json]
132
+ tiangong-ai research project evidence atom batch <project-id> --record <absolute-json> [--workspace <path>] [--json]
133
+ tiangong-ai research project evidence artifact preflight (--bytes <known-bytes> | --path <absolute-file>) [--workspace <path>] [--json]
127
134
  tiangong-ai research project evidence atom register <project-id> --record <absolute-json> [--workspace <path>] [--json]
128
135
  tiangong-ai research project evidence content freeze <project-id> [--workspace <path>] [--json]
136
+ tiangong-ai research project evidence content forecast <project-id> --input <absolute-acquisition-audit.json> [--workspace <path>] [--json]
129
137
  tiangong-ai research project evidence content status <project-id> [--workspace <path>] [--json]
130
138
  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]
131
139
  tiangong-ai research status [--project <project-id>] [--all] [--workspace <absolute-path>] [--json]
132
140
  tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
133
141
 
142
+ Evidence input schemas: ${EVIDENCE_CONTENT_SCHEMA_NAMES.join(", ")}.
143
+ Evidence batches: at most ${EVIDENCE_CONTENT_LIMITS.maxBatchRecords} records and ${EVIDENCE_CONTENT_LIMITS.maxBatchInputBytes} bytes (${EVIDENCE_CONTENT_LIMITS.maxBatchInputBytes / (1024 * 1024)} MiB) of UTF-8 input.
144
+ Use research schema show <name> --json; schemas validate shape only, not exact artifact/lineage/locator semantics.
145
+
134
146
  ${researchSetupHelp()}
135
147
  `;
136
148
  }
@@ -211,8 +223,9 @@ async function runReviewer(argv, io) {
211
223
  if (args.positionals.length)
212
224
  throw unknownAction("research reviewer status", args.positionals[0]);
213
225
  const root = await workspaceFromArgs(args);
214
- writeJson(io, await inspectReviewerBridgeStatus(root), args);
215
- return 0;
226
+ const result = await inspectReviewerStatus(root, io.env);
227
+ writeJson(io, result, args);
228
+ return isObject(result) && result.status === "ready" ? 0 : 3;
216
229
  }
217
230
  if (action === "doctor") {
218
231
  const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, "confirm-agent-smoke-cost": "boolean" }, "research reviewer doctor");
@@ -493,7 +506,10 @@ async function runSchema(argv, io) {
493
506
  return writeHelp(io);
494
507
  const stage = onePositional(args.positionals, "research schema show");
495
508
  let schema;
496
- if (stage === "scientific-design") {
509
+ if (isEvidenceContentSchemaName(stage)) {
510
+ schema = evidenceContentInputSchema(stage);
511
+ }
512
+ else if (stage === "scientific-design") {
497
513
  schema = scientificDesignSchema();
498
514
  }
499
515
  else if (stage.startsWith("scientific-assessment-")) {
@@ -863,6 +879,28 @@ async function runProject(argv, io) {
863
879
  throw unknownAction("research project scientific", scientificAction ?? "");
864
880
  }
865
881
  const [reviewAction, ...reviewRest] = scientificRest;
882
+ if (reviewAction === "execute") {
883
+ const args = parseStrictArgs(reviewRest, {
884
+ ...WORKSPACE_OPTIONS,
885
+ role: "string",
886
+ "confirm-review-cost": "boolean",
887
+ retry: "boolean",
888
+ }, "research project scientific review execute");
889
+ if (strictBoolean(args, "help"))
890
+ return writeHelp(io);
891
+ const projectId = onePositional(args.positionals, "research project scientific review execute");
892
+ const root = await workspaceFromArgs(args);
893
+ const result = await executeScientificReview({
894
+ root,
895
+ projectId,
896
+ role: scientificReviewRole(strictString(args, "role")),
897
+ confirmCost: strictBoolean(args, "confirm-review-cost"),
898
+ retry: strictBoolean(args, "retry"),
899
+ environment: io.env,
900
+ });
901
+ writeJson(io, result, args);
902
+ return result.status === "passed" ? 0 : 3;
903
+ }
866
904
  if (reviewAction === "prepare") {
867
905
  const args = parseStrictArgs(reviewRest, {
868
906
  ...WORKSPACE_OPTIONS,
@@ -1110,6 +1148,24 @@ async function runProject(argv, io) {
1110
1148
  }
1111
1149
  if (evidenceAction === "artifact") {
1112
1150
  const [artifactAction, ...artifactRest] = evidenceRest;
1151
+ if (artifactAction === "preflight") {
1152
+ const args = parseStrictArgs(artifactRest, { ...WORKSPACE_OPTIONS, bytes: "string", path: "string" }, "research project evidence artifact preflight");
1153
+ if (strictBoolean(args, "help"))
1154
+ return writeHelp(io);
1155
+ if (args.positionals.length)
1156
+ throw unknownAction("research project evidence artifact preflight", args.positionals[0]);
1157
+ const bytes = strictString(args, "bytes");
1158
+ const path = strictString(args, "path");
1159
+ const result = await preflightEvidenceArtifact({
1160
+ root: await workspaceFromArgs(args),
1161
+ ...(bytes === undefined
1162
+ ? {}
1163
+ : { bytes: /^\d+$/u.test(bytes) ? Number(bytes) : Number.NaN }),
1164
+ ...(path === undefined ? {} : { path }),
1165
+ });
1166
+ writeJson(io, result, args);
1167
+ return result.decision === "pass" ? 0 : 3;
1168
+ }
1113
1169
  if (artifactAction !== "register") {
1114
1170
  throw unknownAction("research project evidence artifact", artifactAction ?? "");
1115
1171
  }
@@ -1171,7 +1227,7 @@ async function runProject(argv, io) {
1171
1227
  }
1172
1228
  if (evidenceAction === "decomposition") {
1173
1229
  const [decompositionAction, ...decompositionRest] = evidenceRest;
1174
- if (decompositionAction !== "record") {
1230
+ if (decompositionAction !== "record" && decompositionAction !== "batch") {
1175
1231
  throw unknownAction("research project evidence decomposition", decompositionAction ?? "");
1176
1232
  }
1177
1233
  const args = parseStrictArgs(decompositionRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence decomposition record");
@@ -1186,14 +1242,16 @@ async function runProject(argv, io) {
1186
1242
  });
1187
1243
  }
1188
1244
  const root = await workspaceFromArgs(args);
1189
- const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_DECOMPOSITION_INVALID");
1190
- const result = await withWorkspaceLock(root, "research.decomposition.record", () => recordArtifactDecomposition({ root, projectId, value: record }));
1245
+ const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_DECOMPOSITION_INVALID", decompositionAction === "batch" ? EVIDENCE_CONTENT_LIMITS.maxBatchInputBytes : undefined);
1246
+ const result = await withWorkspaceLock(root, "research.decomposition.record", async () => decompositionAction === "batch"
1247
+ ? registerEvidenceContentBatch({ root, projectId, kind: "decomposition", value: record })
1248
+ : recordArtifactDecomposition({ root, projectId, value: record }));
1191
1249
  writeJson(io, result, args);
1192
1250
  return 0;
1193
1251
  }
1194
1252
  if (evidenceAction === "atom") {
1195
1253
  const [atomAction, ...atomRest] = evidenceRest;
1196
- if (atomAction !== "register") {
1254
+ if (atomAction !== "register" && atomAction !== "batch") {
1197
1255
  throw unknownAction("research project evidence atom", atomAction ?? "");
1198
1256
  }
1199
1257
  const args = parseStrictArgs(atomRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence atom register");
@@ -1208,13 +1266,31 @@ async function runProject(argv, io) {
1208
1266
  });
1209
1267
  }
1210
1268
  const root = await workspaceFromArgs(args);
1211
- const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_EVIDENCE_ATOM_INVALID");
1212
- const result = await withWorkspaceLock(root, "research.evidence-atom.register", () => registerEvidenceAtom({ root, projectId, value: record }));
1269
+ const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_EVIDENCE_ATOM_INVALID", atomAction === "batch" ? EVIDENCE_CONTENT_LIMITS.maxBatchInputBytes : undefined);
1270
+ const result = await withWorkspaceLock(root, "research.evidence-atom.register", async () => atomAction === "batch"
1271
+ ? registerEvidenceContentBatch({ root, projectId, kind: "atom", value: record })
1272
+ : registerEvidenceAtom({ root, projectId, value: record }));
1213
1273
  writeJson(io, result, args);
1214
1274
  return 0;
1215
1275
  }
1216
1276
  if (evidenceAction === "content") {
1217
1277
  const [contentAction, ...contentRest] = evidenceRest;
1278
+ if (contentAction === "forecast") {
1279
+ const args = parseStrictArgs(contentRest, { ...WORKSPACE_OPTIONS, input: "string" }, "research project evidence content forecast");
1280
+ if (strictBoolean(args, "help"))
1281
+ return writeHelp(io);
1282
+ const projectId = onePositional(args.positionals, "research project evidence content forecast");
1283
+ const inputPath = strictString(args, "input");
1284
+ if (!inputPath)
1285
+ throw new CliError("content forecast requires --input with a proposed acquisition audit.", { code: "RESEARCH_ACQUISITION_FORECAST_INVALID", exitCode: 2 });
1286
+ const root = await workspaceFromArgs(args);
1287
+ const value = await readBoundedJsonRecord(inputPath, "--input", "RESEARCH_ACQUISITION_FORECAST_INVALID");
1288
+ const result = await inspectAcquisitionForecast(root, projectId, value);
1289
+ writeJson(io, result, args);
1290
+ return result.acquisitionGate.decision === "pass" && !result.knownRoleDeficits.length
1291
+ ? 0
1292
+ : 3;
1293
+ }
1218
1294
  if (contentAction !== "freeze" && contentAction !== "status") {
1219
1295
  throw unknownAction("research project evidence content", contentAction ?? "");
1220
1296
  }
@@ -1600,7 +1676,7 @@ async function runStatus(argv, io) {
1600
1676
  readyPackage,
1601
1677
  recommendedAction: authority.state === "invalid"
1602
1678
  ? "This recovery target has no project.forked commit marker. Do not execute it; inspect and remove or repair the incomplete fork while retaining source authority."
1603
- : projectRecommendedAction(root, current, readyPackage, nativeStage, scientificReview, evidencePipeline, publication),
1679
+ : projectRecommendedAction(root, current, readyPackage, nativeStage, evidencePipeline, publication),
1604
1680
  usage: current.usage,
1605
1681
  inputs: current.inputs,
1606
1682
  packages: current.packages,
@@ -1774,7 +1850,7 @@ async function inspectPublicationForStatus(root, projectId) {
1774
1850
  };
1775
1851
  }
1776
1852
  }
1777
- function projectRecommendedAction(root, project, readyPackage, nativeStage, scientificReview, evidencePipeline, publication) {
1853
+ function projectRecommendedAction(root, project, readyPackage, nativeStage, evidencePipeline, publication) {
1778
1854
  if (project.lineage.supersededBy) {
1779
1855
  return `Continue with superseding project ${project.lineage.supersededBy}.`;
1780
1856
  }
@@ -1815,15 +1891,9 @@ function projectRecommendedAction(root, project, readyPackage, nativeStage, scie
1815
1891
  return `Inference snapshot is invalid (${evidencePipeline.inference.code ?? "unknown"}); repair its frozen upstream bindings before analysis.`;
1816
1892
  }
1817
1893
  }
1818
- if (scientificReview.nextGate) {
1819
- const gate = scientificReview.nextGate;
1820
- if (gate.status === "stopped") {
1821
- return `Scientific ${gate.role} review stopped the project; inspect the frozen review and request user or external action instead of continuing.`;
1822
- }
1823
- const schema = `scientific-assessment-${gate.role}`;
1824
- const canaryOption = gate.role === "evidence-construct" ? " --canary-artifacts <absolute-json-array>" : "";
1825
- return `Use the native producer App to create a bounded ${gate.role} assessment from schema ${schema}, then prepare an independent review: tiangong-ai research project scientific review prepare ${project.id} --role ${gate.role} --assessment <absolute-json>${canaryOption} --reviewer-agent <codex|claude> --reviewer-session <fresh-opaque-id> --workspace ${root}`;
1826
- }
1894
+ const scientificAction = scientificGateRecommendedAction(root, project);
1895
+ if (scientificAction)
1896
+ return scientificAction;
1827
1897
  if (project.status === "complete") {
1828
1898
  if (project.publicationPolicy) {
1829
1899
  if (publication && "code" in publication) {
@@ -1981,7 +2051,7 @@ function nativeHostAgent(value) {
1981
2051
  async function readNativeEvidenceRequest(path) {
1982
2052
  return readBoundedJsonRecord(path, "--request", "RESEARCH_BROKER_REQUEST_INVALID");
1983
2053
  }
1984
- async function readBoundedJsonRecord(path, label, code) {
2054
+ async function readBoundedJsonRecord(path, label, code, maxBytes = 1024 * 1024) {
1985
2055
  if (!isAbsolute(path)) {
1986
2056
  throw new CliError(`${label} must be an absolute JSON file path.`, {
1987
2057
  code,
@@ -1990,7 +2060,7 @@ async function readBoundedJsonRecord(path, label, code) {
1990
2060
  }
1991
2061
  const selected = resolve(path);
1992
2062
  const info = await lstat(selected).catch(() => undefined);
1993
- if (!info?.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
2063
+ if (!info?.isFile() || info.isSymbolicLink() || info.size > maxBytes) {
1994
2064
  throw new CliError(`${label} must be a bounded regular non-symlink JSON file.`, {
1995
2065
  code,
1996
2066
  exitCode: 2,