@tiangong-ai/cli 0.0.32 → 0.0.34
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.
- package/AGENTS.md +7 -2
- package/README.md +72 -19
- package/dist/research/commands.js +58 -1
- package/dist/research/commands.js.map +1 -1
- package/dist/research/orchestration.js +388 -31
- package/dist/research/orchestration.js.map +1 -1
- package/dist/research/workspace/acquisition.d.ts +71 -0
- package/dist/research/workspace/acquisition.js +593 -0
- package/dist/research/workspace/acquisition.js.map +1 -0
- package/dist/research/workspace/artifacts.d.ts +43 -0
- package/dist/research/workspace/artifacts.js +544 -0
- package/dist/research/workspace/artifacts.js.map +1 -0
- package/dist/research/workspace/broker.js +265 -56
- package/dist/research/workspace/broker.js.map +1 -1
- package/dist/research/workspace/constants.d.ts +1 -0
- package/dist/research/workspace/constants.js +4 -2
- package/dist/research/workspace/constants.js.map +1 -1
- package/dist/research/workspace/context.js +106 -11
- package/dist/research/workspace/context.js.map +1 -1
- package/dist/research/workspace/discovery-planning.d.ts +25 -0
- package/dist/research/workspace/discovery-planning.js +106 -0
- package/dist/research/workspace/discovery-planning.js.map +1 -0
- package/dist/research/workspace/discovery-status.d.ts +46 -0
- package/dist/research/workspace/discovery-status.js +183 -0
- package/dist/research/workspace/discovery-status.js.map +1 -0
- package/dist/research/workspace/discovery.d.ts +25 -0
- package/dist/research/workspace/discovery.js +268 -0
- package/dist/research/workspace/discovery.js.map +1 -0
- package/dist/research/workspace/downloads.d.ts +76 -0
- package/dist/research/workspace/downloads.js +274 -0
- package/dist/research/workspace/downloads.js.map +1 -0
- package/dist/research/workspace/evidence-ledger.d.ts +52 -0
- package/dist/research/workspace/evidence-ledger.js +487 -0
- package/dist/research/workspace/evidence-ledger.js.map +1 -0
- package/dist/research/workspace/evidence.d.ts +1 -0
- package/dist/research/workspace/evidence.js +2 -0
- package/dist/research/workspace/evidence.js.map +1 -1
- package/dist/research/workspace/input-plan.js +4 -0
- package/dist/research/workspace/input-plan.js.map +1 -1
- package/dist/research/workspace/native-activity.d.ts +65 -0
- package/dist/research/workspace/native-activity.js +153 -0
- package/dist/research/workspace/native-activity.js.map +1 -0
- package/dist/research/workspace/preflight.d.ts +5 -0
- package/dist/research/workspace/preflight.js +37 -17
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/projects.d.ts +3 -1
- package/dist/research/workspace/projects.js +346 -5
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/runtime.d.ts +106 -4
- package/dist/research/workspace/runtime.js +947 -80
- package/dist/research/workspace/runtime.js.map +1 -1
- package/dist/research/workspace/sanitization.js +28 -6
- package/dist/research/workspace/sanitization.js.map +1 -1
- package/dist/research/workspace/schemas.d.ts +3 -0
- package/dist/research/workspace/schemas.js +206 -33
- package/dist/research/workspace/schemas.js.map +1 -1
- package/dist/research/workspace/setup-catalog.js +5 -5
- package/dist/research/workspace/setup-invocation.d.ts +11 -0
- package/dist/research/workspace/setup-invocation.js +34 -0
- package/dist/research/workspace/setup-invocation.js.map +1 -0
- package/dist/research/workspace/setup-wizard.js +10 -4
- package/dist/research/workspace/setup-wizard.js.map +1 -1
- package/dist/research/workspace/setup.d.ts +55 -7
- package/dist/research/workspace/setup.js +603 -32
- package/dist/research/workspace/setup.js.map +1 -1
- package/dist/research/workspace/types.d.ts +52 -3
- package/dist/research/workspace/workspace.js +45 -6
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { CliError } from "../errors.js";
|
|
4
4
|
import { stringifyJson, write } from "../io.js";
|
|
5
5
|
import { parseStrictArgs, strictBoolean, strictString } from "../strict-args.js";
|
|
@@ -10,12 +10,19 @@ import { setCapabilityCredentialFromEnvironment } from "./workspace/credentials.
|
|
|
10
10
|
import { configureExternalSkillProfile, doctorExternalCapabilities, EXTERNAL_SKILL_CONTEXT_PROFILE, EXTERNAL_SKILL_MEDIA_PROFILE, EXTERNAL_SKILL_PROFILE, importExternalCapability, inspectExternalSkillCatalog, } from "./workspace/external-skills.js";
|
|
11
11
|
import { appendJournalEvent } from "./workspace/journal.js";
|
|
12
12
|
import { fetchNativeCandidateSource } from "./workspace/broker.js";
|
|
13
|
+
import { registerEvidenceArtifact } from "./workspace/artifacts.js";
|
|
14
|
+
import { loadCurrentEvidenceSnapshot } from "./workspace/acquisition.js";
|
|
15
|
+
import { inspectDiscoveryProgress } from "./workspace/discovery-status.js";
|
|
16
|
+
import { recordDiscoveryAssessmentBatch } from "./workspace/discovery.js";
|
|
17
|
+
import { bindEvidenceDownload } from "./workspace/downloads.js";
|
|
18
|
+
import { registerNativeDiscoveryCandidate } from "./workspace/evidence-ledger.js";
|
|
19
|
+
import { recordNativeResearchActivity } from "./workspace/native-activity.js";
|
|
13
20
|
import { readAndVerifyProjectInputPlan } from "./workspace/input-plan.js";
|
|
14
|
-
import { addProjectInput, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, } from "./workspace/projects.js";
|
|
21
|
+
import { addProjectInput, createProjectAddendum, initializeProject, forkProject, listProjects, loadProject, nextReadyPackage, normalizeEvidenceRequirements, refreshProject, retryProjectPackage, setProjectDisposition, } from "./workspace/projects.js";
|
|
15
22
|
import { evaluateProjectPreflight } from "./workspace/preflight.js";
|
|
16
|
-
import { abortNativeResearchStage, prepareNativeResearchStage, runResearchWorkspace, submitNativeResearchStage, } from "./workspace/runtime.js";
|
|
23
|
+
import { abortNativeResearchStage, inspectNativeResearchStage, prepareNativeResearchStage, requestResearchHandoff, resolveResearchHandoff, runResearchWorkspace, submitNativeResearchStage, } from "./workspace/runtime.js";
|
|
17
24
|
import { schemaForStage } from "./workspace/schemas.js";
|
|
18
|
-
import { sha256Text, workspacePaths } from "./workspace/storage.js";
|
|
25
|
+
import { pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
|
|
19
26
|
import { doctorResearchWorkspace, initializeResearchWorkspace, requireResearchWorkspace, withWorkspaceLock, } from "./workspace/workspace.js";
|
|
20
27
|
const COMMON_OPTIONS = { help: "boolean", json: "boolean" };
|
|
21
28
|
const WORKSPACE_OPTIONS = { ...COMMON_OPTIONS, workspace: "string" };
|
|
@@ -56,13 +63,23 @@ export function researchOrchestrationHelp() {
|
|
|
56
63
|
tiangong-ai research project preflight --question <question> [--requirements <absolute-json>] [--input-plan <absolute-json>] [--workspace <path>] [--json]
|
|
57
64
|
tiangong-ai research project input add <project-id> --path <absolute-file> [--role primary|reference|replication] [--workspace <path>] [--json]
|
|
58
65
|
tiangong-ai research project retry <project-id> [--package <package-id>] [--workspace <path>] [--json]
|
|
59
|
-
tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|analyze|synthesize] [--workspace <path>] [--json]
|
|
60
|
-
tiangong-ai research project
|
|
66
|
+
tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|acquire|analyze|synthesize] [--workspace <path>] [--json]
|
|
67
|
+
tiangong-ai research project addendum <closed-project-id> --to <target-project-id> [--workspace <path>] [--json]
|
|
68
|
+
tiangong-ai research project archive <project-id> --reason <text> [--workspace <path>] [--json]
|
|
69
|
+
tiangong-ai research project abandon <project-id> --reason <text> [--workspace <path>] [--json]
|
|
70
|
+
tiangong-ai research project handoff request <project-id> --record <absolute-json> [--workspace <path>] [--json]
|
|
71
|
+
tiangong-ai research project handoff resolve <project-id> --note <text> [--workspace <path>] [--json]
|
|
72
|
+
tiangong-ai research project stage prepare <project-id> --stage discover|acquire|analyze|synthesize --host-agent codex|claude [--workspace <path>] [--json]
|
|
61
73
|
tiangong-ai research project stage submit <project-id> --session <id> --output <absolute-json> [--confirm-model <id>] [--workspace <path>] [--json]
|
|
62
74
|
tiangong-ai research project stage abort <project-id> --session <id> [--workspace <path>] [--json]
|
|
63
75
|
tiangong-ai research project evidence fetch <project-id> --request <absolute-json> [--workspace <path>] [--json]
|
|
64
|
-
tiangong-ai research
|
|
65
|
-
tiangong-ai research
|
|
76
|
+
tiangong-ai research project evidence activity record <project-id> --record <absolute-json> [--workspace <path>] [--json]
|
|
77
|
+
tiangong-ai research project evidence candidate register <project-id> --record <absolute-json> [--workspace <path>] [--json]
|
|
78
|
+
tiangong-ai research project evidence assessment record <project-id> --record <absolute-json> [--workspace <path>] [--json]
|
|
79
|
+
tiangong-ai research project evidence download bind <project-id> --candidate <id> --record <absolute-json> [--workspace <path>] [--json]
|
|
80
|
+
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]
|
|
82
|
+
tiangong-ai research status [--project <project-id>] [--all] [--workspace <absolute-path>] [--json]
|
|
66
83
|
tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
|
|
67
84
|
|
|
68
85
|
${researchSetupHelp()}
|
|
@@ -79,6 +96,7 @@ async function runSchema(argv, io) {
|
|
|
79
96
|
return writeHelp(io);
|
|
80
97
|
const stage = onePositional(args.positionals, "research schema show");
|
|
81
98
|
if (stage !== "discover" &&
|
|
99
|
+
stage !== "acquire" &&
|
|
82
100
|
stage !== "analyze" &&
|
|
83
101
|
stage !== "synthesize" &&
|
|
84
102
|
stage !== "review" &&
|
|
@@ -281,6 +299,45 @@ async function runProject(argv, io) {
|
|
|
281
299
|
const [action, ...rest] = argv;
|
|
282
300
|
if (!action || action === "--help" || action === "-h")
|
|
283
301
|
return writeHelp(io);
|
|
302
|
+
if (action === "handoff") {
|
|
303
|
+
const [handoffAction, ...handoffRest] = rest;
|
|
304
|
+
if (handoffAction === "request") {
|
|
305
|
+
const args = parseStrictArgs(handoffRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project handoff request");
|
|
306
|
+
if (strictBoolean(args, "help"))
|
|
307
|
+
return writeHelp(io);
|
|
308
|
+
const projectId = onePositional(args.positionals, "research project handoff request");
|
|
309
|
+
const recordPath = strictString(args, "record");
|
|
310
|
+
if (!recordPath) {
|
|
311
|
+
throw new CliError("handoff request requires --record.", {
|
|
312
|
+
code: "RESEARCH_PROJECT_HANDOFF_INVALID",
|
|
313
|
+
exitCode: 2,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const root = await workspaceFromArgs(args);
|
|
317
|
+
const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_PROJECT_HANDOFF_INVALID");
|
|
318
|
+
const result = await requestResearchHandoff({ root, projectId, value: record });
|
|
319
|
+
writeJson(io, result, args);
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
if (handoffAction === "resolve") {
|
|
323
|
+
const args = parseStrictArgs(handoffRest, { ...WORKSPACE_OPTIONS, note: "string" }, "research project handoff resolve");
|
|
324
|
+
if (strictBoolean(args, "help"))
|
|
325
|
+
return writeHelp(io);
|
|
326
|
+
const projectId = onePositional(args.positionals, "research project handoff resolve");
|
|
327
|
+
const note = strictString(args, "note");
|
|
328
|
+
if (!note) {
|
|
329
|
+
throw new CliError("handoff resolve requires --note.", {
|
|
330
|
+
code: "RESEARCH_PROJECT_HANDOFF_INVALID",
|
|
331
|
+
exitCode: 2,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const root = await workspaceFromArgs(args);
|
|
335
|
+
const result = await resolveResearchHandoff({ root, projectId, note });
|
|
336
|
+
writeJson(io, result, args);
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
throw unknownAction("research project handoff", handoffAction ?? "");
|
|
340
|
+
}
|
|
284
341
|
if (action === "stage") {
|
|
285
342
|
const [stageAction, ...stageRest] = rest;
|
|
286
343
|
if (stageAction === "prepare") {
|
|
@@ -345,6 +402,156 @@ async function runProject(argv, io) {
|
|
|
345
402
|
}
|
|
346
403
|
if (action === "evidence") {
|
|
347
404
|
const [evidenceAction, ...evidenceRest] = rest;
|
|
405
|
+
if (evidenceAction === "activity") {
|
|
406
|
+
const [activityAction, ...activityRest] = evidenceRest;
|
|
407
|
+
if (activityAction !== "record") {
|
|
408
|
+
throw unknownAction("research project evidence activity", activityAction ?? "");
|
|
409
|
+
}
|
|
410
|
+
const args = parseStrictArgs(activityRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence activity record");
|
|
411
|
+
if (strictBoolean(args, "help"))
|
|
412
|
+
return writeHelp(io);
|
|
413
|
+
const projectId = onePositional(args.positionals, "research project evidence activity record");
|
|
414
|
+
const recordPath = strictString(args, "record");
|
|
415
|
+
if (!recordPath) {
|
|
416
|
+
throw new CliError("activity record requires --record.", {
|
|
417
|
+
code: "RESEARCH_NATIVE_ACTIVITY_INVALID",
|
|
418
|
+
exitCode: 2,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const root = await workspaceFromArgs(args);
|
|
422
|
+
const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_NATIVE_ACTIVITY_INVALID");
|
|
423
|
+
const result = await withWorkspaceLock(root, "research.native-activity.record", () => recordNativeResearchActivity({ root, projectId, value: record }));
|
|
424
|
+
writeJson(io, result, args);
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
if (evidenceAction === "candidate") {
|
|
428
|
+
const [candidateAction, ...candidateRest] = evidenceRest;
|
|
429
|
+
if (candidateAction !== "register") {
|
|
430
|
+
throw unknownAction("research project evidence candidate", candidateAction ?? "");
|
|
431
|
+
}
|
|
432
|
+
const args = parseStrictArgs(candidateRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence candidate register");
|
|
433
|
+
if (strictBoolean(args, "help"))
|
|
434
|
+
return writeHelp(io);
|
|
435
|
+
const projectId = onePositional(args.positionals, "research project evidence candidate register");
|
|
436
|
+
const recordPath = strictString(args, "record");
|
|
437
|
+
if (!recordPath) {
|
|
438
|
+
throw new CliError("candidate register requires --record.", {
|
|
439
|
+
code: "RESEARCH_NATIVE_CANDIDATE_INVALID",
|
|
440
|
+
exitCode: 2,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
const root = await workspaceFromArgs(args);
|
|
444
|
+
const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_NATIVE_CANDIDATE_INVALID");
|
|
445
|
+
const result = await withWorkspaceLock(root, "research.native-candidate.register", () => registerNativeDiscoveryCandidate({ root, projectId, value: record }));
|
|
446
|
+
writeJson(io, result, args);
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
if (evidenceAction === "assessment") {
|
|
450
|
+
const [assessmentAction, ...assessmentRest] = evidenceRest;
|
|
451
|
+
if (assessmentAction !== "record") {
|
|
452
|
+
throw unknownAction("research project evidence assessment", assessmentAction ?? "");
|
|
453
|
+
}
|
|
454
|
+
const args = parseStrictArgs(assessmentRest, { ...WORKSPACE_OPTIONS, record: "string" }, "research project evidence assessment record");
|
|
455
|
+
if (strictBoolean(args, "help"))
|
|
456
|
+
return writeHelp(io);
|
|
457
|
+
const projectId = onePositional(args.positionals, "research project evidence assessment record");
|
|
458
|
+
const recordPath = strictString(args, "record");
|
|
459
|
+
if (!recordPath) {
|
|
460
|
+
throw new CliError("assessment record requires --record.", {
|
|
461
|
+
code: "RESEARCH_DISCOVERY_ASSESSMENT_INVALID",
|
|
462
|
+
exitCode: 2,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const root = await workspaceFromArgs(args);
|
|
466
|
+
const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_DISCOVERY_ASSESSMENT_INVALID");
|
|
467
|
+
const result = await withWorkspaceLock(root, "research.discovery-assessment.record", () => recordDiscoveryAssessmentBatch({ root, projectId, value: record }));
|
|
468
|
+
writeJson(io, result, args);
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
if (evidenceAction === "download") {
|
|
472
|
+
const [downloadAction, ...downloadRest] = evidenceRest;
|
|
473
|
+
if (downloadAction !== "bind") {
|
|
474
|
+
throw unknownAction("research project evidence download", downloadAction ?? "");
|
|
475
|
+
}
|
|
476
|
+
const args = parseStrictArgs(downloadRest, { ...WORKSPACE_OPTIONS, candidate: "string", record: "string" }, "research project evidence download bind");
|
|
477
|
+
if (strictBoolean(args, "help"))
|
|
478
|
+
return writeHelp(io);
|
|
479
|
+
const projectId = onePositional(args.positionals, "research project evidence download bind");
|
|
480
|
+
const candidateId = strictString(args, "candidate");
|
|
481
|
+
const recordPath = strictString(args, "record");
|
|
482
|
+
if (!candidateId || !recordPath) {
|
|
483
|
+
throw new CliError("download bind requires --candidate and --record.", {
|
|
484
|
+
code: "RESEARCH_DOWNLOAD_BINDING_INVALID",
|
|
485
|
+
exitCode: 2,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const root = await workspaceFromArgs(args);
|
|
489
|
+
const record = await readBoundedJsonRecord(recordPath, "--record", "RESEARCH_DOWNLOAD_BINDING_INVALID");
|
|
490
|
+
const result = await withWorkspaceLock(root, "research.download.bind", () => bindEvidenceDownload({ root, projectId, candidateId, value: record }));
|
|
491
|
+
writeJson(io, result, args);
|
|
492
|
+
return result.status === "completed" ? 0 : 3;
|
|
493
|
+
}
|
|
494
|
+
if (evidenceAction === "artifact") {
|
|
495
|
+
const [artifactAction, ...artifactRest] = evidenceRest;
|
|
496
|
+
if (artifactAction !== "register") {
|
|
497
|
+
throw unknownAction("research project evidence artifact", artifactAction ?? "");
|
|
498
|
+
}
|
|
499
|
+
const args = parseStrictArgs(artifactRest, {
|
|
500
|
+
...WORKSPACE_OPTIONS,
|
|
501
|
+
candidate: "string",
|
|
502
|
+
path: "string",
|
|
503
|
+
"media-type": "string",
|
|
504
|
+
"source-url": "string",
|
|
505
|
+
license: "string",
|
|
506
|
+
"license-url": "string",
|
|
507
|
+
"host-type": "string",
|
|
508
|
+
"article-version": "string",
|
|
509
|
+
"download-binding": "string",
|
|
510
|
+
"derived-from-artifact": "string",
|
|
511
|
+
}, "research project evidence artifact register");
|
|
512
|
+
if (strictBoolean(args, "help"))
|
|
513
|
+
return writeHelp(io);
|
|
514
|
+
const projectId = onePositional(args.positionals, "research project evidence artifact register");
|
|
515
|
+
const candidateId = strictString(args, "candidate");
|
|
516
|
+
const path = strictString(args, "path");
|
|
517
|
+
if (!candidateId || !path) {
|
|
518
|
+
throw new CliError("artifact register requires --candidate and --path.", {
|
|
519
|
+
code: "RESEARCH_ARTIFACT_PATH_INVALID",
|
|
520
|
+
exitCode: 2,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
const root = await workspaceFromArgs(args);
|
|
524
|
+
const result = await withWorkspaceLock(root, "research.artifact.register", () => registerEvidenceArtifact({
|
|
525
|
+
root,
|
|
526
|
+
projectId,
|
|
527
|
+
candidateId,
|
|
528
|
+
path,
|
|
529
|
+
...(strictString(args, "media-type")
|
|
530
|
+
? { mediaType: strictString(args, "media-type") }
|
|
531
|
+
: {}),
|
|
532
|
+
...(strictString(args, "source-url")
|
|
533
|
+
? { sourceUrl: strictString(args, "source-url") }
|
|
534
|
+
: {}),
|
|
535
|
+
...(strictString(args, "license") ? { license: strictString(args, "license") } : {}),
|
|
536
|
+
...(strictString(args, "license-url")
|
|
537
|
+
? { licenseUrl: strictString(args, "license-url") }
|
|
538
|
+
: {}),
|
|
539
|
+
...(strictString(args, "host-type")
|
|
540
|
+
? { hostType: strictString(args, "host-type") }
|
|
541
|
+
: {}),
|
|
542
|
+
...(strictString(args, "article-version")
|
|
543
|
+
? { articleVersion: strictString(args, "article-version") }
|
|
544
|
+
: {}),
|
|
545
|
+
...(strictString(args, "download-binding")
|
|
546
|
+
? { downloadBindingId: strictString(args, "download-binding") }
|
|
547
|
+
: {}),
|
|
548
|
+
...(strictString(args, "derived-from-artifact")
|
|
549
|
+
? { derivedFromArtifactId: strictString(args, "derived-from-artifact") }
|
|
550
|
+
: {}),
|
|
551
|
+
}));
|
|
552
|
+
writeJson(io, result, args);
|
|
553
|
+
return 0;
|
|
554
|
+
}
|
|
348
555
|
if (evidenceAction !== "fetch") {
|
|
349
556
|
throw unknownAction("research project evidence", evidenceAction ?? "");
|
|
350
557
|
}
|
|
@@ -466,33 +673,180 @@ async function runProject(argv, io) {
|
|
|
466
673
|
writeJson(io, project, args);
|
|
467
674
|
return 0;
|
|
468
675
|
}
|
|
676
|
+
if (action === "addendum") {
|
|
677
|
+
const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, to: "string" }, "research project addendum");
|
|
678
|
+
if (strictBoolean(args, "help"))
|
|
679
|
+
return writeHelp(io);
|
|
680
|
+
const sourceProjectId = onePositional(args.positionals, "research project addendum");
|
|
681
|
+
const targetProjectId = strictString(args, "to");
|
|
682
|
+
if (!targetProjectId) {
|
|
683
|
+
throw new CliError("research project addendum requires --to.", {
|
|
684
|
+
code: "RESEARCH_PROJECT_ADDENDUM_INVALID",
|
|
685
|
+
exitCode: 2,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const root = await workspaceFromArgs(args);
|
|
689
|
+
const project = await createProjectAddendum(root, sourceProjectId, targetProjectId);
|
|
690
|
+
writeJson(io, project, args);
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
if (action === "archive" || action === "abandon") {
|
|
694
|
+
const args = parseStrictArgs(rest, { ...WORKSPACE_OPTIONS, reason: "string" }, `research project ${action}`);
|
|
695
|
+
if (strictBoolean(args, "help"))
|
|
696
|
+
return writeHelp(io);
|
|
697
|
+
const projectId = onePositional(args.positionals, `research project ${action}`);
|
|
698
|
+
const reason = strictString(args, "reason");
|
|
699
|
+
if (!reason) {
|
|
700
|
+
throw new CliError(`research project ${action} requires --reason.`, {
|
|
701
|
+
code: "RESEARCH_PROJECT_DISPOSITION_INVALID",
|
|
702
|
+
exitCode: 2,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const root = await workspaceFromArgs(args);
|
|
706
|
+
const project = await setProjectDisposition(root, projectId, action === "archive" ? "archived" : "abandoned", reason);
|
|
707
|
+
writeJson(io, project, args);
|
|
708
|
+
return 0;
|
|
709
|
+
}
|
|
469
710
|
throw unknownAction("research project", action);
|
|
470
711
|
}
|
|
471
712
|
async function runStatus(argv, io) {
|
|
472
|
-
const args = parseStrictArgs(argv, { ...WORKSPACE_OPTIONS, project: "string" }, "research status");
|
|
713
|
+
const args = parseStrictArgs(argv, { ...WORKSPACE_OPTIONS, project: "string", all: "boolean" }, "research status");
|
|
473
714
|
if (strictBoolean(args, "help"))
|
|
474
715
|
return writeHelp(io);
|
|
475
716
|
rejectPositionals(args.positionals, "research status");
|
|
476
717
|
const root = await workspaceFromArgs(args);
|
|
477
718
|
const selectedProject = strictString(args, "project");
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
719
|
+
const workspaceProjects = await listProjects(root);
|
|
720
|
+
const allProjects = selectedProject
|
|
721
|
+
? [
|
|
722
|
+
workspaceProjects.find((project) => project.id === selectedProject) ??
|
|
723
|
+
(await loadProject(root, selectedProject)),
|
|
724
|
+
]
|
|
725
|
+
: workspaceProjects;
|
|
726
|
+
const projects = selectedProject || strictBoolean(args, "all")
|
|
727
|
+
? allProjects
|
|
728
|
+
: allProjects.filter((project) => project.lineage.supersededBy === null &&
|
|
729
|
+
project.status !== "archived" &&
|
|
730
|
+
project.status !== "abandoned");
|
|
481
731
|
const result = {
|
|
482
732
|
workspace: root,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
733
|
+
hiddenSupersededProjects: selectedProject || strictBoolean(args, "all")
|
|
734
|
+
? 0
|
|
735
|
+
: workspaceProjects.filter((project) => project.lineage.supersededBy !== null).length,
|
|
736
|
+
hiddenArchivedProjects: selectedProject || strictBoolean(args, "all")
|
|
737
|
+
? 0
|
|
738
|
+
: workspaceProjects.filter((project) => project.status === "archived").length,
|
|
739
|
+
hiddenAbandonedProjects: selectedProject || strictBoolean(args, "all")
|
|
740
|
+
? 0
|
|
741
|
+
: workspaceProjects.filter((project) => project.status === "abandoned").length,
|
|
742
|
+
projects: await Promise.all(projects.map(async (project) => {
|
|
743
|
+
const current = refreshProject(project);
|
|
744
|
+
const nativeStage = await inspectNativeResearchStage(root, current);
|
|
745
|
+
const snapshot = await inspectSnapshotForStatus(root, current.id);
|
|
746
|
+
const readyPackage = nextReadyPackage(current)?.id ?? null;
|
|
747
|
+
return {
|
|
748
|
+
id: current.id,
|
|
749
|
+
question: current.question,
|
|
750
|
+
status: current.status,
|
|
751
|
+
authority: projectAuthority(current, workspaceProjects),
|
|
752
|
+
lineage: current.lineage,
|
|
753
|
+
handoff: current.handoff,
|
|
754
|
+
evidenceState: current.evidenceState,
|
|
755
|
+
snapshot,
|
|
756
|
+
nativeStage,
|
|
757
|
+
readyPackage,
|
|
758
|
+
recommendedAction: projectRecommendedAction(root, current, readyPackage, nativeStage),
|
|
759
|
+
usage: current.usage,
|
|
760
|
+
inputs: current.inputs,
|
|
761
|
+
packages: current.packages,
|
|
762
|
+
discovery: await inspectDiscoveryProgress(root, current),
|
|
763
|
+
};
|
|
491
764
|
})),
|
|
492
765
|
};
|
|
493
766
|
writeJson(io, result, args);
|
|
494
767
|
return 0;
|
|
495
768
|
}
|
|
769
|
+
function projectAuthority(project, projects) {
|
|
770
|
+
const byId = new Map(projects.map((candidate) => [candidate.id, candidate]));
|
|
771
|
+
const visited = new Set([project.id]);
|
|
772
|
+
let current = project;
|
|
773
|
+
while (current.lineage.supersededBy) {
|
|
774
|
+
if (visited.has(current.lineage.supersededBy))
|
|
775
|
+
break;
|
|
776
|
+
visited.add(current.lineage.supersededBy);
|
|
777
|
+
const next = byId.get(current.lineage.supersededBy);
|
|
778
|
+
if (!next)
|
|
779
|
+
return { state: "superseded", projectId: current.lineage.supersededBy };
|
|
780
|
+
current = next;
|
|
781
|
+
}
|
|
782
|
+
if (project.status === "archived")
|
|
783
|
+
return { state: "archived", projectId: current.id };
|
|
784
|
+
if (project.status === "abandoned")
|
|
785
|
+
return { state: "abandoned", projectId: current.id };
|
|
786
|
+
return {
|
|
787
|
+
state: project.id === current.id ? "authoritative" : "superseded",
|
|
788
|
+
projectId: current.id,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
async function inspectSnapshotForStatus(root, projectId) {
|
|
792
|
+
const currentPath = join(workspacePaths(root).projects, projectId, "outputs", "evidence-snapshot.json");
|
|
793
|
+
if (!(await pathExists(currentPath)))
|
|
794
|
+
return { status: "absent" };
|
|
795
|
+
try {
|
|
796
|
+
const snapshot = await loadCurrentEvidenceSnapshot(root, projectId);
|
|
797
|
+
return {
|
|
798
|
+
status: "verified",
|
|
799
|
+
snapshotId: snapshot.snapshotId,
|
|
800
|
+
snapshotSha256: snapshot.snapshotSha256,
|
|
801
|
+
parentSnapshotId: snapshot.parentSnapshotId,
|
|
802
|
+
sourceCount: snapshot.sources.length,
|
|
803
|
+
artifactCount: snapshot.artifacts.length,
|
|
804
|
+
delta: snapshot.delta,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
catch (error) {
|
|
808
|
+
const code = error instanceof CliError ? error.code : "RESEARCH_EVIDENCE_SNAPSHOT_INVALID";
|
|
809
|
+
return {
|
|
810
|
+
status: "invalid",
|
|
811
|
+
code,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function projectRecommendedAction(root, project, readyPackage, nativeStage) {
|
|
816
|
+
if (project.lineage.supersededBy) {
|
|
817
|
+
return `Continue with superseding project ${project.lineage.supersededBy}.`;
|
|
818
|
+
}
|
|
819
|
+
if (project.status === "archived" || project.status === "abandoned") {
|
|
820
|
+
return `Project is ${project.status}; inspect with research status --all and continue only from an authoritative project.`;
|
|
821
|
+
}
|
|
822
|
+
if (project.handoff.state === "user-action-required") {
|
|
823
|
+
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}`;
|
|
824
|
+
}
|
|
825
|
+
if (project.handoff.state === "external-response-required") {
|
|
826
|
+
return `Waiting for an external response: ${project.handoff.summary ?? "external evidence is pending"} Do not continue substitute searching; resolve after the response is registered.`;
|
|
827
|
+
}
|
|
828
|
+
if (nativeStage.status === "stale" || nativeStage.status === "invalid") {
|
|
829
|
+
return nativeStage.recommendedAction ?? "Recover the stale native session explicitly.";
|
|
830
|
+
}
|
|
831
|
+
if (nativeStage.status === "active") {
|
|
832
|
+
return nativeStage.recommendedAction ?? "Resume the active native stage.";
|
|
833
|
+
}
|
|
834
|
+
if (project.status === "complete") {
|
|
835
|
+
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
|
+
}
|
|
837
|
+
if (project.status === "blocked") {
|
|
838
|
+
const failed = project.packages.find((workPackage) => workPackage.status === "failed");
|
|
839
|
+
return failed
|
|
840
|
+
? `Review ${failed.lastFailureKind ?? "deterministic"} failure for ${failed.id}, then run the explicit project retry command if corrected.`
|
|
841
|
+
: "Inspect the blocking package and use explicit retry or fork recovery.";
|
|
842
|
+
}
|
|
843
|
+
if (readyPackage && ["discover", "acquire", "analyze", "synthesize"].includes(readyPackage)) {
|
|
844
|
+
return `Prepare native ${readyPackage}: tiangong-ai research project stage prepare ${project.id} --stage ${readyPackage} --host-agent <codex|claude> --workspace ${root}`;
|
|
845
|
+
}
|
|
846
|
+
return readyPackage === "review"
|
|
847
|
+
? `Run the independent reviewer package: tiangong-ai research run --project ${project.id} --workspace ${root}`
|
|
848
|
+
: "Continue the next ready package.";
|
|
849
|
+
}
|
|
496
850
|
async function runWorkspaceExecution(argv, io) {
|
|
497
851
|
const args = parseStrictArgs(argv, {
|
|
498
852
|
...WORKSPACE_OPTIONS,
|
|
@@ -582,7 +936,7 @@ function researchMode(value) {
|
|
|
582
936
|
function resumeStage(value) {
|
|
583
937
|
if (!value)
|
|
584
938
|
return undefined;
|
|
585
|
-
if (value === "discover" || value === "analyze" || value === "synthesize")
|
|
939
|
+
if (value === "discover" || value === "acquire" || value === "analyze" || value === "synthesize")
|
|
586
940
|
return value;
|
|
587
941
|
throw new CliError(`Unsupported --resume-through stage: ${value}`, {
|
|
588
942
|
code: "RESEARCH_PROJECT_FORK_INVALID",
|
|
@@ -590,9 +944,9 @@ function resumeStage(value) {
|
|
|
590
944
|
});
|
|
591
945
|
}
|
|
592
946
|
function nativeProducerStage(value) {
|
|
593
|
-
if (value === "discover" || value === "analyze" || value === "synthesize")
|
|
947
|
+
if (value === "discover" || value === "acquire" || value === "analyze" || value === "synthesize")
|
|
594
948
|
return value;
|
|
595
|
-
throw new CliError("--stage must be discover, analyze, or synthesize.", {
|
|
949
|
+
throw new CliError("--stage must be discover, acquire, analyze, or synthesize.", {
|
|
596
950
|
code: "RESEARCH_NATIVE_STAGE_INVALID",
|
|
597
951
|
exitCode: 2,
|
|
598
952
|
});
|
|
@@ -606,17 +960,20 @@ function nativeHostAgent(value) {
|
|
|
606
960
|
});
|
|
607
961
|
}
|
|
608
962
|
async function readNativeEvidenceRequest(path) {
|
|
963
|
+
return readBoundedJsonRecord(path, "--request", "RESEARCH_BROKER_REQUEST_INVALID");
|
|
964
|
+
}
|
|
965
|
+
async function readBoundedJsonRecord(path, label, code) {
|
|
609
966
|
if (!isAbsolute(path)) {
|
|
610
|
-
throw new CliError(
|
|
611
|
-
code
|
|
967
|
+
throw new CliError(`${label} must be an absolute JSON file path.`, {
|
|
968
|
+
code,
|
|
612
969
|
exitCode: 2,
|
|
613
970
|
});
|
|
614
971
|
}
|
|
615
972
|
const selected = resolve(path);
|
|
616
973
|
const info = await lstat(selected).catch(() => undefined);
|
|
617
974
|
if (!info?.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
|
|
618
|
-
throw new CliError(
|
|
619
|
-
code
|
|
975
|
+
throw new CliError(`${label} must be a bounded regular non-symlink JSON file.`, {
|
|
976
|
+
code,
|
|
620
977
|
exitCode: 2,
|
|
621
978
|
});
|
|
622
979
|
}
|
|
@@ -625,14 +982,14 @@ async function readNativeEvidenceRequest(path) {
|
|
|
625
982
|
value = JSON.parse(await readFile(selected, "utf8"));
|
|
626
983
|
}
|
|
627
984
|
catch {
|
|
628
|
-
throw new CliError(
|
|
629
|
-
code
|
|
985
|
+
throw new CliError(`${label} contains invalid JSON.`, {
|
|
986
|
+
code,
|
|
630
987
|
exitCode: 2,
|
|
631
988
|
});
|
|
632
989
|
}
|
|
633
990
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
634
|
-
throw new CliError(
|
|
635
|
-
code
|
|
991
|
+
throw new CliError(`${label} must contain one JSON object.`, {
|
|
992
|
+
code,
|
|
636
993
|
exitCode: 2,
|
|
637
994
|
});
|
|
638
995
|
}
|