@tiangong-ai/cli 0.0.31 → 0.0.33
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 +2 -2
- package/README.md +140 -98
- package/dist/research/orchestration.js +519 -21
- 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.d.ts +5 -0
- package/dist/research/workspace/broker.js +318 -43
- package/dist/research/workspace/broker.js.map +1 -1
- package/dist/research/workspace/companion-readiness.d.ts +11 -0
- package/dist/research/workspace/companion-readiness.js +75 -0
- package/dist/research/workspace/companion-readiness.js.map +1 -0
- 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 +18 -0
- package/dist/research/workspace/preflight.js +47 -20
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/projects.d.ts +3 -1
- package/dist/research/workspace/projects.js +357 -5
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/runtime.d.ts +171 -3
- package/dist/research/workspace/runtime.js +1582 -77
- 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 +2 -2
- package/dist/research/workspace/setup-wizard.js +45 -17
- package/dist/research/workspace/setup-wizard.js.map +1 -1
- package/dist/research/workspace/setup.d.ts +27 -5
- package/dist/research/workspace/setup.js +400 -85
- package/dist/research/workspace/setup.js.map +1 -1
- package/dist/research/workspace/types.d.ts +38 -3
- package/dist/research/workspace/workspace.d.ts +2 -0
- package/dist/research/workspace/workspace.js +128 -26
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute } from "node:path";
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
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";
|
|
@@ -9,12 +9,20 @@ import { inspectResearchContext } from "./workspace/context.js";
|
|
|
9
9
|
import { setCapabilityCredentialFromEnvironment } from "./workspace/credentials.js";
|
|
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
|
+
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";
|
|
12
20
|
import { readAndVerifyProjectInputPlan } from "./workspace/input-plan.js";
|
|
13
|
-
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";
|
|
14
22
|
import { evaluateProjectPreflight } from "./workspace/preflight.js";
|
|
15
|
-
import { runResearchWorkspace } from "./workspace/runtime.js";
|
|
23
|
+
import { abortNativeResearchStage, inspectNativeResearchStage, prepareNativeResearchStage, requestResearchHandoff, resolveResearchHandoff, runResearchWorkspace, submitNativeResearchStage, } from "./workspace/runtime.js";
|
|
16
24
|
import { schemaForStage } from "./workspace/schemas.js";
|
|
17
|
-
import { sha256Text, workspacePaths } from "./workspace/storage.js";
|
|
25
|
+
import { pathExists, sha256Text, workspacePaths } from "./workspace/storage.js";
|
|
18
26
|
import { doctorResearchWorkspace, initializeResearchWorkspace, requireResearchWorkspace, withWorkspaceLock, } from "./workspace/workspace.js";
|
|
19
27
|
const COMMON_OPTIONS = { help: "boolean", json: "boolean" };
|
|
20
28
|
const WORKSPACE_OPTIONS = { ...COMMON_OPTIONS, workspace: "string" };
|
|
@@ -55,9 +63,23 @@ export function researchOrchestrationHelp() {
|
|
|
55
63
|
tiangong-ai research project preflight --question <question> [--requirements <absolute-json>] [--input-plan <absolute-json>] [--workspace <path>] [--json]
|
|
56
64
|
tiangong-ai research project input add <project-id> --path <absolute-file> [--role primary|reference|replication] [--workspace <path>] [--json]
|
|
57
65
|
tiangong-ai research project retry <project-id> [--package <package-id>] [--workspace <path>] [--json]
|
|
58
|
-
tiangong-ai research project fork <source-project-id> --to <target-project-id> [--resume-through discover|analyze|synthesize] [--workspace <path>] [--json]
|
|
59
|
-
tiangong-ai research
|
|
60
|
-
tiangong-ai research
|
|
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]
|
|
73
|
+
tiangong-ai research project stage submit <project-id> --session <id> --output <absolute-json> [--confirm-model <id>] [--workspace <path>] [--json]
|
|
74
|
+
tiangong-ai research project stage abort <project-id> --session <id> [--workspace <path>] [--json]
|
|
75
|
+
tiangong-ai research project evidence fetch <project-id> --request <absolute-json> [--workspace <path>] [--json]
|
|
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]
|
|
61
83
|
tiangong-ai research run [--project <project-id>] [--max-parallel <1-8>] [--max-cycles <1-100>] [--dry-run] [--progress-jsonl] [--workspace <absolute-path>] [--json]
|
|
62
84
|
|
|
63
85
|
${researchSetupHelp()}
|
|
@@ -74,6 +96,7 @@ async function runSchema(argv, io) {
|
|
|
74
96
|
return writeHelp(io);
|
|
75
97
|
const stage = onePositional(args.positionals, "research schema show");
|
|
76
98
|
if (stage !== "discover" &&
|
|
99
|
+
stage !== "acquire" &&
|
|
77
100
|
stage !== "analyze" &&
|
|
78
101
|
stage !== "synthesize" &&
|
|
79
102
|
stage !== "review" &&
|
|
@@ -276,6 +299,279 @@ async function runProject(argv, io) {
|
|
|
276
299
|
const [action, ...rest] = argv;
|
|
277
300
|
if (!action || action === "--help" || action === "-h")
|
|
278
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
|
+
}
|
|
341
|
+
if (action === "stage") {
|
|
342
|
+
const [stageAction, ...stageRest] = rest;
|
|
343
|
+
if (stageAction === "prepare") {
|
|
344
|
+
const args = parseStrictArgs(stageRest, { ...WORKSPACE_OPTIONS, stage: "string", "host-agent": "string" }, "research project stage prepare");
|
|
345
|
+
if (strictBoolean(args, "help"))
|
|
346
|
+
return writeHelp(io);
|
|
347
|
+
const projectId = onePositional(args.positionals, "research project stage prepare");
|
|
348
|
+
const stage = nativeProducerStage(strictString(args, "stage"));
|
|
349
|
+
const hostAgent = nativeHostAgent(strictString(args, "host-agent"));
|
|
350
|
+
const root = await workspaceFromArgs(args);
|
|
351
|
+
const result = await prepareNativeResearchStage({ root, projectId, stage, hostAgent });
|
|
352
|
+
writeJson(io, result, args);
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
if (stageAction === "submit") {
|
|
356
|
+
const args = parseStrictArgs(stageRest, {
|
|
357
|
+
...WORKSPACE_OPTIONS,
|
|
358
|
+
session: "string",
|
|
359
|
+
output: "string",
|
|
360
|
+
"confirm-model": "string",
|
|
361
|
+
}, "research project stage submit");
|
|
362
|
+
if (strictBoolean(args, "help"))
|
|
363
|
+
return writeHelp(io);
|
|
364
|
+
const projectId = onePositional(args.positionals, "research project stage submit");
|
|
365
|
+
const sessionId = strictString(args, "session");
|
|
366
|
+
const outputPath = strictString(args, "output");
|
|
367
|
+
if (!sessionId || !outputPath) {
|
|
368
|
+
throw new CliError("stage submit requires --session and --output.", {
|
|
369
|
+
code: "RESEARCH_NATIVE_STAGE_OUTPUT_INVALID",
|
|
370
|
+
exitCode: 2,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const root = await workspaceFromArgs(args);
|
|
374
|
+
const result = await submitNativeResearchStage({
|
|
375
|
+
root,
|
|
376
|
+
projectId,
|
|
377
|
+
sessionId,
|
|
378
|
+
outputPath,
|
|
379
|
+
confirmedModel: strictString(args, "confirm-model") ?? null,
|
|
380
|
+
});
|
|
381
|
+
writeJson(io, result, args);
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
if (stageAction === "abort") {
|
|
385
|
+
const args = parseStrictArgs(stageRest, { ...WORKSPACE_OPTIONS, session: "string" }, "research project stage abort");
|
|
386
|
+
if (strictBoolean(args, "help"))
|
|
387
|
+
return writeHelp(io);
|
|
388
|
+
const projectId = onePositional(args.positionals, "research project stage abort");
|
|
389
|
+
const sessionId = strictString(args, "session");
|
|
390
|
+
if (!sessionId) {
|
|
391
|
+
throw new CliError("stage abort requires --session.", {
|
|
392
|
+
code: "RESEARCH_NATIVE_STAGE_SESSION_REQUIRED",
|
|
393
|
+
exitCode: 2,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const root = await workspaceFromArgs(args);
|
|
397
|
+
const result = await abortNativeResearchStage({ root, projectId, sessionId });
|
|
398
|
+
writeJson(io, result, args);
|
|
399
|
+
return 0;
|
|
400
|
+
}
|
|
401
|
+
throw unknownAction("research project stage", stageAction ?? "");
|
|
402
|
+
}
|
|
403
|
+
if (action === "evidence") {
|
|
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
|
+
}
|
|
555
|
+
if (evidenceAction !== "fetch") {
|
|
556
|
+
throw unknownAction("research project evidence", evidenceAction ?? "");
|
|
557
|
+
}
|
|
558
|
+
const args = parseStrictArgs(evidenceRest, { ...WORKSPACE_OPTIONS, request: "string" }, "research project evidence fetch");
|
|
559
|
+
if (strictBoolean(args, "help"))
|
|
560
|
+
return writeHelp(io);
|
|
561
|
+
const projectId = onePositional(args.positionals, "research project evidence fetch");
|
|
562
|
+
const requestPath = strictString(args, "request");
|
|
563
|
+
if (!requestPath) {
|
|
564
|
+
throw new CliError("evidence fetch requires --request.", {
|
|
565
|
+
code: "RESEARCH_BROKER_REQUEST_INVALID",
|
|
566
|
+
exitCode: 2,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
const request = await readNativeEvidenceRequest(requestPath);
|
|
570
|
+
const root = await workspaceFromArgs(args);
|
|
571
|
+
const result = await withWorkspaceLock(root, "research.native-evidence.fetch", () => fetchNativeCandidateSource({ root, projectId, request }));
|
|
572
|
+
writeJson(io, result, args);
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
279
575
|
if (action === "init") {
|
|
280
576
|
const args = parseStrictArgs(rest, {
|
|
281
577
|
...WORKSPACE_OPTIONS,
|
|
@@ -377,33 +673,180 @@ async function runProject(argv, io) {
|
|
|
377
673
|
writeJson(io, project, args);
|
|
378
674
|
return 0;
|
|
379
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
|
+
}
|
|
380
710
|
throw unknownAction("research project", action);
|
|
381
711
|
}
|
|
382
712
|
async function runStatus(argv, io) {
|
|
383
|
-
const args = parseStrictArgs(argv, { ...WORKSPACE_OPTIONS, project: "string" }, "research status");
|
|
713
|
+
const args = parseStrictArgs(argv, { ...WORKSPACE_OPTIONS, project: "string", all: "boolean" }, "research status");
|
|
384
714
|
if (strictBoolean(args, "help"))
|
|
385
715
|
return writeHelp(io);
|
|
386
716
|
rejectPositionals(args.positionals, "research status");
|
|
387
717
|
const root = await workspaceFromArgs(args);
|
|
388
718
|
const selectedProject = strictString(args, "project");
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
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");
|
|
392
731
|
const result = {
|
|
393
732
|
workspace: root,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
};
|
|
402
764
|
})),
|
|
403
765
|
};
|
|
404
766
|
writeJson(io, result, args);
|
|
405
767
|
return 0;
|
|
406
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
|
+
}
|
|
407
850
|
async function runWorkspaceExecution(argv, io) {
|
|
408
851
|
const args = parseStrictArgs(argv, {
|
|
409
852
|
...WORKSPACE_OPTIONS,
|
|
@@ -457,6 +900,8 @@ async function readEvidenceRequirements(path) {
|
|
|
457
900
|
!Array.isArray(value.sourceTypes) ||
|
|
458
901
|
(value.requiredCapabilityIds !== undefined &&
|
|
459
902
|
!Array.isArray(value.requiredCapabilityIds)) ||
|
|
903
|
+
(value.requiredCompanionIds !== undefined &&
|
|
904
|
+
!Array.isArray(value.requiredCompanionIds)) ||
|
|
460
905
|
(value.requiredDiscoveryScopes !== undefined &&
|
|
461
906
|
!Array.isArray(value.requiredDiscoveryScopes)) ||
|
|
462
907
|
typeof value.minSources !== "number" ||
|
|
@@ -474,6 +919,7 @@ async function readEvidenceRequirements(path) {
|
|
|
474
919
|
return normalizeEvidenceRequirements({
|
|
475
920
|
...value,
|
|
476
921
|
requiredCapabilityIds: value.requiredCapabilityIds ?? [],
|
|
922
|
+
requiredCompanionIds: value.requiredCompanionIds ?? [],
|
|
477
923
|
requiredDiscoveryScopes: value.requiredDiscoveryScopes ?? [],
|
|
478
924
|
});
|
|
479
925
|
}
|
|
@@ -490,13 +936,65 @@ function researchMode(value) {
|
|
|
490
936
|
function resumeStage(value) {
|
|
491
937
|
if (!value)
|
|
492
938
|
return undefined;
|
|
493
|
-
if (value === "discover" || value === "analyze" || value === "synthesize")
|
|
939
|
+
if (value === "discover" || value === "acquire" || value === "analyze" || value === "synthesize")
|
|
494
940
|
return value;
|
|
495
941
|
throw new CliError(`Unsupported --resume-through stage: ${value}`, {
|
|
496
942
|
code: "RESEARCH_PROJECT_FORK_INVALID",
|
|
497
943
|
exitCode: 2,
|
|
498
944
|
});
|
|
499
945
|
}
|
|
946
|
+
function nativeProducerStage(value) {
|
|
947
|
+
if (value === "discover" || value === "acquire" || value === "analyze" || value === "synthesize")
|
|
948
|
+
return value;
|
|
949
|
+
throw new CliError("--stage must be discover, acquire, analyze, or synthesize.", {
|
|
950
|
+
code: "RESEARCH_NATIVE_STAGE_INVALID",
|
|
951
|
+
exitCode: 2,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
function nativeHostAgent(value) {
|
|
955
|
+
if (value === "codex" || value === "claude")
|
|
956
|
+
return value;
|
|
957
|
+
throw new CliError("--host-agent must be codex or claude.", {
|
|
958
|
+
code: "RESEARCH_NATIVE_HOST_INVALID",
|
|
959
|
+
exitCode: 2,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
async function readNativeEvidenceRequest(path) {
|
|
963
|
+
return readBoundedJsonRecord(path, "--request", "RESEARCH_BROKER_REQUEST_INVALID");
|
|
964
|
+
}
|
|
965
|
+
async function readBoundedJsonRecord(path, label, code) {
|
|
966
|
+
if (!isAbsolute(path)) {
|
|
967
|
+
throw new CliError(`${label} must be an absolute JSON file path.`, {
|
|
968
|
+
code,
|
|
969
|
+
exitCode: 2,
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
const selected = resolve(path);
|
|
973
|
+
const info = await lstat(selected).catch(() => undefined);
|
|
974
|
+
if (!info?.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) {
|
|
975
|
+
throw new CliError(`${label} must be a bounded regular non-symlink JSON file.`, {
|
|
976
|
+
code,
|
|
977
|
+
exitCode: 2,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
let value;
|
|
981
|
+
try {
|
|
982
|
+
value = JSON.parse(await readFile(selected, "utf8"));
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
throw new CliError(`${label} contains invalid JSON.`, {
|
|
986
|
+
code,
|
|
987
|
+
exitCode: 2,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
991
|
+
throw new CliError(`${label} must contain one JSON object.`, {
|
|
992
|
+
code,
|
|
993
|
+
exitCode: 2,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
return value;
|
|
997
|
+
}
|
|
500
998
|
async function workspaceFromArgs(args) {
|
|
501
999
|
return requireResearchWorkspace(strictString(args, "workspace") ?? process.cwd());
|
|
502
1000
|
}
|