@tiangong-ai/cli 0.0.35 → 0.0.37

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 (47) hide show
  1. package/AGENTS.md +16 -6
  2. package/README.md +128 -10
  3. package/dist/research/orchestration.js +334 -29
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-routes.d.ts +14 -0
  6. package/dist/research/workspace/acquisition-routes.js +45 -0
  7. package/dist/research/workspace/acquisition-routes.js.map +1 -0
  8. package/dist/research/workspace/audit-bundle.d.ts +44 -0
  9. package/dist/research/workspace/audit-bundle.js +357 -0
  10. package/dist/research/workspace/audit-bundle.js.map +1 -0
  11. package/dist/research/workspace/broker.js +54 -10
  12. package/dist/research/workspace/broker.js.map +1 -1
  13. package/dist/research/workspace/downloads.d.ts +6 -0
  14. package/dist/research/workspace/downloads.js +32 -8
  15. package/dist/research/workspace/downloads.js.map +1 -1
  16. package/dist/research/workspace/evidence-exhaustion.d.ts +45 -0
  17. package/dist/research/workspace/evidence-exhaustion.js +365 -0
  18. package/dist/research/workspace/evidence-exhaustion.js.map +1 -0
  19. package/dist/research/workspace/native-activity.d.ts +6 -0
  20. package/dist/research/workspace/native-activity.js +24 -7
  21. package/dist/research/workspace/native-activity.js.map +1 -1
  22. package/dist/research/workspace/preflight.d.ts +34 -2
  23. package/dist/research/workspace/preflight.js +161 -1
  24. package/dist/research/workspace/preflight.js.map +1 -1
  25. package/dist/research/workspace/projects.d.ts +28 -4
  26. package/dist/research/workspace/projects.js +301 -12
  27. package/dist/research/workspace/projects.js.map +1 -1
  28. package/dist/research/workspace/publication-workflow.js +2 -0
  29. package/dist/research/workspace/publication-workflow.js.map +1 -1
  30. package/dist/research/workspace/runtime.d.ts +151 -3
  31. package/dist/research/workspace/runtime.js +180 -6
  32. package/dist/research/workspace/runtime.js.map +1 -1
  33. package/dist/research/workspace/sanitization.js +9 -3
  34. package/dist/research/workspace/sanitization.js.map +1 -1
  35. package/dist/research/workspace/scientific-design.d.ts +359 -0
  36. package/dist/research/workspace/scientific-design.js +2021 -0
  37. package/dist/research/workspace/scientific-design.js.map +1 -0
  38. package/dist/research/workspace/scientific-review.d.ts +101 -0
  39. package/dist/research/workspace/scientific-review.js +1167 -0
  40. package/dist/research/workspace/scientific-review.js.map +1 -0
  41. package/dist/research/workspace/setup-catalog.js +2 -2
  42. package/dist/research/workspace/setup.js +12 -3
  43. package/dist/research/workspace/setup.js.map +1 -1
  44. package/dist/research/workspace/types.d.ts +54 -0
  45. package/dist/research/workspace/workspace.js +25 -7
  46. package/dist/research/workspace/workspace.js.map +1 -1
  47. package/package.json +2 -1
@@ -11,8 +11,10 @@ import { configureExternalSkillProfile, doctorExternalCapabilities, EXTERNAL_SKI
11
11
  import { appendJournalEvent } from "./workspace/journal.js";
12
12
  import { fetchNativeCandidateSource } from "./workspace/broker.js";
13
13
  import { registerEvidenceArtifact } from "./workspace/artifacts.js";
14
+ import { exportProjectAuditBundle, verifyProjectAuditBundle } from "./workspace/audit-bundle.js";
14
15
  import { loadCurrentEvidenceSnapshot } from "./workspace/acquisition.js";
15
16
  import { inspectDiscoveryProgress } from "./workspace/discovery-status.js";
17
+ import { inspectEvidenceAccessStatus } from "./workspace/evidence-exhaustion.js";
16
18
  import { recordDiscoveryAssessmentBatch } from "./workspace/discovery.js";
17
19
  import { bindEvidenceDownload } from "./workspace/downloads.js";
18
20
  import { registerNativeDiscoveryCandidate } from "./workspace/evidence-ledger.js";
@@ -25,6 +27,8 @@ import { approveResearchPolicy, initializeResearchPolicy, inspectResearchPolicyC
25
27
  import { resolveInstalledResearchPolicySource, runInteractiveResearchPolicyWizard, } from "./workspace/research-policy-wizard.js";
26
28
  import { abortNativeResearchStage, inspectNativeResearchStage, prepareNativeResearchStage, requestResearchHandoff, resolveResearchHandoff, runResearchWorkspace, submitNativeResearchStage, } from "./workspace/runtime.js";
27
29
  import { schemaForStage } from "./workspace/schemas.js";
30
+ import { readAndVerifyScientificDesign, scientificDesignSchema, } from "./workspace/scientific-design.js";
31
+ import { inspectScientificReviewStatus, prepareScientificReview, scientificGateAssessmentSchema, scientificReviewSchema, submitScientificReview, } from "./workspace/scientific-review.js";
28
32
  import { pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
29
33
  import { doctorResearchWorkspace, initializeResearchWorkspace, requireResearchWorkspace, withWorkspaceLock, } from "./workspace/workspace.js";
30
34
  const COMMON_OPTIONS = { help: "boolean", json: "boolean" };
@@ -78,16 +82,22 @@ export function researchOrchestrationHelp() {
78
82
  tiangong-ai research publication review submit <project-id> --role evidence|methods-reproducibility|domain-novelty|journal-editor --review <absolute-json> [--workspace <path>] [--json]
79
83
  tiangong-ai research publication status <project-id> [--workspace <path>] [--json]
80
84
  tiangong-ai research publication close <project-id> [--workspace <path>] [--json]
81
- tiangong-ai research project init <project-id> --question <question> [--goal evidence-report|top-journal] [--requirements <absolute-json>] [--input-plan <absolute-json>] [--confirm-budget] [--workspace <path>] [--json]
82
- tiangong-ai research project preflight --question <question> [--goal evidence-report|top-journal] [--policy-project <project-id>] [--requirements <absolute-json>] [--input-plan <absolute-json>] [--workspace <path>] [--json]
85
+ tiangong-ai research project init <project-id> --question <question> [--goal evidence-report|top-journal] [--design <absolute-json> --design-producer-agent codex|claude --design-producer-session <opaque-id>] [--requirements <absolute-json>] [--input-plan <absolute-json>] [--confirm-budget] [--workspace <path>] [--json]
86
+ tiangong-ai research project preflight --question <question> [--goal evidence-report|top-journal] [--policy-project <project-id> --design <absolute-json>] [--requirements <absolute-json>] [--input-plan <absolute-json>] [--workspace <path>] [--json]
83
87
  tiangong-ai research project input add <project-id> --path <absolute-file> [--role primary|reference|replication] [--trust-status verified-owner-input|unverified-owner-input|reference-only|replication-candidate] [--independently-reproduced] [--workspace <path>] [--json]
84
88
  tiangong-ai research project retry <project-id> [--package <package-id>] [--workspace <path>] [--json]
85
- tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|acquire|analyze|synthesize] [--workspace <path>] [--json]
86
- tiangong-ai research project addendum <closed-project-id> --to <target-project-id> [--workspace <path>] [--json]
89
+ tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|acquire|analyze|synthesize] [--design <absolute-json> --design-producer-agent codex|claude --design-producer-session <opaque-id>] [--workspace <path>] [--json]
90
+ tiangong-ai research project addendum <closed-project-id> --to <target-project-id> [--design <absolute-json> --design-producer-agent codex|claude --design-producer-session <opaque-id>] [--workspace <path>] [--json]
87
91
  tiangong-ai research project archive <project-id> --reason <text> [--workspace <path>] [--json]
88
92
  tiangong-ai research project abandon <project-id> --reason <text> [--workspace <path>] [--json]
89
93
  tiangong-ai research project handoff request <project-id> --record <absolute-json> [--workspace <path>] [--json]
90
94
  tiangong-ai research project handoff resolve <project-id> --note <text> [--workspace <path>] [--json]
95
+ tiangong-ai research project access status <project-id> [--workspace <path>] [--json]
96
+ tiangong-ai research project scientific review prepare <project-id> --role research-design|evidence-construct|pilot-methods --assessment <absolute-json> --reviewer-agent codex|claude --reviewer-session <opaque-id> [--workspace <path>] [--json]
97
+ tiangong-ai research project scientific review submit <project-id> --role research-design|evidence-construct|pilot-methods --review <absolute-json> [--workspace <path>] [--json]
98
+ tiangong-ai research project scientific status <project-id> [--workspace <path>] [--json]
99
+ tiangong-ai research project audit export <project-id> --output <absolute-new-directory> [--workspace <path>] [--json]
100
+ tiangong-ai research project audit verify --bundle <absolute-directory> [--json]
91
101
  tiangong-ai research project stage prepare <project-id> --stage discover|acquire|analyze|synthesize --host-agent codex|claude [--workspace <path>] [--json]
92
102
  tiangong-ai research project stage submit <project-id> --session <id> --output <absolute-json> [--confirm-model <id>] [--workspace <path>] [--json]
93
103
  tiangong-ai research project stage abort <project-id> --session <id> [--workspace <path>] [--json]
@@ -97,7 +107,7 @@ export function researchOrchestrationHelp() {
97
107
  tiangong-ai research project evidence assessment record <project-id> --record <absolute-json> [--workspace <path>] [--json]
98
108
  tiangong-ai research project evidence download bind <project-id> --candidate <id> --record <absolute-json> [--workspace <path>] [--json]
99
109
  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]
100
- tiangong-ai research schema show <discover|acquire|analyze|synthesize|review|doctor|publication-assessment|publication-review-evidence|publication-review-methods-reproducibility|publication-review-domain-novelty|publication-review-journal-editor> [--json]
110
+ 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]
101
111
  tiangong-ai research status [--project <project-id>] [--all] [--workspace <absolute-path>] [--json]
102
112
  tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
103
113
 
@@ -303,33 +313,74 @@ async function runSchema(argv, io) {
303
313
  return writeHelp(io);
304
314
  if (action !== "show")
305
315
  throw unknownAction("research schema", action);
306
- const args = parseStrictArgs(rest, COMMON_OPTIONS, "research schema show");
316
+ const args = parseStrictArgs(rest, { ...COMMON_OPTIONS, compatibility: "string" }, "research schema show");
307
317
  if (strictBoolean(args, "help"))
308
318
  return writeHelp(io);
309
319
  const stage = onePositional(args.positionals, "research schema show");
310
- if (stage === "publication-assessment") {
311
- writeJson(io, publicationAssessmentSchema(), args);
312
- return 0;
320
+ let schema;
321
+ if (stage === "scientific-design") {
322
+ schema = scientificDesignSchema();
323
+ }
324
+ else if (stage.startsWith("scientific-assessment-")) {
325
+ const role = scientificReviewRole(stage.slice("scientific-assessment-".length));
326
+ schema = scientificGateAssessmentSchema(role);
327
+ }
328
+ else if (stage.startsWith("scientific-review-")) {
329
+ const role = scientificReviewRole(stage.slice("scientific-review-".length));
330
+ schema = scientificReviewSchema(role);
331
+ }
332
+ else if (stage === "publication-assessment") {
333
+ schema = publicationAssessmentSchema();
313
334
  }
314
- if (stage.startsWith("publication-review-")) {
335
+ else if (stage.startsWith("publication-review-")) {
315
336
  const role = publicationReviewRole(stage.slice("publication-review-".length));
316
- writeJson(io, publicationReviewSchema(role), args);
317
- return 0;
337
+ schema = publicationReviewSchema(role);
338
+ }
339
+ else if (stage === "discover" ||
340
+ stage === "acquire" ||
341
+ stage === "analyze" ||
342
+ stage === "synthesize" ||
343
+ stage === "review" ||
344
+ stage === "doctor") {
345
+ schema = schemaForStage(stage);
318
346
  }
319
- if (stage !== "discover" &&
320
- stage !== "acquire" &&
321
- stage !== "analyze" &&
322
- stage !== "synthesize" &&
323
- stage !== "review" &&
324
- stage !== "doctor") {
347
+ else {
325
348
  throw new CliError(`Unsupported research schema stage: ${stage}`, {
326
349
  code: "RESEARCH_SCHEMA_INVALID",
327
350
  exitCode: 2,
328
351
  });
329
352
  }
330
- writeJson(io, schemaForStage(stage), args);
353
+ writeJson(io, compatibleSchema(schema, strictString(args, "compatibility")), args);
331
354
  return 0;
332
355
  }
356
+ function compatibleSchema(schema, compatibility) {
357
+ if (!compatibility)
358
+ return schema;
359
+ if (compatibility !== "claude-code") {
360
+ throw new CliError(`Unsupported schema compatibility target: ${compatibility}`, {
361
+ code: "RESEARCH_SCHEMA_COMPATIBILITY_INVALID",
362
+ exitCode: 2,
363
+ details: { supported: ["claude-code"] },
364
+ });
365
+ }
366
+ const compatible = structuredClone(schema);
367
+ const stripMetadata = (value) => {
368
+ if (Array.isArray(value)) {
369
+ for (const item of value)
370
+ stripMetadata(item);
371
+ return;
372
+ }
373
+ if (!value || typeof value !== "object")
374
+ return;
375
+ const record = value;
376
+ delete record.$schema;
377
+ delete record.$id;
378
+ for (const item of Object.values(record))
379
+ stripMetadata(item);
380
+ };
381
+ stripMetadata(compatible);
382
+ return compatible;
383
+ }
333
384
  async function runContext(argv, io) {
334
385
  const [action, ...rest] = argv;
335
386
  if (!action || action === "--help" || action === "-h")
@@ -520,6 +571,54 @@ async function runProject(argv, io) {
520
571
  const [action, ...rest] = argv;
521
572
  if (!action || action === "--help" || action === "-h")
522
573
  return writeHelp(io);
574
+ if (action === "audit") {
575
+ const [auditAction, ...auditRest] = rest;
576
+ if (auditAction === "export") {
577
+ const args = parseStrictArgs(auditRest, { ...WORKSPACE_OPTIONS, output: "string" }, "research project audit export");
578
+ if (strictBoolean(args, "help"))
579
+ return writeHelp(io);
580
+ const projectId = onePositional(args.positionals, "research project audit export");
581
+ const destination = strictString(args, "output");
582
+ if (!destination) {
583
+ throw new CliError("Audit export requires --output.", {
584
+ code: "RESEARCH_AUDIT_BUNDLE_PATH_INVALID",
585
+ exitCode: 2,
586
+ });
587
+ }
588
+ const root = await workspaceFromArgs(args);
589
+ writeJson(io, await exportProjectAuditBundle({ root, projectId, destination }), args);
590
+ return 0;
591
+ }
592
+ if (auditAction === "verify") {
593
+ const args = parseStrictArgs(auditRest, { ...COMMON_OPTIONS, bundle: "string" }, "research project audit verify");
594
+ if (strictBoolean(args, "help"))
595
+ return writeHelp(io);
596
+ rejectPositionals(args.positionals, "research project audit verify");
597
+ const bundlePath = strictString(args, "bundle");
598
+ if (!bundlePath) {
599
+ throw new CliError("Audit verification requires --bundle.", {
600
+ code: "RESEARCH_AUDIT_BUNDLE_PATH_INVALID",
601
+ exitCode: 2,
602
+ });
603
+ }
604
+ writeJson(io, await verifyProjectAuditBundle(bundlePath), args);
605
+ return 0;
606
+ }
607
+ throw unknownAction("research project audit", auditAction ?? "");
608
+ }
609
+ if (action === "access") {
610
+ const [accessAction, ...accessRest] = rest;
611
+ if (accessAction !== "status") {
612
+ throw unknownAction("research project access", accessAction ?? "");
613
+ }
614
+ const args = parseStrictArgs(accessRest, WORKSPACE_OPTIONS, "research project access status");
615
+ if (strictBoolean(args, "help"))
616
+ return writeHelp(io);
617
+ const projectId = onePositional(args.positionals, "research project access status");
618
+ const root = await workspaceFromArgs(args);
619
+ writeJson(io, await inspectEvidenceAccessStatus(root, projectId), args);
620
+ return 0;
621
+ }
523
622
  if (action === "handoff") {
524
623
  const [handoffAction, ...handoffRest] = rest;
525
624
  if (handoffAction === "request") {
@@ -559,6 +658,71 @@ async function runProject(argv, io) {
559
658
  }
560
659
  throw unknownAction("research project handoff", handoffAction ?? "");
561
660
  }
661
+ if (action === "scientific") {
662
+ const [scientificAction, ...scientificRest] = rest;
663
+ if (scientificAction === "status") {
664
+ const args = parseStrictArgs(scientificRest, WORKSPACE_OPTIONS, "research project scientific status");
665
+ if (strictBoolean(args, "help"))
666
+ return writeHelp(io);
667
+ const projectId = onePositional(args.positionals, "research project scientific status");
668
+ const root = await workspaceFromArgs(args);
669
+ writeJson(io, await inspectScientificReviewStatus(root, projectId), args);
670
+ return 0;
671
+ }
672
+ if (scientificAction !== "review") {
673
+ throw unknownAction("research project scientific", scientificAction ?? "");
674
+ }
675
+ const [reviewAction, ...reviewRest] = scientificRest;
676
+ if (reviewAction === "prepare") {
677
+ const args = parseStrictArgs(reviewRest, {
678
+ ...WORKSPACE_OPTIONS,
679
+ role: "string",
680
+ assessment: "string",
681
+ "reviewer-agent": "string",
682
+ "reviewer-session": "string",
683
+ }, "research project scientific review prepare");
684
+ if (strictBoolean(args, "help"))
685
+ return writeHelp(io);
686
+ const projectId = onePositional(args.positionals, "research project scientific review prepare");
687
+ const assessmentPath = strictString(args, "assessment");
688
+ const reviewerSessionId = strictString(args, "reviewer-session");
689
+ if (!assessmentPath || !reviewerSessionId) {
690
+ throw new CliError("Scientific review prepare requires --assessment, --reviewer-agent, and --reviewer-session.", { code: "RESEARCH_SCIENTIFIC_REVIEW_ARGUMENT_REQUIRED", exitCode: 2 });
691
+ }
692
+ const root = await workspaceFromArgs(args);
693
+ writeJson(io, await prepareScientificReview({
694
+ root,
695
+ projectId,
696
+ role: scientificReviewRole(strictString(args, "role")),
697
+ assessmentPath,
698
+ reviewerAgent: publicationAgent(strictString(args, "reviewer-agent"), "reviewer"),
699
+ reviewerSessionId,
700
+ }), args);
701
+ return 0;
702
+ }
703
+ if (reviewAction === "submit") {
704
+ const args = parseStrictArgs(reviewRest, { ...WORKSPACE_OPTIONS, role: "string", review: "string" }, "research project scientific review submit");
705
+ if (strictBoolean(args, "help"))
706
+ return writeHelp(io);
707
+ const projectId = onePositional(args.positionals, "research project scientific review submit");
708
+ const reviewPath = strictString(args, "review");
709
+ if (!reviewPath) {
710
+ throw new CliError("Scientific review submit requires --review.", {
711
+ code: "RESEARCH_SCIENTIFIC_REVIEW_ARGUMENT_REQUIRED",
712
+ exitCode: 2,
713
+ });
714
+ }
715
+ const root = await workspaceFromArgs(args);
716
+ writeJson(io, await submitScientificReview({
717
+ root,
718
+ projectId,
719
+ role: scientificReviewRole(strictString(args, "role")),
720
+ reviewPath,
721
+ }), args);
722
+ return 0;
723
+ }
724
+ throw unknownAction("research project scientific review", reviewAction ?? "");
725
+ }
562
726
  if (action === "stage") {
563
727
  const [stageAction, ...stageRest] = rest;
564
728
  if (stageAction === "prepare") {
@@ -800,6 +964,9 @@ async function runProject(argv, io) {
800
964
  goal: "string",
801
965
  requirements: "string",
802
966
  "input-plan": "string",
967
+ design: "string",
968
+ "design-producer-agent": "string",
969
+ "design-producer-session": "string",
803
970
  "confirm-budget": "boolean",
804
971
  }, "research project init");
805
972
  if (strictBoolean(args, "help"))
@@ -816,7 +983,26 @@ async function runProject(argv, io) {
816
983
  const goal = researchGoal(strictString(args, "goal"));
817
984
  const requirementsPath = strictString(args, "requirements");
818
985
  const inputPlanPath = strictString(args, "input-plan");
819
- const project = await initializeProject(root, projectId, question, requirementsPath ? await readEvidenceRequirements(requirementsPath) : undefined, strictBoolean(args, "confirm-budget"), inputPlanPath ? await readAndVerifyProjectInputPlan(inputPlanPath) : undefined, goal === "top-journal" ? await loadApprovedResearchPolicy(root, projectId) : undefined);
986
+ const publicationPolicy = goal === "top-journal" ? await loadApprovedResearchPolicy(root, projectId) : undefined;
987
+ const designPath = strictString(args, "design");
988
+ const designProducerAgent = strictString(args, "design-producer-agent");
989
+ const designProducerSession = strictString(args, "design-producer-session");
990
+ if (goal === "top-journal" && (!designPath || !designProducerAgent || !designProducerSession)) {
991
+ throw new CliError("Top-journal init requires --design, --design-producer-agent, and --design-producer-session.", { code: "RESEARCH_SCIENTIFIC_DESIGN_REQUIRED", exitCode: 2 });
992
+ }
993
+ if (goal !== "top-journal" && (designPath || designProducerAgent || designProducerSession)) {
994
+ throw new CliError("Scientific design options are only valid with --goal top-journal.", {
995
+ code: "RESEARCH_SCIENTIFIC_DESIGN_POLICY_REQUIRED",
996
+ exitCode: 2,
997
+ });
998
+ }
999
+ const project = await initializeProject(root, projectId, question, requirementsPath ? await readEvidenceRequirements(requirementsPath) : undefined, strictBoolean(args, "confirm-budget"), inputPlanPath ? await readAndVerifyProjectInputPlan(inputPlanPath) : undefined, publicationPolicy, designPath && designProducerAgent && designProducerSession
1000
+ ? {
1001
+ design: await readAndVerifyScientificDesign(designPath, projectId),
1002
+ producerAgent: publicationAgent(designProducerAgent, "design producer"),
1003
+ producerSessionId: designProducerSession,
1004
+ }
1005
+ : undefined);
820
1006
  writeJson(io, project, args);
821
1007
  return 0;
822
1008
  }
@@ -828,6 +1014,7 @@ async function runProject(argv, io) {
828
1014
  "policy-project": "string",
829
1015
  requirements: "string",
830
1016
  "input-plan": "string",
1017
+ design: "string",
831
1018
  }, "research project preflight");
832
1019
  if (strictBoolean(args, "help"))
833
1020
  return writeHelp(io);
@@ -842,6 +1029,7 @@ async function runProject(argv, io) {
842
1029
  const root = await workspaceFromArgs(args);
843
1030
  const goal = researchGoal(strictString(args, "goal"));
844
1031
  const policyProject = strictString(args, "policy-project");
1032
+ let publicationPolicy = null;
845
1033
  if (goal === "top-journal") {
846
1034
  if (!policyProject) {
847
1035
  throw new CliError("Top-journal preflight requires --policy-project.", {
@@ -849,7 +1037,7 @@ async function runProject(argv, io) {
849
1037
  exitCode: 2,
850
1038
  });
851
1039
  }
852
- await loadApprovedResearchPolicy(root, policyProject);
1040
+ publicationPolicy = await loadApprovedResearchPolicy(root, policyProject);
853
1041
  }
854
1042
  else if (policyProject) {
855
1043
  throw new CliError("--policy-project is only valid with --goal top-journal.", {
@@ -861,7 +1049,26 @@ async function runProject(argv, io) {
861
1049
  const requirements = requirementsPath ? await readEvidenceRequirements(requirementsPath) : null;
862
1050
  const inputPlanPath = strictString(args, "input-plan");
863
1051
  const inputPlan = inputPlanPath ? await readAndVerifyProjectInputPlan(inputPlanPath) : null;
864
- const result = await evaluateProjectPreflight(root, question, requirements, inputPlan);
1052
+ const designPath = strictString(args, "design");
1053
+ if (goal === "top-journal" && !designPath) {
1054
+ throw new CliError("Top-journal preflight requires --design.", {
1055
+ code: "RESEARCH_SCIENTIFIC_DESIGN_REQUIRED",
1056
+ exitCode: 2,
1057
+ });
1058
+ }
1059
+ if (goal !== "top-journal" && designPath) {
1060
+ throw new CliError("--design is only valid with --goal top-journal.", {
1061
+ code: "RESEARCH_SCIENTIFIC_DESIGN_POLICY_REQUIRED",
1062
+ exitCode: 2,
1063
+ });
1064
+ }
1065
+ const scientificDesign = designPath
1066
+ ? await readAndVerifyScientificDesign(designPath, policyProject)
1067
+ : null;
1068
+ const result = await evaluateProjectPreflight(root, question, requirements, inputPlan, {
1069
+ publicationPolicy,
1070
+ scientificDesign,
1071
+ });
865
1072
  writeJson(io, result, args);
866
1073
  return result.readyToInitialize ? 0 : 3;
867
1074
  }
@@ -909,7 +1116,14 @@ async function runProject(argv, io) {
909
1116
  return 0;
910
1117
  }
911
1118
  if (action === "fork") {
912
- const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, to: "string", "resume-through": "string" }, "research project fork");
1119
+ const args = parseStrictArgs(rest, {
1120
+ ...WORKSPACE_OPTIONS,
1121
+ to: "string",
1122
+ "resume-through": "string",
1123
+ design: "string",
1124
+ "design-producer-agent": "string",
1125
+ "design-producer-session": "string",
1126
+ }, "research project fork");
913
1127
  if (strictBoolean(args, "help"))
914
1128
  return writeHelp(io);
915
1129
  const sourceProjectId = onePositional(args.positionals, "research project fork");
@@ -921,12 +1135,41 @@ async function runProject(argv, io) {
921
1135
  });
922
1136
  }
923
1137
  const root = await workspaceFromArgs(args);
924
- const project = await forkProject(root, sourceProjectId, targetProjectId, resumeStage(strictString(args, "resume-through")));
1138
+ const source = await loadProject(root, sourceProjectId);
1139
+ const designPath = strictString(args, "design");
1140
+ const designProducerAgent = strictString(args, "design-producer-agent");
1141
+ const designProducerSession = strictString(args, "design-producer-session");
1142
+ if (source.publicationPolicy &&
1143
+ (!designPath || !designProducerAgent || !designProducerSession)) {
1144
+ throw new CliError("A top-journal fork requires a target-specific approved policy plus --design, --design-producer-agent, and --design-producer-session.", { code: "RESEARCH_SCIENTIFIC_DESIGN_REAPPROVAL_REQUIRED", exitCode: 3 });
1145
+ }
1146
+ if (!source.publicationPolicy && (designPath || designProducerAgent || designProducerSession)) {
1147
+ throw new CliError("Scientific reapproval options require a top-journal source project.", {
1148
+ code: "RESEARCH_SCIENTIFIC_DESIGN_REAPPROVAL_INVALID",
1149
+ exitCode: 2,
1150
+ });
1151
+ }
1152
+ const project = await forkProject(root, sourceProjectId, targetProjectId, resumeStage(strictString(args, "resume-through")), designPath && designProducerAgent && designProducerSession
1153
+ ? {
1154
+ publicationPolicy: await loadApprovedResearchPolicy(root, targetProjectId),
1155
+ scientificDesign: {
1156
+ design: await readAndVerifyScientificDesign(designPath, targetProjectId),
1157
+ producerAgent: publicationAgent(designProducerAgent, "design producer"),
1158
+ producerSessionId: designProducerSession,
1159
+ },
1160
+ }
1161
+ : undefined);
925
1162
  writeJson(io, project, args);
926
1163
  return 0;
927
1164
  }
928
1165
  if (action === "addendum") {
929
- const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, to: "string" }, "research project addendum");
1166
+ const args = parseStrictArgs(rest, {
1167
+ ...WORKSPACE_OPTIONS,
1168
+ to: "string",
1169
+ design: "string",
1170
+ "design-producer-agent": "string",
1171
+ "design-producer-session": "string",
1172
+ }, "research project addendum");
930
1173
  if (strictBoolean(args, "help"))
931
1174
  return writeHelp(io);
932
1175
  const sourceProjectId = onePositional(args.positionals, "research project addendum");
@@ -938,7 +1181,30 @@ async function runProject(argv, io) {
938
1181
  });
939
1182
  }
940
1183
  const root = await workspaceFromArgs(args);
941
- const project = await createProjectAddendum(root, sourceProjectId, targetProjectId);
1184
+ const source = await loadProject(root, sourceProjectId);
1185
+ const designPath = strictString(args, "design");
1186
+ const designProducerAgent = strictString(args, "design-producer-agent");
1187
+ const designProducerSession = strictString(args, "design-producer-session");
1188
+ if (source.publicationPolicy &&
1189
+ (!designPath || !designProducerAgent || !designProducerSession)) {
1190
+ throw new CliError("A top-journal addendum requires a target-specific approved policy plus --design, --design-producer-agent, and --design-producer-session.", { code: "RESEARCH_SCIENTIFIC_DESIGN_REAPPROVAL_REQUIRED", exitCode: 3 });
1191
+ }
1192
+ if (!source.publicationPolicy && (designPath || designProducerAgent || designProducerSession)) {
1193
+ throw new CliError("Scientific reapproval options require a top-journal source project.", {
1194
+ code: "RESEARCH_SCIENTIFIC_DESIGN_REAPPROVAL_INVALID",
1195
+ exitCode: 2,
1196
+ });
1197
+ }
1198
+ const project = await createProjectAddendum(root, sourceProjectId, targetProjectId, designPath && designProducerAgent && designProducerSession
1199
+ ? {
1200
+ publicationPolicy: await loadApprovedResearchPolicy(root, targetProjectId),
1201
+ scientificDesign: {
1202
+ design: await readAndVerifyScientificDesign(designPath, targetProjectId),
1203
+ producerAgent: publicationAgent(designProducerAgent, "design producer"),
1204
+ producerSessionId: designProducerSession,
1205
+ },
1206
+ }
1207
+ : undefined);
942
1208
  writeJson(io, project, args);
943
1209
  return 0;
944
1210
  }
@@ -996,6 +1262,10 @@ async function runStatus(argv, io) {
996
1262
  const nativeStage = await inspectNativeResearchStage(root, current);
997
1263
  const snapshot = await inspectSnapshotForStatus(root, current.id);
998
1264
  const readyPackage = nextReadyPackage(current)?.id ?? null;
1265
+ const scientificReview = await inspectScientificReviewStatus(root, current.id);
1266
+ const evidenceAccess = current.scientificDesign
1267
+ ? await inspectEvidenceAccessForStatus(root, current.id)
1268
+ : null;
999
1269
  const publication = current.publicationPolicy
1000
1270
  ? await inspectPublicationForStatus(root, current.id)
1001
1271
  : null;
@@ -1009,9 +1279,11 @@ async function runStatus(argv, io) {
1009
1279
  evidenceState: current.evidenceState,
1010
1280
  snapshot,
1011
1281
  nativeStage,
1282
+ scientificReview,
1283
+ evidenceAccess,
1012
1284
  publication,
1013
1285
  readyPackage,
1014
- recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage, publication),
1286
+ recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage, scientificReview, publication),
1015
1287
  usage: current.usage,
1016
1288
  inputs: current.inputs,
1017
1289
  packages: current.packages,
@@ -1022,6 +1294,17 @@ async function runStatus(argv, io) {
1022
1294
  writeJson(io, result, args);
1023
1295
  return 0;
1024
1296
  }
1297
+ async function inspectEvidenceAccessForStatus(root, projectId) {
1298
+ try {
1299
+ return await inspectEvidenceAccessStatus(root, projectId);
1300
+ }
1301
+ catch (error) {
1302
+ return {
1303
+ status: "invalid",
1304
+ code: error instanceof CliError ? error.code : "RESEARCH_EVIDENCE_ACCESS_PLAN_INVALID",
1305
+ };
1306
+ }
1307
+ }
1025
1308
  function projectAuthority(project, projects) {
1026
1309
  const byId = new Map(projects.map((candidate) => [candidate.id, candidate]));
1027
1310
  const visited = new Set([project.id]);
@@ -1079,7 +1362,7 @@ async function inspectPublicationForStatus(root, projectId) {
1079
1362
  };
1080
1363
  }
1081
1364
  }
1082
- function projectRecommendedAction(root, project, readyPackage, nativeStage, publication) {
1365
+ function projectRecommendedAction(root, project, readyPackage, nativeStage, scientificReview, publication) {
1083
1366
  if (project.lineage.supersededBy) {
1084
1367
  return `Continue with superseding project ${project.lineage.supersededBy}.`;
1085
1368
  }
@@ -1087,7 +1370,12 @@ function projectRecommendedAction(root, project, readyPackage, nativeStage, publ
1087
1370
  return `Project is ${project.status}; inspect with research status --all and continue only from an authoritative project.`;
1088
1371
  }
1089
1372
  if (project.handoff.state === "user-action-required") {
1090
- return `User action required: ${project.handoff.summary ?? "review the requested action"} Resolve only after completion: tiangong-ai research project handoff resolve ${project.id} --note <resolution-note> --workspace ${root}`;
1373
+ const resources = project.handoff.accessRequests.map((request) => request.resourceName);
1374
+ const detail = resources.length ? ` Required resources: ${resources.join(", ")}.` : "";
1375
+ const stop = project.handoff.kind === "evidence-exhausted"
1376
+ ? " Do not continue substitute searching; the plan-bound agent routes are frozen in the handoff."
1377
+ : "";
1378
+ return `User action required: ${project.handoff.summary ?? "review the requested action"}${detail}${stop} Resolve only after completion: tiangong-ai research project handoff resolve ${project.id} --note <resolution-note> --workspace ${root}`;
1091
1379
  }
1092
1380
  if (project.handoff.state === "external-response-required") {
1093
1381
  return `Waiting for an external response: ${project.handoff.summary ?? "external evidence is pending"} Do not continue substitute searching; resolve after the response is registered.`;
@@ -1098,6 +1386,14 @@ function projectRecommendedAction(root, project, readyPackage, nativeStage, publ
1098
1386
  if (nativeStage.status === "active") {
1099
1387
  return nativeStage.recommendedAction ?? "Resume the active native stage.";
1100
1388
  }
1389
+ if (scientificReview.nextGate) {
1390
+ const gate = scientificReview.nextGate;
1391
+ if (gate.status === "stopped") {
1392
+ return `Scientific ${gate.role} review stopped the project; inspect the frozen review and request user or external action instead of continuing.`;
1393
+ }
1394
+ const schema = `scientific-assessment-${gate.role}`;
1395
+ 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> --reviewer-agent <codex|claude> --reviewer-session <fresh-opaque-id> --workspace ${root}`;
1396
+ }
1101
1397
  if (project.status === "complete") {
1102
1398
  if (project.publicationPolicy) {
1103
1399
  if (publication && "code" in publication) {
@@ -1349,6 +1645,15 @@ function publicationReviewRole(value) {
1349
1645
  }
1350
1646
  throw new CliError("--role must be evidence, methods-reproducibility, domain-novelty, or journal-editor.", { code: "RESEARCH_PUBLICATION_REVIEW_ROLE_INVALID", exitCode: 2 });
1351
1647
  }
1648
+ function scientificReviewRole(value) {
1649
+ if (value === "research-design" || value === "evidence-construct" || value === "pilot-methods") {
1650
+ return value;
1651
+ }
1652
+ throw new CliError("--role must be research-design, evidence-construct, or pilot-methods.", {
1653
+ code: "RESEARCH_SCIENTIFIC_REVIEW_ROLE_INVALID",
1654
+ exitCode: 2,
1655
+ });
1656
+ }
1352
1657
  function publicationAgent(value, label) {
1353
1658
  if (value === "codex" || value === "claude")
1354
1659
  return value;