@tiangong-ai/cli 0.0.34 → 0.0.35

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 (33) hide show
  1. package/AGENTS.md +4 -2
  2. package/README.md +63 -0
  3. package/dist/research/orchestration.js +366 -9
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/input-plan.js +27 -1
  6. package/dist/research/workspace/input-plan.js.map +1 -1
  7. package/dist/research/workspace/projects.d.ts +6 -3
  8. package/dist/research/workspace/projects.js +21 -2
  9. package/dist/research/workspace/projects.js.map +1 -1
  10. package/dist/research/workspace/publication-workflow.d.ts +146 -0
  11. package/dist/research/workspace/publication-workflow.js +992 -0
  12. package/dist/research/workspace/publication-workflow.js.map +1 -0
  13. package/dist/research/workspace/publication.d.ts +110 -0
  14. package/dist/research/workspace/publication.js +246 -0
  15. package/dist/research/workspace/publication.js.map +1 -0
  16. package/dist/research/workspace/research-policy-wizard.d.ts +40 -0
  17. package/dist/research/workspace/research-policy-wizard.js +167 -0
  18. package/dist/research/workspace/research-policy-wizard.js.map +1 -0
  19. package/dist/research/workspace/research-policy.d.ts +70 -0
  20. package/dist/research/workspace/research-policy.js +886 -0
  21. package/dist/research/workspace/research-policy.js.map +1 -0
  22. package/dist/research/workspace/runtime.d.ts +17 -0
  23. package/dist/research/workspace/runtime.js +81 -2
  24. package/dist/research/workspace/runtime.js.map +1 -1
  25. package/dist/research/workspace/setup-catalog.js +2 -2
  26. package/dist/research/workspace/setup-wizard.d.ts +18 -1
  27. package/dist/research/workspace/setup-wizard.js +1 -1
  28. package/dist/research/workspace/setup-wizard.js.map +1 -1
  29. package/dist/research/workspace/setup.d.ts +1 -0
  30. package/dist/research/workspace/setup.js +55 -0
  31. package/dist/research/workspace/setup.js.map +1 -1
  32. package/dist/research/workspace/types.d.ts +31 -0
  33. package/package.json +3 -2
package/AGENTS.md CHANGED
@@ -15,8 +15,8 @@ checkPaths:
15
15
  - .docpact/config.yaml
16
16
  - docs/agents/**
17
17
  - src/**
18
- lastReviewedAt: 2026-08-12
19
- lastReviewedCommit: 7d692de934c51178df520ccdaa212acc7dc303f0
18
+ lastReviewedAt: 2026-08-13
19
+ lastReviewedCommit: df263405bccb21cd5838d92d82dccf80ca01acb1
20
20
  ---
21
21
 
22
22
  # Tiangong AI CLI Contract
@@ -47,6 +47,8 @@ This repository owns the Tiangong AI command-line interface.
47
47
  - `tiangong-ai kb status`
48
48
  - `tiangong-ai research context`
49
49
  - `tiangong-ai research setup`
50
+ - `tiangong-ai research policy`
51
+ - `tiangong-ai research publication`
50
52
  - `tiangong-ai research workspace`
51
53
  - `tiangong-ai research capability`
52
54
  - `tiangong-ai research project`
package/README.md CHANGED
@@ -269,6 +269,69 @@ regular non-symlink `runtime-lock.json` exact stable CLI version. Setup and
269
269
  release CI reject a missing resolver or any stale exact CLI version in the
270
270
  orchestrator's `SKILL.md` or `references/*.md`.
271
271
 
272
+ ### Top-journal Research Policy and final publication gate
273
+
274
+ A `top-journal` project starts with a human-reviewed Markdown Policy, not with
275
+ model execution. After project-scoped setup reaches `READY`, use the guided
276
+ Wizard:
277
+
278
+ ```bash
279
+ tiangong-ai research policy wizard top-journal-paper \
280
+ --workspace /absolute/path/to/workspace
281
+ tiangong-ai research policy status top-journal-paper \
282
+ --workspace /absolute/path/to/workspace --json
283
+ ```
284
+
285
+ The Wizard resolves only the verified project-installed
286
+ `tiangong-auto-research` tree. It copies a baseline plus one article type,
287
+ field, journal class, project brief, and four reviewer rubrics. Generic defaults
288
+ are clearly reported and require a separate acknowledgement. An exact-journal
289
+ Policy additionally requires a current official HTTPS guideline URL, retrieval
290
+ date, and substantive human content for all journal-specific sections. Approval
291
+ binds the manifest and every document by SHA-256; edits, manifest tampering, or
292
+ expiry block preflight and all later stages until the Policy is reviewed and
293
+ approved again.
294
+
295
+ Use the Policy project ID when admitting the research project:
296
+
297
+ ```bash
298
+ tiangong-ai research project preflight \
299
+ --question "A specific, testable research question" \
300
+ --goal top-journal --policy-project top-journal-paper \
301
+ --requirements /absolute/path/to/evidence-requirements.json \
302
+ --workspace /absolute/path/to/workspace --json
303
+ ```
304
+
305
+ The base evidence lifecycle remains
306
+ `discover -> acquire -> analyze -> synthesize -> review -> close`, authored in
307
+ the current interactive Codex or Claude Code host. After base closure, the
308
+ current native host writes a final manuscript and schema-valid publication
309
+ assessment. `research publication freeze` then content-addresses the Policy,
310
+ evidence snapshot, base outputs, manuscript, assessment, and supplements.
311
+ Exactly four fresh independent sessions review that frozen generation:
312
+ evidence, methods/reproducibility, domain/novelty, and journal-editor. A revised
313
+ manuscript invalidates prior reviews; reviewer-session reuse is rejected from
314
+ the append-only journal even if mutable cache state is removed. The raw opaque
315
+ producer/reviewer session identifiers are accepted only at the command boundary;
316
+ generation, packet, review, journal, and closure objects persist only their
317
+ SHA-256 bindings.
318
+
319
+ ```bash
320
+ tiangong-ai research schema show publication-assessment --json
321
+ tiangong-ai research publication freeze top-journal-paper \
322
+ --manuscript /absolute/path/to/final-manuscript.md \
323
+ --assessment /absolute/path/to/publication-assessment.json \
324
+ --producer-agent codex --producer-session OPAQUE_NATIVE_SESSION \
325
+ --workspace /absolute/path/to/workspace --json
326
+ tiangong-ai research publication status top-journal-paper \
327
+ --workspace /absolute/path/to/workspace --json
328
+ ```
329
+
330
+ The CLI returns a mechanically bounded ceiling:
331
+ `top-journal-candidate`, `top-journal-class-ready`, or
332
+ `target-journal-submission-ready`. Evidence and review failures can only lower
333
+ it. None of these states predicts or guarantees editorial acceptance.
334
+
272
335
  `research setup status --json` reports credential persistence separately from
273
336
  readiness. It also reports the effective exact-npx CLI package/version/root,
274
337
  the selected project orchestrator, any temporary recovery shim, ignored global
@@ -20,6 +20,9 @@ import { recordNativeResearchActivity } from "./workspace/native-activity.js";
20
20
  import { readAndVerifyProjectInputPlan } from "./workspace/input-plan.js";
21
21
  import { addProjectInput, createProjectAddendum, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, setProjectDisposition, } from "./workspace/projects.js";
22
22
  import { evaluateProjectPreflight } from "./workspace/preflight.js";
23
+ import { closePublication, freezePublicationManuscript, inspectPublicationStatus, preparePublicationReview, publicationAssessmentSchema, publicationReviewSchema, submitPublicationReview, } from "./workspace/publication-workflow.js";
24
+ import { approveResearchPolicy, initializeResearchPolicy, inspectResearchPolicyCatalog, inspectResearchPolicyStatus, loadApprovedResearchPolicy, } from "./workspace/research-policy.js";
25
+ import { resolveInstalledResearchPolicySource, runInteractiveResearchPolicyWizard, } from "./workspace/research-policy-wizard.js";
23
26
  import { abortNativeResearchStage, inspectNativeResearchStage, prepareNativeResearchStage, requestResearchHandoff, resolveResearchHandoff, runResearchWorkspace, submitNativeResearchStage, } from "./workspace/runtime.js";
24
27
  import { schemaForStage } from "./workspace/schemas.js";
25
28
  import { pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
@@ -35,6 +38,10 @@ export async function runResearchOrchestrationCommand(subcommand, argv, io) {
35
38
  return runWorkspace(argv, io);
36
39
  if (subcommand === "capability")
37
40
  return runCapability(argv, io);
41
+ if (subcommand === "policy")
42
+ return runPolicy(argv, io);
43
+ if (subcommand === "publication")
44
+ return runPublication(argv, io);
38
45
  if (subcommand === "project")
39
46
  return runProject(argv, io);
40
47
  if (subcommand === "schema")
@@ -59,9 +66,21 @@ export function researchOrchestrationHelp() {
59
66
  tiangong-ai research capability credential set --id <logical-id> --from-env <name> [--workspace <absolute-path>] [--json]
60
67
  tiangong-ai research capability lock [--workspace <absolute-path>] [--json]
61
68
  tiangong-ai research capability verify [--workspace <absolute-path>] [--json]
62
- tiangong-ai research project init <project-id> --question <question> [--requirements <absolute-json>] [--input-plan <absolute-json>] [--confirm-budget] [--workspace <path>] [--json]
63
- tiangong-ai research project preflight --question <question> [--requirements <absolute-json>] [--input-plan <absolute-json>] [--workspace <path>] [--json]
64
- tiangong-ai research project input add <project-id> --path <absolute-file> [--role primary|reference|replication] [--workspace <path>] [--json]
69
+ tiangong-ai research policy wizard <project-id> [--workspace <path>] [--json]
70
+ tiangong-ai research policy catalog [--source-root <absolute-installed-skill-root>] [--workspace <path>] [--json]
71
+ tiangong-ai research policy init <project-id> [--source-root <absolute-installed-skill-root>] --article-type <id> --field <id> --journal-class <id> [--include-exact-journal-template] [--workspace <path>] [--json]
72
+ tiangong-ai research policy status <project-id> [--workspace <path>] [--json]
73
+ tiangong-ai research policy validate <project-id> [--workspace <path>] [--json]
74
+ tiangong-ai research policy approve <project-id> --confirm [--acknowledge-defaults] [--workspace <path>] [--json]
75
+ tiangong-ai research policy resolve <project-id> [--workspace <path>] [--json]
76
+ 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]
77
+ 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]
78
+ tiangong-ai research publication review submit <project-id> --role evidence|methods-reproducibility|domain-novelty|journal-editor --review <absolute-json> [--workspace <path>] [--json]
79
+ tiangong-ai research publication status <project-id> [--workspace <path>] [--json]
80
+ 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]
83
+ 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]
65
84
  tiangong-ai research project retry <project-id> [--package <package-id>] [--workspace <path>] [--json]
66
85
  tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|acquire|analyze|synthesize] [--workspace <path>] [--json]
67
86
  tiangong-ai research project addendum <closed-project-id> --to <target-project-id> [--workspace <path>] [--json]
@@ -78,13 +97,206 @@ export function researchOrchestrationHelp() {
78
97
  tiangong-ai research project evidence assessment record <project-id> --record <absolute-json> [--workspace <path>] [--json]
79
98
  tiangong-ai research project evidence download bind <project-id> --candidate <id> --record <absolute-json> [--workspace <path>] [--json]
80
99
  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]
81
- tiangong-ai research schema show <discover|acquire|analyze|synthesize|review|doctor> [--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]
82
101
  tiangong-ai research status [--project <project-id>] [--all] [--workspace <absolute-path>] [--json]
83
102
  tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
84
103
 
85
104
  ${researchSetupHelp()}
86
105
  `;
87
106
  }
107
+ async function runPublication(argv, io) {
108
+ const [action, ...rest] = argv;
109
+ if (!action || action === "--help" || action === "-h")
110
+ return writeHelp(io);
111
+ if (action === "freeze") {
112
+ const args = parseStrictArgs(rest, {
113
+ ...WORKSPACE_OPTIONS,
114
+ manuscript: "string",
115
+ assessment: "string",
116
+ supplements: "string",
117
+ "producer-agent": "string",
118
+ "producer-session": "string",
119
+ }, "research publication freeze");
120
+ if (strictBoolean(args, "help"))
121
+ return writeHelp(io);
122
+ const projectId = onePositional(args.positionals, "research publication freeze");
123
+ const manuscriptPath = strictString(args, "manuscript");
124
+ const assessmentPath = strictString(args, "assessment");
125
+ const producerSessionId = strictString(args, "producer-session");
126
+ if (!manuscriptPath || !assessmentPath || !producerSessionId) {
127
+ throw new CliError("research publication freeze requires --manuscript, --assessment, --producer-agent, and --producer-session.", { code: "RESEARCH_PUBLICATION_ARGUMENT_REQUIRED", exitCode: 2 });
128
+ }
129
+ const root = await workspaceFromArgs(args);
130
+ const supplementsPath = strictString(args, "supplements");
131
+ writeJson(io, await freezePublicationManuscript({
132
+ root,
133
+ projectId,
134
+ manuscriptPath,
135
+ assessmentPath,
136
+ supplementPaths: supplementsPath ? await readAbsolutePathArray(supplementsPath) : [],
137
+ producerAgent: publicationAgent(strictString(args, "producer-agent"), "producer"),
138
+ producerSessionId,
139
+ }), args);
140
+ return 0;
141
+ }
142
+ if (action === "review") {
143
+ const [reviewAction, ...reviewRest] = rest;
144
+ if (reviewAction === "--help" || reviewAction === "-h")
145
+ return writeHelp(io);
146
+ if (reviewAction === "prepare") {
147
+ const args = parseStrictArgs(reviewRest, {
148
+ ...WORKSPACE_OPTIONS,
149
+ role: "string",
150
+ "reviewer-agent": "string",
151
+ "reviewer-session": "string",
152
+ }, "research publication review prepare");
153
+ if (strictBoolean(args, "help"))
154
+ return writeHelp(io);
155
+ const projectId = onePositional(args.positionals, "research publication review prepare");
156
+ const reviewerSessionId = strictString(args, "reviewer-session");
157
+ if (!reviewerSessionId) {
158
+ throw new CliError("publication review prepare requires --reviewer-session.", {
159
+ code: "RESEARCH_PUBLICATION_ARGUMENT_REQUIRED",
160
+ exitCode: 2,
161
+ });
162
+ }
163
+ const root = await workspaceFromArgs(args);
164
+ writeJson(io, await preparePublicationReview({
165
+ root,
166
+ projectId,
167
+ role: publicationReviewRole(strictString(args, "role")),
168
+ reviewerAgent: publicationAgent(strictString(args, "reviewer-agent"), "reviewer"),
169
+ reviewerSessionId,
170
+ }), args);
171
+ return 0;
172
+ }
173
+ if (reviewAction === "submit") {
174
+ const args = parseStrictArgs(reviewRest, { ...WORKSPACE_OPTIONS, role: "string", review: "string" }, "research publication review submit");
175
+ if (strictBoolean(args, "help"))
176
+ return writeHelp(io);
177
+ const projectId = onePositional(args.positionals, "research publication review submit");
178
+ const reviewPath = strictString(args, "review");
179
+ if (!reviewPath) {
180
+ throw new CliError("publication review submit requires --review.", {
181
+ code: "RESEARCH_PUBLICATION_ARGUMENT_REQUIRED",
182
+ exitCode: 2,
183
+ });
184
+ }
185
+ const root = await workspaceFromArgs(args);
186
+ writeJson(io, await submitPublicationReview({
187
+ root,
188
+ projectId,
189
+ role: publicationReviewRole(strictString(args, "role")),
190
+ reviewPath,
191
+ }), args);
192
+ return 0;
193
+ }
194
+ throw unknownAction("research publication review", reviewAction ?? "missing");
195
+ }
196
+ if (action === "status" || action === "close") {
197
+ const args = parseStrictArgs(rest, WORKSPACE_OPTIONS, `research publication ${action}`);
198
+ if (strictBoolean(args, "help"))
199
+ return writeHelp(io);
200
+ const projectId = onePositional(args.positionals, `research publication ${action}`);
201
+ const root = await workspaceFromArgs(args);
202
+ writeJson(io, action === "status"
203
+ ? await inspectPublicationStatus(root, projectId)
204
+ : await closePublication(root, projectId), args);
205
+ return 0;
206
+ }
207
+ throw unknownAction("research publication", action);
208
+ }
209
+ async function runPolicy(argv, io) {
210
+ const [action, ...rest] = argv;
211
+ if (!action || action === "--help" || action === "-h")
212
+ return writeHelp(io);
213
+ if (action === "wizard") {
214
+ const args = parseStrictArgs(rest, WORKSPACE_OPTIONS, "research policy wizard");
215
+ if (strictBoolean(args, "help"))
216
+ return writeHelp(io);
217
+ const projectId = onePositional(args.positionals, "research policy wizard");
218
+ const root = await workspaceFromArgs(args);
219
+ return runInteractiveResearchPolicyWizard({
220
+ root,
221
+ projectId,
222
+ io,
223
+ json: strictBoolean(args, "json"),
224
+ });
225
+ }
226
+ if (action === "catalog") {
227
+ const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, "source-root": "string" }, "research policy catalog");
228
+ if (strictBoolean(args, "help"))
229
+ return writeHelp(io);
230
+ rejectPositionals(args.positionals, "research policy catalog");
231
+ const explicitSourceRoot = strictString(args, "source-root");
232
+ const sourceRoot = explicitSourceRoot ??
233
+ (await resolveInstalledResearchPolicySource(await workspaceFromArgs(args), io.env));
234
+ writeJson(io, await inspectResearchPolicyCatalog(sourceRoot), args);
235
+ return 0;
236
+ }
237
+ if (action === "init") {
238
+ const args = parseStrictArgs(rest, {
239
+ ...WORKSPACE_OPTIONS,
240
+ "source-root": "string",
241
+ "article-type": "string",
242
+ field: "string",
243
+ "journal-class": "string",
244
+ "include-exact-journal-template": "boolean",
245
+ }, "research policy init");
246
+ if (strictBoolean(args, "help"))
247
+ return writeHelp(io);
248
+ const projectId = onePositional(args.positionals, "research policy init");
249
+ const articleType = strictString(args, "article-type");
250
+ const field = strictString(args, "field");
251
+ const journalClass = strictString(args, "journal-class");
252
+ if (!articleType || !field || !journalClass) {
253
+ throw new CliError("research policy init requires --article-type, --field, and --journal-class.", { code: "RESEARCH_POLICY_INVALID", exitCode: 2 });
254
+ }
255
+ const root = await workspaceFromArgs(args);
256
+ const sourceRoot = strictString(args, "source-root") ??
257
+ (await resolveInstalledResearchPolicySource(root, io.env));
258
+ writeJson(io, await initializeResearchPolicy({
259
+ root,
260
+ projectId,
261
+ sourceRoot,
262
+ articleType,
263
+ field,
264
+ journalClass,
265
+ includeExactJournalTemplate: strictBoolean(args, "include-exact-journal-template"),
266
+ }), args);
267
+ return 0;
268
+ }
269
+ if (["status", "validate", "approve", "resolve"].includes(action)) {
270
+ const args = parseStrictArgs(rest, {
271
+ ...WORKSPACE_OPTIONS,
272
+ confirm: "boolean",
273
+ "acknowledge-defaults": "boolean",
274
+ }, `research policy ${action}`);
275
+ if (strictBoolean(args, "help"))
276
+ return writeHelp(io);
277
+ const projectId = onePositional(args.positionals, `research policy ${action}`);
278
+ const root = await workspaceFromArgs(args);
279
+ if (action === "approve") {
280
+ const result = await approveResearchPolicy(root, projectId, {
281
+ confirm: strictBoolean(args, "confirm"),
282
+ acknowledgeDefaults: strictBoolean(args, "acknowledge-defaults"),
283
+ });
284
+ writeJson(io, result, args);
285
+ return 0;
286
+ }
287
+ if (action === "resolve") {
288
+ writeJson(io, await loadApprovedResearchPolicy(root, projectId), args);
289
+ return 0;
290
+ }
291
+ const result = await inspectResearchPolicyStatus(root, projectId);
292
+ writeJson(io, result, args);
293
+ return action === "validate" &&
294
+ ["missing", "invalid", "changed", "stale"].includes(result.status)
295
+ ? 3
296
+ : 0;
297
+ }
298
+ throw unknownAction("research policy", action);
299
+ }
88
300
  async function runSchema(argv, io) {
89
301
  const [action, ...rest] = argv;
90
302
  if (!action || action === "--help" || action === "-h")
@@ -95,6 +307,15 @@ async function runSchema(argv, io) {
95
307
  if (strictBoolean(args, "help"))
96
308
  return writeHelp(io);
97
309
  const stage = onePositional(args.positionals, "research schema show");
310
+ if (stage === "publication-assessment") {
311
+ writeJson(io, publicationAssessmentSchema(), args);
312
+ return 0;
313
+ }
314
+ if (stage.startsWith("publication-review-")) {
315
+ const role = publicationReviewRole(stage.slice("publication-review-".length));
316
+ writeJson(io, publicationReviewSchema(role), args);
317
+ return 0;
318
+ }
98
319
  if (stage !== "discover" &&
99
320
  stage !== "acquire" &&
100
321
  stage !== "analyze" &&
@@ -576,6 +797,7 @@ async function runProject(argv, io) {
576
797
  const args = parseStrictArgs(rest, {
577
798
  ...WORKSPACE_OPTIONS,
578
799
  question: "string",
800
+ goal: "string",
579
801
  requirements: "string",
580
802
  "input-plan": "string",
581
803
  "confirm-budget": "boolean",
@@ -591,9 +813,10 @@ async function runProject(argv, io) {
591
813
  });
592
814
  }
593
815
  const root = await workspaceFromArgs(args);
816
+ const goal = researchGoal(strictString(args, "goal"));
594
817
  const requirementsPath = strictString(args, "requirements");
595
818
  const inputPlanPath = strictString(args, "input-plan");
596
- const project = await initializeProject(root, projectId, question, requirementsPath ? await readEvidenceRequirements(requirementsPath) : undefined, strictBoolean(args, "confirm-budget"), inputPlanPath ? await readAndVerifyProjectInputPlan(inputPlanPath) : undefined);
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);
597
820
  writeJson(io, project, args);
598
821
  return 0;
599
822
  }
@@ -601,6 +824,8 @@ async function runProject(argv, io) {
601
824
  const args = parseStrictArgs(rest, {
602
825
  ...WORKSPACE_OPTIONS,
603
826
  question: "string",
827
+ goal: "string",
828
+ "policy-project": "string",
604
829
  requirements: "string",
605
830
  "input-plan": "string",
606
831
  }, "research project preflight");
@@ -615,6 +840,23 @@ async function runProject(argv, io) {
615
840
  });
616
841
  }
617
842
  const root = await workspaceFromArgs(args);
843
+ const goal = researchGoal(strictString(args, "goal"));
844
+ const policyProject = strictString(args, "policy-project");
845
+ if (goal === "top-journal") {
846
+ if (!policyProject) {
847
+ throw new CliError("Top-journal preflight requires --policy-project.", {
848
+ code: "RESEARCH_POLICY_REQUIRED",
849
+ exitCode: 2,
850
+ });
851
+ }
852
+ await loadApprovedResearchPolicy(root, policyProject);
853
+ }
854
+ else if (policyProject) {
855
+ throw new CliError("--policy-project is only valid with --goal top-journal.", {
856
+ code: "RESEARCH_POLICY_INVALID",
857
+ exitCode: 2,
858
+ });
859
+ }
618
860
  const requirementsPath = strictString(args, "requirements");
619
861
  const requirements = requirementsPath ? await readEvidenceRequirements(requirementsPath) : null;
620
862
  const inputPlanPath = strictString(args, "input-plan");
@@ -629,7 +871,13 @@ async function runProject(argv, io) {
629
871
  return writeHelp(io);
630
872
  if (inputAction !== "add")
631
873
  throw unknownAction("research project input", inputAction ?? "");
632
- const args = parseStrictArgs(inputRest, { ...WORKSPACE_OPTIONS, path: "string", role: "string" }, "research project input add");
874
+ const args = parseStrictArgs(inputRest, {
875
+ ...WORKSPACE_OPTIONS,
876
+ path: "string",
877
+ role: "string",
878
+ "trust-status": "string",
879
+ "independently-reproduced": "boolean",
880
+ }, "research project input add");
633
881
  if (strictBoolean(args, "help"))
634
882
  return writeHelp(io);
635
883
  const projectId = onePositional(args.positionals, "research project input add");
@@ -642,7 +890,11 @@ async function runProject(argv, io) {
642
890
  }
643
891
  const role = inputRole(strictString(args, "role"));
644
892
  const root = await workspaceFromArgs(args);
645
- const input = await addProjectInput(root, projectId, inputPath, role);
893
+ const trustStatus = projectInputTrustStatus(strictString(args, "trust-status"), role);
894
+ const input = await addProjectInput(root, projectId, inputPath, role, {
895
+ ...(trustStatus ? { trustStatus } : {}),
896
+ independentlyReproduced: strictBoolean(args, "independently-reproduced"),
897
+ });
646
898
  writeJson(io, input, args);
647
899
  return 0;
648
900
  }
@@ -744,6 +996,9 @@ async function runStatus(argv, io) {
744
996
  const nativeStage = await inspectNativeResearchStage(root, current);
745
997
  const snapshot = await inspectSnapshotForStatus(root, current.id);
746
998
  const readyPackage = nextReadyPackage(current)?.id ?? null;
999
+ const publication = current.publicationPolicy
1000
+ ? await inspectPublicationForStatus(root, current.id)
1001
+ : null;
747
1002
  return {
748
1003
  id: current.id,
749
1004
  question: current.question,
@@ -754,8 +1009,9 @@ async function runStatus(argv, io) {
754
1009
  evidenceState: current.evidenceState,
755
1010
  snapshot,
756
1011
  nativeStage,
1012
+ publication,
757
1013
  readyPackage,
758
- recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage),
1014
+ recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage, publication),
759
1015
  usage: current.usage,
760
1016
  inputs: current.inputs,
761
1017
  packages: current.packages,
@@ -812,7 +1068,18 @@ async function inspectSnapshotForStatus(root, projectId) {
812
1068
  };
813
1069
  }
814
1070
  }
815
- function projectRecommendedAction(root, project, readyPackage, nativeStage) {
1071
+ async function inspectPublicationForStatus(root, projectId) {
1072
+ try {
1073
+ return await inspectPublicationStatus(root, projectId);
1074
+ }
1075
+ catch (error) {
1076
+ return {
1077
+ generationStatus: "invalid",
1078
+ code: error instanceof CliError ? error.code : "RESEARCH_PUBLICATION_STATE_INVALID",
1079
+ };
1080
+ }
1081
+ }
1082
+ function projectRecommendedAction(root, project, readyPackage, nativeStage, publication) {
816
1083
  if (project.lineage.supersededBy) {
817
1084
  return `Continue with superseding project ${project.lineage.supersededBy}.`;
818
1085
  }
@@ -832,6 +1099,21 @@ function projectRecommendedAction(root, project, readyPackage, nativeStage) {
832
1099
  return nativeStage.recommendedAction ?? "Resume the active native stage.";
833
1100
  }
834
1101
  if (project.status === "complete") {
1102
+ if (project.publicationPolicy) {
1103
+ if (publication && "code" in publication) {
1104
+ return `Publication state is invalid (${publication.code}); inspect Research Policy and frozen object bindings before continuing.`;
1105
+ }
1106
+ if (!publication || publication.generationStatus === "not-started") {
1107
+ return `Freeze the final manuscript and publication assessment, then inspect publication status: tiangong-ai research publication freeze ${project.id} --help`;
1108
+ }
1109
+ if (publication.closureSha256) {
1110
+ return `Publication closure is complete at ${publication.readinessVerdict}; inspect the immutable closure before post-closure authoring.`;
1111
+ }
1112
+ if (publication.reviewState !== "complete") {
1113
+ return `Complete fresh independent publication reviews for ${publication.missingReviewRoles.join(", ")}; inspect: tiangong-ai research publication status ${project.id} --workspace ${root}`;
1114
+ }
1115
+ return `Mechanically close the reviewed frozen manuscript: tiangong-ai research publication close ${project.id} --workspace ${root}`;
1116
+ }
835
1117
  return `Create an immutable evidence addendum only when new evidence exists: tiangong-ai research project addendum ${project.id} --to <new-project-id> --workspace ${root}`;
836
1118
  }
837
1119
  if (project.status === "blocked") {
@@ -933,6 +1215,16 @@ function researchMode(value) {
933
1215
  exitCode: 2,
934
1216
  });
935
1217
  }
1218
+ function researchGoal(value) {
1219
+ if (!value || value === "evidence-report")
1220
+ return "evidence-report";
1221
+ if (value === "top-journal")
1222
+ return value;
1223
+ throw new CliError(`Unsupported research goal: ${value}`, {
1224
+ code: "RESEARCH_GOAL_INVALID",
1225
+ exitCode: 2,
1226
+ });
1227
+ }
936
1228
  function resumeStage(value) {
937
1229
  if (!value)
938
1230
  return undefined;
@@ -1034,6 +1326,71 @@ function inputRole(value) {
1034
1326
  exitCode: 2,
1035
1327
  });
1036
1328
  }
1329
+ function projectInputTrustStatus(value, role) {
1330
+ if (!value)
1331
+ return undefined;
1332
+ if (value === "verified-owner-input" ||
1333
+ value === "unverified-owner-input" ||
1334
+ value === "reference-only" ||
1335
+ value === "replication-candidate") {
1336
+ return value;
1337
+ }
1338
+ throw new CliError(`Unsupported trust status for ${role} input: ${value}`, {
1339
+ code: "RESEARCH_INPUT_TRUST_INVALID",
1340
+ exitCode: 2,
1341
+ });
1342
+ }
1343
+ function publicationReviewRole(value) {
1344
+ if (value === "evidence" ||
1345
+ value === "methods-reproducibility" ||
1346
+ value === "domain-novelty" ||
1347
+ value === "journal-editor") {
1348
+ return value;
1349
+ }
1350
+ throw new CliError("--role must be evidence, methods-reproducibility, domain-novelty, or journal-editor.", { code: "RESEARCH_PUBLICATION_REVIEW_ROLE_INVALID", exitCode: 2 });
1351
+ }
1352
+ function publicationAgent(value, label) {
1353
+ if (value === "codex" || value === "claude")
1354
+ return value;
1355
+ throw new CliError(`--${label}-agent must be codex or claude.`, {
1356
+ code: "RESEARCH_PUBLICATION_AGENT_INVALID",
1357
+ exitCode: 2,
1358
+ });
1359
+ }
1360
+ async function readAbsolutePathArray(path) {
1361
+ if (!isAbsolute(path)) {
1362
+ throw new CliError("--supplements must be an absolute JSON file path.", {
1363
+ code: "RESEARCH_PUBLICATION_FILE_INVALID",
1364
+ exitCode: 2,
1365
+ });
1366
+ }
1367
+ const selected = resolve(path);
1368
+ const info = await lstat(selected).catch(() => undefined);
1369
+ if (!info?.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
1370
+ throw new CliError("--supplements must be a bounded regular non-symlink JSON file.", {
1371
+ code: "RESEARCH_PUBLICATION_FILE_INVALID",
1372
+ exitCode: 2,
1373
+ });
1374
+ }
1375
+ let value;
1376
+ try {
1377
+ value = JSON.parse(await readFile(selected, "utf8"));
1378
+ }
1379
+ catch {
1380
+ throw new CliError("--supplements contains invalid JSON.", {
1381
+ code: "RESEARCH_PUBLICATION_FILE_INVALID",
1382
+ exitCode: 2,
1383
+ });
1384
+ }
1385
+ if (!Array.isArray(value) ||
1386
+ value.some((item) => typeof item !== "string" || !isAbsolute(item) || resolve(item) !== item)) {
1387
+ throw new CliError("--supplements must contain an array of absolute canonical paths.", {
1388
+ code: "RESEARCH_PUBLICATION_FILE_INVALID",
1389
+ exitCode: 2,
1390
+ });
1391
+ }
1392
+ return value;
1393
+ }
1037
1394
  function integerOption(value, fallback, label) {
1038
1395
  if (!value)
1039
1396
  return fallback;